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 <tag>` 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.
This commit is contained in:
can1357
2026-05-30 19:22:36 +02:00
parent 6c43cd6df1
commit c7c627f0c5
4 changed files with 72 additions and 16 deletions
+18 -7
View File
@@ -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
+1
View File
@@ -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",
+16 -2
View File
@@ -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<GeneratedLeafPackage[]> {
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 });
+34 -4
View File
@@ -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 <tag>` 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<void> {
async function publishNativeLeafPackage(tag: string): Promise<void> {
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) {
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<void> {
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<void> {
}
if (import.meta.main) {
if (nativeLeafTag) {
await publishNativeLeafPackage(nativeLeafTag);
} else {
for (const pkg of packages) {
await publishPackage(pkg);
}
}
}