From 244d65e133ed5370fb6732088c7271e5e3e3b717 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Mon, 24 Aug 2026 23:38:58 -0700 Subject: [PATCH] ci(release): migrate npm publishing to OIDC --- .github/workflows/publish.yaml | 5 +++- scripts/publish/src/ci/bin.ts | 5 ++++ scripts/publish/src/lib/version.test.ts | 28 ++++++++++++++++++++-- scripts/publish/src/lib/version.ts | 31 +++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index be44656971..14120693fb 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -248,6 +248,9 @@ jobs: name: Publish npm if: ${{ !cancelled() && needs.wasm-commands.result == 'success' && needs.codex-wasm.result == 'success' && needs.build-sidecar.result == 'success' && needs.build-sidecar-darwin.result == 'success' }} runs-on: ubuntu-latest + permissions: + contents: read + id-token: write steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 @@ -319,7 +322,7 @@ jobs: --version ${{ needs.context.outputs.version }} - name: Publish npm packages env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: "" run: | pnpm --filter=publish exec tsx src/ci/bin.ts publish-npm \ --tag ${{ needs.context.outputs.npm_tag }} \ diff --git a/scripts/publish/src/ci/bin.ts b/scripts/publish/src/ci/bin.ts index 55e3ed4c16..8792fdda19 100644 --- a/scripts/publish/src/ci/bin.ts +++ b/scripts/publish/src/ci/bin.ts @@ -147,6 +147,10 @@ program "--version-only", "Only rewrite version fields without publish-time dependency injection", ) + .option( + "--repository ", + "GitHub repository recorded in publish-time package metadata (defaults to GITHUB_REPOSITORY)", + ) .option("--dry-run", "Do not write, only report") .action(async (opts) => { const repoRoot = findRepoRoot(); @@ -155,6 +159,7 @@ program await bumpPackageJsons(repoRoot, version, { dryRun: !!opts.dryRun, versionOnly: !!opts.versionOnly, + repository: opts.repository ?? process.env.GITHUB_REPOSITORY, }); await bumpCargoVersions(repoRoot, version, { dryRun: !!opts.dryRun }); }); diff --git a/scripts/publish/src/lib/version.test.ts b/scripts/publish/src/lib/version.test.ts index 0dbd535e69..4f79f9710c 100644 --- a/scripts/publish/src/lib/version.test.ts +++ b/scripts/publish/src/lib/version.test.ts @@ -4,7 +4,11 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import test from "node:test"; import { DEFAULT_SIDECAR_PLATFORMS } from "./packages.js"; -import { bumpCargoVersions, bumpPackageJsons } from "./version.js"; +import { + bumpCargoVersions, + bumpPackageJsons, + githubRepositoryUrl, +} from "./version.js"; async function writeJson(root: string, rel: string, value: unknown) { const path = join(root, rel); @@ -105,7 +109,9 @@ test("bumpPackageJsons injects sidecar platform optional dependencies", async () }); } - await bumpPackageJsons(repoRoot, "0.3.0"); + await bumpPackageJsons(repoRoot, "0.3.0", { + repository: "rivet-dev/agentos", + }); const sidecarManifest = JSON.parse( await readFile( @@ -129,6 +135,11 @@ test("bumpPackageJsons injects sidecar platform optional dependencies", async () "utf8", ), ); + assert.deepEqual(sidecarManifest.repository, { + type: "git", + url: "https://github.com/rivet-dev/agentos.git", + directory: "packages/sidecar-binary", + }); assert.deepEqual( runtimeSidecarManifest.optionalDependencies, Object.fromEntries( @@ -178,6 +189,7 @@ test("bumpPackageJsons pins lockstep and independent AgentOS Apps runtimes", asy } await bumpPackageJsons(repoRoot, "0.0.0-preview.abc1234", { + repository: "rivet-dev/agentos", resolveNpmLatestVersion: async (name) => { assert.equal(name, "@agentos-software/tar"); return "0.3.5"; @@ -199,3 +211,15 @@ test("bumpPackageJsons pins lockstep and independent AgentOS Apps runtimes", asy await rm(repoRoot, { recursive: true, force: true }); } }); + +test("githubRepositoryUrl validates owner/repo slugs", () => { + assert.equal( + githubRepositoryUrl("rivet-dev/agentos"), + "https://github.com/rivet-dev/agentos.git", + ); + assert.throws(() => githubRepositoryUrl("rivet-dev"), /expected owner\/repo/); + assert.throws( + () => githubRepositoryUrl("https://github.com/rivet-dev/agentos"), + /expected owner\/repo/, + ); +}); diff --git a/scripts/publish/src/lib/version.ts b/scripts/publish/src/lib/version.ts index 32fa5b8a7d..857c0d2b1d 100644 --- a/scripts/publish/src/lib/version.ts +++ b/scripts/publish/src/lib/version.ts @@ -28,6 +28,11 @@ const log = scoped("version"); interface PackageJson { name?: string; version?: string; + repository?: { + type: "git"; + url: string; + directory: string; + }; dependencies?: Record; devDependencies?: Record; peerDependencies?: Record; @@ -97,6 +102,26 @@ export interface BumpOptions { * the publish-time mode used by CI — never committed. */ versionOnly?: boolean; + /** GitHub repository slug recorded in publish-time package metadata. */ + repository?: string; +} + +export function githubRepositoryUrl(repository: string): string { + if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository)) { + throw new Error( + `invalid GitHub repository ${JSON.stringify(repository)}; expected owner/repo`, + ); + } + return `https://github.com/${repository}.git`; +} + +function requirePublishRepository(repository: string | undefined): string { + if (!repository) { + throw new Error( + "publish-time package metadata requires a GitHub repository", + ); + } + return repository; } /** @@ -171,6 +196,12 @@ export async function bumpPackageJsons( pkgJson.version = version; if (!versionOnly) { + pkgJson.repository = { + type: "git", + url: githubRepositoryUrl(requirePublishRepository(opts.repository)), + directory: pkg.relDir, + }; + // Inject optionalDependencies on meta packages so end users get the // correct platform-specific binary via npm's os/cpu/libc resolution. const platformPkgs = metaPlatformMap.get(pkg.name);