diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 526319d3..5bcbce2d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,22 +3,83 @@ name: Publish to npm on: release: types: [published] + workflow_dispatch: + inputs: + release_tag: + description: Existing unpublished GitHub release tag to recover + required: true + type: string jobs: publish: - if: github.event.release.target_commitish == 'main' + if: >- + (github.event_name == 'release' && github.event.release.target_commitish == 'main') || + (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') runs-on: ubuntu-latest permissions: contents: read id-token: write + env: + RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.event.release.tag_name }} + TERM_SESSION_ID: ${{ format('jumbo-publish-{0}-{1}', github.run_id, github.run_attempt) }} steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: refs/tags/${{ env.RELEASE_TAG }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: '22' registry-url: 'https://registry.npmjs.org' + - name: Validate unpublished release tag + env: + GH_TOKEN: ${{ github.token }} + run: | + node --input-type=module --eval ' + import { readFile } from "node:fs/promises"; + + const releaseTag = process.env.RELEASE_TAG; + if (!releaseTag) { + throw new Error("A release tag is required"); + } + + const packageJson = JSON.parse(await readFile("package.json", "utf8")); + const tagVersion = releaseTag.startsWith("v") ? releaseTag.slice(1) : releaseTag; + if (packageJson.version !== tagVersion) { + throw new Error(`Release tag ${releaseTag} does not match package version ${packageJson.version}`); + } + + const releaseUrl = `https://api.github.com/repos/${process.env.GITHUB_REPOSITORY}/releases/tags/${encodeURIComponent(releaseTag)}`; + const githubHeaders = { + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }; + if (process.env.GH_TOKEN) { + githubHeaders.Authorization = `Bearer ${process.env.GH_TOKEN}`; + } + const releaseResponse = await fetch(releaseUrl, { headers: githubHeaders }); + if (!releaseResponse.ok) { + throw new Error(`GitHub release ${releaseTag} is unavailable (${releaseResponse.status})`); + } + + const release = await releaseResponse.json(); + if (release.draft || !release.published_at || release.target_commitish !== "main") { + throw new Error(`GitHub release ${releaseTag} is not a published main-branch release`); + } + + const registryUrl = `https://registry.npmjs.org/${encodeURIComponent(packageJson.name)}/${encodeURIComponent(packageJson.version)}`; + const registryResponse = await fetch(registryUrl); + if (registryResponse.status === 200) { + throw new Error(`${packageJson.name}@${packageJson.version} is already published`); + } + if (registryResponse.status !== 404) { + throw new Error(`Could not verify npm publication state (${registryResponse.status})`); + } + + console.log(`Validated unpublished release ${releaseTag}`); + ' + - run: npm ci - run: npm run build diff --git a/tests/presentation/work/worker-daemon.production.integration.test.ts b/tests/presentation/work/worker-daemon.production.integration.test.ts index 777d266d..25b2fa10 100644 --- a/tests/presentation/work/worker-daemon.production.integration.test.ts +++ b/tests/presentation/work/worker-daemon.production.integration.test.ts @@ -24,6 +24,7 @@ const COMPILED_CONTROLLER = path.join( "NodeWorkerDaemonProcessController.js", ); const COMPILED_CLI = path.join(PROJECT_ROOT, "dist", "cli.js"); +const TEST_HOST_SESSION_ID = "jumbo-worker-daemon-production-integration"; let workspaceRoot: string; let originalPath: string | undefined; @@ -143,6 +144,7 @@ async function createProductionManager(): Promise { Path: `${fakeAgentBin}${path.delimiter}${inheritedPath}`, PATH: `${fakeAgentBin}${path.delimiter}${inheritedPath}`, JUMBO_FAKE_AGENT_CLI: COMPILED_CLI, + TERM_SESSION_ID: TEST_HOST_SESSION_ID, JUMBO_AGENT_COMMAND_VIBE: process.execPath, JUMBO_AGENT_ARGS_VIBE: JSON.stringify([ fakeAgentScript,