From c7c627f0c5ae3e58562d54a7c39350b8b76af286 Mon Sep 17 00:00:00 2001 From: can1357 Date: Sat, 30 May 2026 19:22:36 +0200 Subject: [PATCH] ci(ci): published native leaf packages per target in the release flow - CI now enabled OIDC publishing in the build matrix, installed Node 24/npm, and added a per-target native addon publish step. - The release script now accepts `--native-leaf ` and publishes only the matching generated native leaf package. - Native package generation gained optional tag filtering with validation of requested leaf tags for targeted release publishing. --- .github/workflows/ci.yml | 25 +++++++---- package.json | 1 + packages/natives/scripts/gen-npm-packages.ts | 18 +++++++- scripts/ci-release-publish.ts | 44 ++++++++++++++++---- 4 files changed, 72 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2db3c98c2..d20642091 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -368,11 +368,20 @@ jobs: runs-on: ${{ matrix.os }} permissions: contents: read + id-token: write steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 with: bun-version: "1.3" + - uses: actions/setup-node@v4 + with: + node-version: "24" + registry-url: "https://registry.npmjs.org" + # Trusted publishing allowed-actions flags require npm >= 11.16.0. + - name: Ensure npm supports trusted publishing + if: ${{ !inputs.skip_npm }} + run: npm install -g npm@latest - name: Cache bun dependencies uses: actions/cache@v4 with: @@ -399,6 +408,14 @@ jobs: runtime_dir="$(mktemp -d)" HOME="$runtime_dir/home" XDG_DATA_HOME="$runtime_dir/xdg" "${{ matrix.binary_path }}" --version HOME="$runtime_dir/home" XDG_DATA_HOME="$runtime_dir/xdg" "${{ matrix.binary_path }}" --smoke-test + - name: Publish native addon package + if: ${{ !inputs.skip_npm }} + env: + # Fallback auth: setup-node wrote an .npmrc referencing + # NODE_AUTH_TOKEN; npm uses it only when OIDC has no trusted + # publisher for the package (or on a first publish). + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: bun run ci:release:publish-native-leaf ${{ matrix.target_id }} - name: Upload release binary artifact uses: actions/upload-artifact@v4 with: @@ -457,7 +474,7 @@ jobs: needs.release_binary.result == 'success' && needs.release_github_verify.result == 'success' && !inputs.skip_npm }} - needs: [release_binary, release_github_verify, rust-hash] + needs: [release_binary, release_github_verify] runs-on: ubuntu-22.04 # `id-token: write` lets npm mint the GitHub OIDC token it exchanges for a # short-lived publish token (trusted publishing + provenance). When a @@ -484,12 +501,6 @@ jobs: path: ~/.bun/install/cache key: bun-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} - run: bun install --frozen-lockfile - - name: Download native addons - uses: actions/download-artifact@v4 - with: - pattern: pi-natives-*-h${{ needs.rust-hash.outputs.hash }} - path: packages/natives/native - merge-multiple: true - name: Publish to npm env: # Fallback auth: setup-node wrote an .npmrc referencing diff --git a/package.json b/package.json index 334472716..ba916da56 100644 --- a/package.json +++ b/package.json @@ -115,6 +115,7 @@ "ci:test:install-methods": "bash scripts/install-tests/run-ci.sh", "ci:release:build-binaries": "bun scripts/ci-release-build-binaries.ts", "ci:release:publish": "bun scripts/ci-release-publish.ts", + "ci:release:publish-native-leaf": "bun scripts/ci-release-publish.ts --native-leaf", "bench:gen-fixtures": "bun --cwd=packages/typescript-edit-benchmark run src/generate.ts --typescript-dir /tmp/typescript-source --count-per-type 8", "bench:edit": "bun --cwd=packages/typescript-edit-benchmark run start", "stats:sync": "python3 scripts/session-stats/sync.py", diff --git a/packages/natives/scripts/gen-npm-packages.ts b/packages/natives/scripts/gen-npm-packages.ts index 4e9629ffa..0b5b9d970 100755 --- a/packages/natives/scripts/gen-npm-packages.ts +++ b/packages/natives/scripts/gen-npm-packages.ts @@ -3,7 +3,7 @@ import * as fs from "node:fs/promises"; import * as path from "node:path"; -interface LeafTarget { +export interface LeafTarget { tag: string; os: string; cpu: string; @@ -44,6 +44,7 @@ export interface GenerateNpmPackagesInput { packageDir?: string; dryRun?: boolean; version?: string; + tags?: readonly string[]; } export const LEAF_TARGETS: readonly LeafTarget[] = [ @@ -108,10 +109,23 @@ function buildReadme(tag: string, manifest: LeafManifest): string { return `# ${manifest.name}\n\nPlatform native addon package for \`@oh-my-pi/pi-natives\` on ${tag}.\n\nThis package is generated during release and installed as an optional dependency of the core package.\n`; } +function selectTargets(tags: readonly string[] | undefined): readonly LeafTarget[] { + if (!tags) return LEAF_TARGETS; + const wanted = new Set(tags); + const targets = LEAF_TARGETS.filter(target => wanted.has(target.tag)); + if (targets.length !== wanted.size) { + const known = new Set(LEAF_TARGETS.map(target => target.tag)); + const unknown = tags.filter(tag => !known.has(tag)); + throw new Error(`Unknown native package tag(s): ${unknown.join(", ")}`); + } + return targets; +} + export async function generateNpmPackages({ packageDir = packageDirDefault, dryRun = false, version, + tags, }: GenerateNpmPackagesInput = {}): Promise { const manifestVersion = version ?? ((await Bun.file(path.join(packageDir, "package.json")).json()) as { version: string }).version; @@ -119,7 +133,7 @@ export async function generateNpmPackages({ const npmDir = path.join(packageDir, "npm"); const leaves: GeneratedLeafPackage[] = []; - for (const target of LEAF_TARGETS) { + for (const target of selectTargets(tags)) { const files = await discoverAddonFiles(nativeDir, target.tag); const manifestFiles = files.length > 0 ? files : [expectedAddonFilenames(target.tag)[0]]; const manifest = buildLeafManifest({ ...target, files: manifestFiles, version: manifestVersion }); diff --git a/scripts/ci-release-publish.ts b/scripts/ci-release-publish.ts index a37b291bd..172e903ab 100644 --- a/scripts/ci-release-publish.ts +++ b/scripts/ci-release-publish.ts @@ -2,6 +2,11 @@ /** * Publish workspace packages. * + * The default mode publishes public JS packages and the `@oh-my-pi/pi-natives` + * core package. Generated native leaf packages are published separately with + * `--native-leaf ` from the release_binary matrix after that matrix entry + * downloads the matching `.node` artifacts. + * * For each public TypeScript package we: * 1. Emit `.d.ts` declarations into `dist/types/` so consumers get * stable types regardless of their tsconfig `lib`. @@ -54,6 +59,21 @@ interface PackageManifest extends JsonObject { const repoRoot = path.join(import.meta.dir, ".."); const isDryRun = process.argv.includes("--dry-run"); + +function nativeLeafTagFromArgs(argv: readonly string[]): string | null { + for (let i = 0; i < argv.length; i++) { + const arg = argv[i]; + if (arg === "--native-leaf") { + const tag = argv[i + 1]; + if (!tag) throw new Error("--native-leaf requires a native target tag"); + return tag; + } + if (arg.startsWith("--native-leaf=")) return arg.slice("--native-leaf=".length); + } + return null; +} + +const nativeLeafTag = nativeLeafTagFromArgs(process.argv.slice(2)); export const packages: PublishPackage[] = [ { dir: "packages/utils", kind: "typescript" }, { dir: "packages/ai", kind: "typescript" }, @@ -216,14 +236,20 @@ async function publishGeneratedLeafPackage(leaf: GeneratedLeafPackage): Promise< await packAndPublish(leaf.dir, leaf.manifest.name); } -async function publishNativePackage(pkg: PublishPackage): Promise { +async function publishNativeLeafPackage(tag: string): Promise { + const pkg = packages.find(candidate => candidate.kind === "native"); + if (!pkg) throw new Error("No native package configured"); const pkgDir = path.join(repoRoot, pkg.dir); const coreManifest = (await Bun.file(path.join(pkgDir, "package.json")).json()) as PackageManifest; if (typeof coreManifest.version !== "string") throw new Error(`Missing version in ${pkg.dir}/package.json`); - const leaves = await generateNpmPackages({ packageDir: pkgDir, dryRun: isDryRun, version: coreManifest.version }); - for (const leaf of leaves) { - await publishGeneratedLeafPackage(leaf); - } + const leaves = await generateNpmPackages({ packageDir: pkgDir, dryRun: isDryRun, version: coreManifest.version, tags: [tag] }); + const leaf = leaves[0]; + if (!leaf) throw new Error(`No native leaf generated for ${tag}`); + await publishGeneratedLeafPackage(leaf); +} + +async function publishNativePackage(pkg: PublishPackage): Promise { + const pkgDir = path.join(repoRoot, pkg.dir); const manifest = await prepareNativeCorePackage(pkgDir, !isDryRun); const name = manifest.name ?? path.basename(pkg.dir); if (isDryRun) { @@ -249,7 +275,11 @@ async function publishPackage(pkg: PublishPackage): Promise { } if (import.meta.main) { - for (const pkg of packages) { - await publishPackage(pkg); + if (nativeLeafTag) { + await publishNativeLeafPackage(nativeLeafTag); + } else { + for (const pkg of packages) { + await publishPackage(pkg); + } } }