diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3cf66c3..55b8049 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,10 @@ on: branches: - main +permissions: + contents: write + id-token: write + jobs: build: strategy: @@ -47,5 +51,44 @@ jobs: name: ${{ matrix.settings.artifact }} path: target - - name: Publish - run: pnpm changeset publish + publish: + name: Publish to npm + runs-on: ubuntu-latest + needs: build + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 25.9.0 + registry-url: 'https://registry.npmjs.org' + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 10.33.0 + run_install: true + + - name: Publish packages + id: changesets + uses: changesets/action@v1 + with: + publish: pnpm exec changeset publish + createGithubReleases: true + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Download build artifacts + if: steps.changesets.outputs.published == 'true' + uses: actions/download-artifact@v6 + with: + path: artifacts + + - name: Upload executable archives to release + if: steps.changesets.outputs.published == 'true' + run: node scripts/upload-release-assets.ts + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }} diff --git a/scripts/upload-release-assets.ts b/scripts/upload-release-assets.ts new file mode 100644 index 0000000..daac802 --- /dev/null +++ b/scripts/upload-release-assets.ts @@ -0,0 +1,63 @@ +import { spawn } from 'node:child_process' +import { readdir } from 'node:fs/promises' +import { join, resolve } from 'node:path' +import process from 'node:process' + +type RunOptions = Parameters[2] + +interface PublishedPackage { + name: string + version: string +} + +function run(command: string, args: string[], options: RunOptions = {}) { + return new Promise((resolvePromise, rejectPromise) => { + const child = spawn(command, args, { stdio: 'inherit', ...options }) + child.on('error', rejectPromise) + child.on('close', (code) => { + if (code === 0) { + resolvePromise() + return + } + rejectPromise(new Error(`Command failed: ${command} ${args.join(' ')}`)) + }) + }) +} + +function resolveReleaseMeta() { + const raw = process.env.PUBLISHED_PACKAGES ?? '[]' + const publishedPackages = JSON.parse(raw) as PublishedPackage[] + if (!Array.isArray(publishedPackages) || publishedPackages.length === 0) { + throw new Error('No published packages returned by changesets/action') + } + const pkg = publishedPackages.find(item => item.name === '@arkts/mcp') ?? publishedPackages[0] + const version = pkg.version + const tagName = `${pkg.name}@${pkg.version}` + return { version, tagName } +} + +async function main() { + const artifactsDir = resolve(process.env.ARTIFACTS_DIR ?? 'artifacts') + const releaseAssetsDir = resolve(process.env.RELEASE_ASSETS_DIR ?? 'release-assets') + const { version, tagName } = resolveReleaseMeta() + + await run('mkdir', ['-p', releaseAssetsDir]) + + const entries = await readdir(artifactsDir, { withFileTypes: true }) + const artifactDirs = entries.filter(entry => entry.isDirectory()).map(entry => entry.name) + if (artifactDirs.length === 0) { + throw new Error(`No artifact directories found in ${artifactsDir}`) + } + + for (const artifactName of artifactDirs) { + const artifactPath = join(artifactsDir, artifactName) + const zipPath = join(releaseAssetsDir, `${artifactName}-${version}.zip`) + await run('zip', ['-r', zipPath, '.'], { cwd: artifactPath }) + } + + await run('gh', ['release', 'upload', tagName, `${releaseAssetsDir}/*.zip`, '--clobber'], { + shell: true, + }) +} + +await main()