name: Publish
# Based on: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
on:
push:
tags:
- 'v*'
permissions:
id-token: write # Required for OIDC
contents: read
jobs:
build_and_check:
name: publish
runs-on: ubuntu-latest
timeout-minutes: 30
env:
NODE_VERSION: '22'
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_CACHE: 'remote:rw'
permissions:
actions: read
contents: write
id-token: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '22'
registry-url: 'https://registry.npmjs.org'
- name: Cache node modules
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Slack start message
uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0
with:
payload: |
{"text": "Starting publish: ${{ github.ref_name }}"}
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build:all
env:
MEDPLUM_BASE_URL: '__MEDPLUM_BASE_URL__'
MEDPLUM_CLIENT_ID: '__MEDPLUM_CLIENT_ID__'
MEDPLUM_REGISTER_ENABLED: '__MEDPLUM_REGISTER_ENABLED__'
MEDPLUM_AWS_TEXTRACT_ENABLED: '__MEDPLUM_AWS_TEXTRACT_ENABLED__'
GOOGLE_CLIENT_ID: '__GOOGLE_CLIENT_ID__'
RECAPTCHA_SITE_KEY: '__RECAPTCHA_SITE_KEY__'
- name: Update npm # Ensure npm 11.5.1 or later is installed
run: npm install -g npm@latest
- name: Publish to NPM
run: ./scripts/publish.sh
- name: Sync example repos
run: ./scripts/update-example-changes.sh "${{ secrets.SYNC_REPO_TOKEN }}"
- name: Login to Docker Hub
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
- name: Build and push server Docker image
run: ./scripts/build-docker-server.sh --release
env:
SERVER_DOCKERHUB_REPOSITORY: ${{ secrets.DOCKERHUB_REPOSITORY }}
- name: Build and push app Docker image
run: ./scripts/build-docker-app.sh --release
env:
APP_DOCKERHUB_REPOSITORY: ${{ secrets.APP_DOCKERHUB_REPOSITORY }}
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Deploy bot layer
run: ./scripts/deploy-bot-layer.sh
build_agent_win64:
runs-on: windows-latest
timeout-minutes: 45
env:
NODE_VERSION: '24'
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_CACHE: 'remote:rw'
permissions:
actions: read
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install NSIS
run: choco install nsis
- name: Add NSIS to PATH
run: echo "C:\Program Files (x86)\NSIS" >> $GITHUB_PATH
shell: bash
- name: Install Wget
run: choco install wget
- name: Import GPG key
run: echo "${{ secrets.MEDPLUM_RELEASE_GPG_KEY }}" | gpg --batch --no-tty --import
- name: Setup Java
uses: actions/setup-java@17f84c3641ba7b8f6deff6309fc4c864478f5d62 # v3.14.1
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
# See: https://github.com/actions/cache/blob/5a3ec84eff668545956fd18022155c47e93e2684/examples.md#node---npm
- name: Get npm cache directory
id: npm-cache-dir
shell: pwsh
run: echo "dir=$(npm config get cache)" >> ${env:GITHUB_OUTPUT}
- name: Cache node modules
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
env:
cache-name: cache-node-modules
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-build-agent-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-agent-${{ env.cache-name }}-
${{ runner.os }}-build-agent
${{ runner.os }}-
- name: Install dependencies
run: npm ci
- name: Set repo hash for agent build
shell: bash
# This forces git shorthash to match between @medplum/agent and @medplum/core
run: |
set -e
echo "MEDPLUM_GIT_SHORTHASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV
- name: Build
run: npm run build -- --filter=@medplum/agent
- name: Find signtool
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
id: find-signtool
with:
result-encoding: string
script: |
const fs = require('node:fs/promises');
/**
* Searches the installed Windows SDKs for the most recent signtool.exe version
* Taken from https://github.com/dlemstra/code-sign-action
* @returns Path to most recent signtool.exe (x86 version)
*/
async function getSigntoolLocation() {
const windowsKitsFolder = 'C:/Program Files (x86)/Windows Kits/10/bin/';
const folders = await fs.readdir(windowsKitsFolder);
let fileName = '';
let maxVersion = 0;
for (const folder of folders) {
if (!folder.endsWith('.0')) {
continue;
}
const folderVersion = Number.parseInt(folder.replaceAll('.',''));
if (folderVersion > maxVersion) {
const signtoolFilename = `${windowsKitsFolder}${folder}/x64/signtool.exe`;
try {
const stat = await fs.stat(signtoolFilename);
if (stat.isFile()) {
fileName = signtoolFilename;
maxVersion = folderVersion;
}
} catch {
console.warn('Skipping %s due to error.', signtoolFilename);
}
}
}
if(fileName == '') {
throw new Error('Unable to find signtool.exe in ' + windowsKitsFolder);
}
console.log(fileName);
return fileName;
}
const path = await getSigntoolLocation();
return path.replace(' ', '\ ');
- name: Build Agent installer
shell: bash
run: ./scripts/build-agent-installer-win64.sh
env:
GPG_KEY_ID: ${{ secrets.MEDPLUM_RELEASE_GPG_KEY_ID }}
GPG_PASSPHRASE: ${{ secrets.MEDPLUM_RELEASE_GPG_PASSPHRASE }}
SIGNTOOL_PATH: ${{steps.find-signtool.outputs.result}}
SM_HOST: ${{ secrets.SM_HOST }}
SM_API_KEY: ${{ secrets.SM_API_KEY }}
SM_CLIENT_CERT_FILE_BASE64: ${{ secrets.SM_CLIENT_CERT_FILE_BASE64 }}
SM_CLIENT_CERT_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD }}
SM_CERT_ALIAS: ${{ secrets.SM_CERT_ALIAS }}
- name: Upload Agent installer
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const packageJson = require('./packages/agent/package.json');
const fs = require('fs');
const tag = context.ref.replace("refs/tags/", "");
const release = await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag
});
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.data.id,
name: "medplum-agent-installer-" + packageJson.version + ".exe",
data: await fs.readFileSync(`packages/agent/medplum-agent-installer-${packageJson.version}-${process.env.MEDPLUM_GIT_SHORTHASH}.exe`)
});
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.data.id,
name: "medplum-agent-installer-" + packageJson.version + ".exe.sha256",
data: await fs.readFileSync(`packages/agent/medplum-agent-installer-${packageJson.version}-${process.env.MEDPLUM_GIT_SHORTHASH}.exe.sha256`)
});
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.data.id,
name: "medplum-agent-installer-" + packageJson.version + ".exe.asc",
data: await fs.readFileSync(`packages/agent/medplum-agent-installer-${packageJson.version}-${process.env.MEDPLUM_GIT_SHORTHASH}.exe.asc`)
});
build_agent_linux:
strategy:
matrix:
arch:
- image: ubuntu-latest
suffix: x64
- image: ubuntu-24.04-arm
suffix: arm64
runs-on: ${{ matrix.arch.image }}
timeout-minutes: 45
env:
NODE_VERSION: '24'
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_CACHE: 'remote:rw'
permissions:
actions: read
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Import GPG key
run: echo "${{ secrets.MEDPLUM_RELEASE_GPG_KEY }}" | gpg --batch --no-tty --import
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- name: Cache node modules
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-agent-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-agent-${{ env.cache-name }}-
${{ runner.os }}-build-agent
${{ runner.os }}-
- name: Install dependencies
run: npm ci
- name: Set repo hash for agent build
shell: bash
# This forces git shorthash to match between @medplum/agent and @medplum/core
run: |
set -e
echo "MEDPLUM_GIT_SHORTHASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV
- name: Build
run: npm run build -- --filter=@medplum/agent
- name: Build Agent
shell: bash
run: ./scripts/build-agent-installer-linux.sh
env:
GPG_KEY_ID: ${{ secrets.MEDPLUM_RELEASE_GPG_KEY_ID }}
GPG_PASSPHRASE: ${{ secrets.MEDPLUM_RELEASE_GPG_PASSPHRASE }}
- name: Upload Agent
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const packageJson = require('./packages/agent/package.json');
const fs = require('fs');
const tag = context.ref.replace("refs/tags/", "");
const release = await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag
});
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.data.id,
name: "medplum-agent-" + packageJson.version + "-linux-${{ matrix.arch.suffix }}",
data: await fs.readFileSync(`packages/agent/medplum-agent-${packageJson.version}-linux`)
});
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.data.id,
name: "medplum-agent-" + packageJson.version + "-linux-${{ matrix.arch.suffix }}.sha256",
data: await fs.readFileSync(`packages/agent/medplum-agent-${packageJson.version}-linux.sha256`)
});
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.data.id,
name: "medplum-agent-" + packageJson.version + "-linux-${{ matrix.arch.suffix }}.asc",
data: await fs.readFileSync(`packages/agent/medplum-agent-${packageJson.version}-linux.asc`)
});
build_agent_macos:
runs-on: macos-latest
timeout-minutes: 45
env:
NODE_VERSION: '24'
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_CACHE: 'remote:rw'
permissions:
actions: read
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Import GPG key
run: echo "${{ secrets.MEDPLUM_RELEASE_GPG_KEY }}" | gpg --batch --no-tty --import
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- name: Cache node modules
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-agent-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-agent-${{ env.cache-name }}-
${{ runner.os }}-build-agent
${{ runner.os }}-
- name: Install dependencies
run: npm ci
- name: Set repo hash for agent build
shell: bash
# This forces git shorthash to match between @medplum/agent and @medplum/core
run: |
set -e
echo "MEDPLUM_GIT_SHORTHASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Build
run: npm run build -- --filter=@medplum/agent
- name: Build Agent
shell: bash
run: ./scripts/build-agent-macos.sh
env:
GPG_KEY_ID: ${{ secrets.MEDPLUM_RELEASE_GPG_KEY_ID }}
GPG_PASSPHRASE: ${{ secrets.MEDPLUM_RELEASE_GPG_PASSPHRASE }}
- name: Upload Agent
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const packageJson = require('./packages/agent/package.json');
const fs = require('fs');
const tag = context.ref.replace("refs/tags/", "");
const release = await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag
});
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.data.id,
name: "medplum-agent-" + packageJson.version + "-macos-arm64",
data: await fs.readFileSync(`packages/agent/medplum-agent-${packageJson.version}-macos`)
});
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.data.id,
name: "medplum-agent-" + packageJson.version + "-macos-arm64.sha256",
data: await fs.readFileSync(`packages/agent/medplum-agent-${packageJson.version}-macos.sha256`)
});
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.data.id,
name: "medplum-agent-" + packageJson.version + "-macos-arm64.asc",
data: await fs.readFileSync(`packages/agent/medplum-agent-${packageJson.version}-macos.asc`)
});