diff --git a/.changeset/config.json b/.changeset/config.json index e6cd60457b..2b0fe30902 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -19,13 +19,6 @@ "@executor/ir", "@executor/runtime-deno-subprocess", "@executor/runtime-quickjs", - "@executor-js/plugin-file-secrets", - "@executor-js/plugin-google-discovery", - "@executor-js/plugin-graphql", - "@executor-js/plugin-keychain", - "@executor-js/plugin-mcp", - "@executor-js/plugin-onepassword", - "@executor-js/plugin-openapi", "@executor/marketing" ] } diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eb123a7a92..7fbb97457e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,6 +35,12 @@ jobs: uses: actions/setup-node@v4 with: node-version: 24 + registry-url: https://registry.npmjs.org + + - name: Update npm for trusted publishing + run: | + npm install -g npm@latest + npm --version - name: Install dependencies run: bun install --frozen-lockfile @@ -50,6 +56,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Publish @executor-js library packages + if: steps.changesets.outputs.hasChangesets == 'false' + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: bun run release:publish:packages + - name: Detect release version change if: steps.changesets.outputs.hasChangesets == 'false' id: detect_release diff --git a/RELEASING.md b/RELEASING.md index c84bb9f1cb..6d84e6f463 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,6 +1,8 @@ # Releasing -This repo uses Changesets for version orchestration and a custom publish script for the generated `executor` npm package plus its platform packages. +This repo uses Changesets for version orchestration and two publish paths: +the CLI (`executor` npm package plus its platform packages) and the +`@executor-js/*` library packages (`core`, `sdk`, and the public plugins). ## Normal release flow @@ -9,12 +11,16 @@ This repo uses Changesets for version orchestration and a custom publish script 2. Merge that PR to `main`. 3. `.github/workflows/release.yml` opens or updates a `Version Packages` PR. 4. Merge the `Version Packages` PR. -5. The release workflow tags the commit and dispatches `.github/workflows/publish-executor-package.yml`. -6. The publish workflow: - - runs `bun run release:check` - - performs a full dry-run release build before publish - - publishes npm packages under the correct dist-tag - - creates or updates the GitHub release with build artifacts +5. The release workflow then does two things in parallel: + - Publishes every `@executor-js/*` library package whose current version + is not already on npm, via `bun run release:publish:packages` + (see `scripts/publish-packages.ts`). + - If `apps/cli/package.json` bumped, tags the commit and dispatches + `.github/workflows/publish-executor-package.yml`, which: + - runs `bun run release:check` + - performs a full dry-run release build before publish + - publishes the CLI npm package under the correct dist-tag + - creates or updates the GitHub release with build artifacts ## Beta releases @@ -33,7 +39,7 @@ Beta versions publish to npm under `beta`. ## Local dry run -To build the full release payload without publishing to npm or GitHub: +To build the full CLI release payload without publishing to npm or GitHub: - `bun run release:publish:dry-run` @@ -42,6 +48,10 @@ That produces: - platform archives in `apps/cli/dist` - the packed wrapper tarball in `apps/cli/dist/release` +To pack the `@executor-js/*` library packages without publishing: + +- `bun run release:publish:packages:dry-run` + ## Notes - Changesets owns the published CLI version via `apps/cli/package.json`. diff --git a/package.json b/package.json index 1509975e4b..bf19ccb3c2 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "dev:cli": "bun run apps/cli/src/main.ts", "test": "turbo run test", "test:release:bootstrap": "vitest run tests/release-bootstrap-smoke.test.ts", - "build:packages": "bun run --filter='@executor-js/core' build && bun run --filter='@executor-js/sdk' --filter='@executor/plugin-*' build", + "build:packages": "bun run --filter='@executor-js/core' build && bun run --filter='@executor-js/sdk' --filter='@executor-js/plugin-*' build", "typecheck": "bun run --filter='*' typecheck", "lint": "oxlint -c .oxlintrc.jsonc . --deny-warnings", "lint:fix": "oxlint -c .oxlintrc.jsonc --fix .", @@ -77,6 +77,8 @@ "release:beta:stop": "changeset pre exit", "release:publish:dry-run": "bun run --cwd apps/cli release:publish:dry-run", "release:publish": "bun run --cwd apps/cli release:publish", + "release:publish:packages": "bun run scripts/publish-packages.ts", + "release:publish:packages:dry-run": "bun run scripts/publish-packages.ts --dry-run", "prepare": "effect-language-service patch" }, "devDependencies": { diff --git a/packages/core/core/package.json b/packages/core/core/package.json index 1ad16e1186..f3bc0eaab3 100644 --- a/packages/core/core/package.json +++ b/packages/core/core/package.json @@ -2,6 +2,16 @@ "name": "@executor-js/core", "version": "1.4.0", "type": "module", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/RhysSullivan/executor.git", + "directory": "packages/core/core" + }, + "homepage": "https://github.com/RhysSullivan/executor/tree/main/packages/core/core", + "bugs": { + "url": "https://github.com/RhysSullivan/executor/issues" + }, "exports": { ".": "./src/index.ts" }, diff --git a/packages/plugins/file-secrets/package.json b/packages/plugins/file-secrets/package.json index 0d90872ce2..f5f5b7ebb7 100644 --- a/packages/plugins/file-secrets/package.json +++ b/packages/plugins/file-secrets/package.json @@ -2,6 +2,16 @@ "name": "@executor-js/plugin-file-secrets", "type": "module", "version": "1.4.2", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/RhysSullivan/executor.git", + "directory": "packages/plugins/file-secrets" + }, + "homepage": "https://github.com/RhysSullivan/executor/tree/main/packages/plugins/file-secrets", + "bugs": { + "url": "https://github.com/RhysSullivan/executor/issues" + }, "exports": { ".": "./src/promise.ts", "./core": "./src/index.ts" diff --git a/packages/plugins/google-discovery/package.json b/packages/plugins/google-discovery/package.json index 0b0817883d..55cf5a78cd 100644 --- a/packages/plugins/google-discovery/package.json +++ b/packages/plugins/google-discovery/package.json @@ -2,6 +2,16 @@ "name": "@executor-js/plugin-google-discovery", "type": "module", "version": "1.4.2", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/RhysSullivan/executor.git", + "directory": "packages/plugins/google-discovery" + }, + "homepage": "https://github.com/RhysSullivan/executor/tree/main/packages/plugins/google-discovery", + "bugs": { + "url": "https://github.com/RhysSullivan/executor/issues" + }, "exports": { ".": "./src/promise.ts", "./core": "./src/sdk/index.ts", diff --git a/packages/plugins/graphql/package.json b/packages/plugins/graphql/package.json index ddc625b644..3be0e0332d 100644 --- a/packages/plugins/graphql/package.json +++ b/packages/plugins/graphql/package.json @@ -2,6 +2,16 @@ "name": "@executor-js/plugin-graphql", "type": "module", "version": "1.4.2", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/RhysSullivan/executor.git", + "directory": "packages/plugins/graphql" + }, + "homepage": "https://github.com/RhysSullivan/executor/tree/main/packages/plugins/graphql", + "bugs": { + "url": "https://github.com/RhysSullivan/executor/issues" + }, "exports": { ".": "./src/promise.ts", "./core": "./src/sdk/index.ts", diff --git a/packages/plugins/keychain/package.json b/packages/plugins/keychain/package.json index 881db1ea5d..d1faf1032d 100644 --- a/packages/plugins/keychain/package.json +++ b/packages/plugins/keychain/package.json @@ -2,6 +2,16 @@ "name": "@executor-js/plugin-keychain", "type": "module", "version": "1.4.2", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/RhysSullivan/executor.git", + "directory": "packages/plugins/keychain" + }, + "homepage": "https://github.com/RhysSullivan/executor/tree/main/packages/plugins/keychain", + "bugs": { + "url": "https://github.com/RhysSullivan/executor/issues" + }, "exports": { ".": "./src/promise.ts", "./core": "./src/index.ts" diff --git a/packages/plugins/mcp/package.json b/packages/plugins/mcp/package.json index db15f9033b..6f69d6b77e 100644 --- a/packages/plugins/mcp/package.json +++ b/packages/plugins/mcp/package.json @@ -2,6 +2,16 @@ "name": "@executor-js/plugin-mcp", "type": "module", "version": "1.4.2", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/RhysSullivan/executor.git", + "directory": "packages/plugins/mcp" + }, + "homepage": "https://github.com/RhysSullivan/executor/tree/main/packages/plugins/mcp", + "bugs": { + "url": "https://github.com/RhysSullivan/executor/issues" + }, "exports": { ".": "./src/promise.ts", "./core": "./src/sdk/index.ts", diff --git a/packages/plugins/onepassword/package.json b/packages/plugins/onepassword/package.json index 714a5ff5cd..3015beb9d5 100644 --- a/packages/plugins/onepassword/package.json +++ b/packages/plugins/onepassword/package.json @@ -2,6 +2,16 @@ "name": "@executor-js/plugin-onepassword", "type": "module", "version": "1.4.2", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/RhysSullivan/executor.git", + "directory": "packages/plugins/onepassword" + }, + "homepage": "https://github.com/RhysSullivan/executor/tree/main/packages/plugins/onepassword", + "bugs": { + "url": "https://github.com/RhysSullivan/executor/issues" + }, "exports": { ".": "./src/promise.ts", "./api": "./src/api/index.ts", diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json index 0fe0dbaf06..2a63e6d3af 100644 --- a/packages/plugins/openapi/package.json +++ b/packages/plugins/openapi/package.json @@ -2,6 +2,16 @@ "name": "@executor-js/plugin-openapi", "type": "module", "version": "1.4.2", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/RhysSullivan/executor.git", + "directory": "packages/plugins/openapi" + }, + "homepage": "https://github.com/RhysSullivan/executor/tree/main/packages/plugins/openapi", + "bugs": { + "url": "https://github.com/RhysSullivan/executor/issues" + }, "exports": { ".": "./src/promise.ts", "./core": "./src/sdk/index.ts", diff --git a/packages/published/sdk/package.json b/packages/published/sdk/package.json index 056e3d1bc1..7088861393 100644 --- a/packages/published/sdk/package.json +++ b/packages/published/sdk/package.json @@ -2,6 +2,16 @@ "name": "@executor-js/sdk", "version": "1.4.0", "type": "module", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/RhysSullivan/executor.git", + "directory": "packages/published/sdk" + }, + "homepage": "https://github.com/RhysSullivan/executor/tree/main/packages/published/sdk", + "bugs": { + "url": "https://github.com/RhysSullivan/executor/issues" + }, "exports": { ".": "./src/index.ts" }, diff --git a/scripts/publish-packages.ts b/scripts/publish-packages.ts new file mode 100644 index 0000000000..e6a58487bb --- /dev/null +++ b/scripts/publish-packages.ts @@ -0,0 +1,122 @@ +#!/usr/bin/env bun +/** + * Publishes the public @executor-js/* workspace packages to npm. + * + * Walks a hard-coded list of publishable package directories, determines the + * dist-tag from the version string (anything containing `-` is treated as beta), + * and packs + publishes each package whose current version is not already on npm. + * + * Invoked from `.github/workflows/release.yml` via the `publish:` input on + * changesets/action after the Version Packages PR has been merged, and locally + * via `bun run release:publish:packages` (or `--dry-run`). + */ +import { $ } from "bun"; +import { existsSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +type Channel = "latest" | "beta"; + +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); + +/** + * Workspace-relative paths of the public packages. Kept explicit so a new + * directory under `packages/plugins/` does not accidentally ship to npm. + */ +const PUBLIC_PACKAGE_DIRS = [ + "packages/core/core", + "packages/published/sdk", + "packages/plugins/file-secrets", + "packages/plugins/google-discovery", + "packages/plugins/graphql", + "packages/plugins/keychain", + "packages/plugins/mcp", + "packages/plugins/onepassword", + "packages/plugins/openapi", +] as const; + +const parseArgs = (argv: ReadonlyArray): { dryRun: boolean } => { + let dryRun = false; + for (const arg of argv) { + if (arg === "--dry-run") { + dryRun = true; + continue; + } + throw new Error(`Unknown argument: ${arg}`); + } + return { dryRun }; +}; + +const resolveChannel = (version: string): Channel => + version.includes("-") ? "beta" : "latest"; + +const readPackageMeta = async (pkgDir: string) => { + const pkgJsonPath = join(pkgDir, "package.json"); + const pkg = await Bun.file(pkgJsonPath).json() as { + name?: string; + version?: string; + private?: boolean; + }; + + if (!pkg.name || !pkg.version) { + throw new Error(`Missing name/version in ${pkgJsonPath}`); + } + if (pkg.private === true) { + throw new Error(`${pkg.name} is marked private and cannot be published`); + } + + return { name: pkg.name, version: pkg.version }; +}; + +const packageAlreadyPublished = async (name: string, version: string): Promise => { + const proc = Bun.spawn(["npm", "view", `${name}@${version}`, "version"], { + stdio: ["ignore", "ignore", "ignore"], + }); + return (await proc.exited) === 0; +}; + +const publishPackage = async (pkgDir: string, channel: Channel, dryRun: boolean) => { + const { name, version } = await readPackageMeta(pkgDir); + + if (!existsSync(join(pkgDir, "dist"))) { + throw new Error(`Missing dist/ in ${pkgDir}. Did you run 'bun run build:packages'?`); + } + + if (await packageAlreadyPublished(name, version)) { + console.log(`[skip] ${name}@${version} already on npm`); + return; + } + + console.log(`[publish] ${name}@${version} (${channel})${dryRun ? " [dry-run]" : ""}`); + + await $`bun pm pack`.cwd(pkgDir); + + if (dryRun) { + return; + } + + const args = ["publish", "*.tgz", "--access", "public", "--tag", channel]; + if (process.env.GITHUB_ACTIONS === "true") { + args.push("--provenance"); + } + await $`npm ${args}`.cwd(pkgDir); +}; + +const main = async () => { + const { dryRun } = parseArgs(process.argv.slice(2)); + + // Use the @executor-js/sdk version as the source of truth for the channel. + // All @executor-js packages version together (they're not in the changeset + // ignore list), so they share a release channel. + const sdkMeta = await readPackageMeta(join(repoRoot, "packages/published/sdk")); + const channel = resolveChannel(sdkMeta.version); + console.log(`Publishing @executor-js packages (${channel})${dryRun ? " [dry-run]" : ""}`); + + await $`bun run build:packages`.cwd(repoRoot); + + for (const relDir of PUBLIC_PACKAGE_DIRS) { + await publishPackage(join(repoRoot, relDir), channel, dryRun); + } +}; + +await main();