From 005d7e0a3f4547aabc43aded2acbf27c2ca16d5a Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Tue, 2 Jun 2026 15:20:32 +0100 Subject: [PATCH] chore: add pkg.pr.new preview package releases (#3806) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What Adds [pkg.pr.new](https://pkg.pr.new) continuous preview releases. Every push to a branch builds the public `@trigger.dev/*` packages and publishes installable preview builds keyed by commit SHA — **without touching the npm registry**. pkg.pr.new drops install instructions on the associated PR: ``` npm i https://pkg.pr.new/@trigger.dev/sdk@ ``` This lets reviewers and users try a branch (SDK, CLI, core, etc.) before anything is released, separate from the changesets release, the manual `--snapshot` prerelease, and the chat-prerelease flow. ## How `.github/workflows/preview-packages.yml` (push trigger) → install → generate Prisma → **stamp preview version** → build → `pkg-pr-new publish`. ### The version stamp (the important part) pkg.pr.new serves previews by SHA but does **not** rewrite the package.json `version` field. If a preview shipped as `4.5.0-rc.4`, a consumer who installed it would pin `4.5.0-rc.4` to the preview tarball in their lockfile/cache — and a later `npm i @trigger.dev/sdk@4.5.0-rc.4` from npm could resolve to the stale preview. This is a known, by-design gap in the tool (stackblitz-labs/pkg.pr.new#250, #390). `scripts/stamp-preview-version.mjs` runs **before the build** and rewrites every public package to a unique `0.0.0-preview-`. The `0.0.0-` prefix can never satisfy a real semver range, so the collision is structurally impossible (same convention React/Next canaries use). Running before the build also means `scripts/updateVersion.ts` bakes the preview version into the runtime `VERSION` constant, so previews are self-identifying (`trigger --version`, the `x-trigger-cli-version` header, the MCP server version) instead of all reporting the RC version. Sibling `workspace:` specifiers are relaxed to `workspace:*` so `pnpm pack` resolves them against the rewritten versions — `packages/python` pins peerDependencies as `workspace:^4.5.0-rc.4`, which would otherwise be unsatisfiable once the version changes. Non-public deps (`@trigger.dev/database`, `@internal/*`) are left untouched. All mutations happen on the ephemeral CI checkout; nothing is committed. ## GitHub App The pkg.pr.new GitHub App is **already installed** on `triggerdotdev/trigger.dev` (has been for a while), so no setup is needed. Confirmed live — this branch's pushes published all 10 public packages, e.g. ``` pnpm add https://pkg.pr.new/@trigger.dev/sdk@e4dfc59 ``` ## Fork limitation pkg.pr.new authenticates with a GitHub Actions OIDC token, which GitHub does not issue to `pull_request` workflows from forks. The `push` trigger therefore covers branches pushed to this repo (core team), not external fork PRs. Fork coverage would need a `workflow_run` two-stage setup; left out for now. ## Notes - Pinned `pkg-pr-new@0.0.75` (no Node engine constraint; Node 20 CI is fine). - pkg.pr.new [#525](https://github.com/stackblitz-labs/pkg.pr.new/pull/525) adds a built-in `--previewVersion` flag (still open). If it lands we can drop the version-rewrite half of the script, but we'd keep a pre-build stamp anyway so `updateVersion.ts` picks up the preview version (the flag rewrites at pack time, too late for the baked `VERSION`). --- .github/workflows/preview-packages.yml | 83 +++++++++++++++++++++++ package.json | 1 + pnpm-lock.yaml | 9 +++ scripts/stamp-preview-version.mjs | 92 ++++++++++++++++++++++++++ 4 files changed, 185 insertions(+) create mode 100644 .github/workflows/preview-packages.yml create mode 100644 scripts/stamp-preview-version.mjs diff --git a/.github/workflows/preview-packages.yml b/.github/workflows/preview-packages.yml new file mode 100644 index 000000000..f4dd5b399 --- /dev/null +++ b/.github/workflows/preview-packages.yml @@ -0,0 +1,83 @@ +name: 📦 Preview packages (pkg.pr.new) + +# Publishes installable preview builds of the public @trigger.dev/* packages +# for every push to a branch, via https://pkg.pr.new. These are NOT published +# to npm — pkg.pr.new serves them by commit SHA and drops install instructions +# in a comment on the associated PR, e.g. +# npm i https://pkg.pr.new/@trigger.dev/sdk@ +# +# Prerequisites: +# - The pkg.pr.new GitHub App must be installed on triggerdotdev/trigger.dev +# (https://github.com/apps/pkg-pr-new). Publishing fails until it is. +# +# Fork note: pkg.pr.new authenticates with a GitHub Actions OIDC token, which +# GitHub does not issue to pull_request workflows from forks. This `push` +# trigger therefore covers branches pushed to this repo (the core team), not +# external fork PRs. Adding fork coverage would require a workflow_run two-stage +# setup. + +on: + push: + branches-ignore: + - main + - changeset-release/main + paths: + - "package.json" + - "packages/**" + - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" + - "turbo.json" + - ".github/workflows/preview-packages.yml" + - "scripts/stamp-preview-version.mjs" + - "scripts/updateVersion.ts" + +concurrency: + group: preview-packages-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + id-token: write # OIDC token used by pkg.pr.new to authenticate the publish + +jobs: + publish: + name: Build and publish previews + runs-on: ubuntu-latest + if: github.repository == 'triggerdotdev/trigger.dev' + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + persist-credentials: false + + - name: ⎔ Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + with: + version: 10.33.2 + + - name: ⎔ Setup node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 20.20.0 + cache: "pnpm" + + - name: 📥 Install dependencies + run: pnpm install --frozen-lockfile + + - name: 📀 Generate Prisma client + run: pnpm run generate + + # Stamp a unique 0.0.0-preview- version before building so it can't + # collide with real npm versions and so updateVersion.ts bakes it into the + # runtime VERSION constant. See scripts/stamp-preview-version.mjs. + - name: 🏷️ Stamp preview version + run: node scripts/stamp-preview-version.mjs + env: + GITHUB_SHA: ${{ github.sha }} + + - name: 🔨 Build packages + run: pnpm run build --filter "@trigger.dev/*" --filter "trigger.dev" + + - name: 🚀 Publish previews to pkg.pr.new + run: pnpm exec pkg-pr-new publish --pnpm --compact --commentWithSha './packages/*' diff --git a/package.json b/package.json index cea11cf52..b32e15708 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "autoprefixer": "^10.4.12", "eslint-plugin-turbo": "^2.0.4", "lefthook": "^1.11.3", + "pkg-pr-new": "0.0.75", "pkg-types": "1.1.3", "prettier": "^3.0.0", "tsx": "^3.7.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e2f19b746..15229853d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -111,6 +111,9 @@ importers: lefthook: specifier: ^1.11.3 version: 1.11.3 + pkg-pr-new: + specifier: 0.0.75 + version: 0.0.75 pkg-types: specifier: 1.1.3 version: 1.1.3 @@ -17100,6 +17103,10 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} + pkg-pr-new@0.0.75: + resolution: {integrity: sha512-u9mdErTewKSMsr+ceCt8VcNuNP0ro5AXiPXhUVApuEyqr2Zlvt+DdCFBcm+yGWN8mhOdZJ27meIDbnoZgfzpOw==} + hasBin: true + pkg-types@1.1.3: resolution: {integrity: sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==} @@ -38314,6 +38321,8 @@ snapshots: dependencies: find-up: 4.1.0 + pkg-pr-new@0.0.75: {} + pkg-types@1.1.3: dependencies: confbox: 0.1.8 diff --git a/scripts/stamp-preview-version.mjs b/scripts/stamp-preview-version.mjs new file mode 100644 index 000000000..a6b21d107 --- /dev/null +++ b/scripts/stamp-preview-version.mjs @@ -0,0 +1,92 @@ +// Rewrites the version of every public packages/* package to a unique, +// preview-only semver (0.0.0-preview-) BEFORE the build runs. +// +// Why this exists: +// pkg.pr.new serves preview builds by commit SHA but does NOT change the +// package.json "version" field (as of 0.0.75). If a preview is published +// while package.json still says e.g. 4.5.0-rc.4, a consumer who installs the +// preview pins 4.5.0-rc.4 to the pkg.pr.new tarball in their lockfile/cache, +// and a later `npm i @trigger.dev/sdk@4.5.0-rc.4` from npm can resolve to the +// stale preview. See stackblitz-labs/pkg.pr.new#250 and #390. +// +// A 0.0.0- prefix can never satisfy a real semver range, so the collision +// becomes structurally impossible (the same convention React/Next canaries +// use). +// +// Running BEFORE the build also means scripts/updateVersion.ts bakes this +// same preview version into the runtime VERSION constant, so previews are +// self-identifying (trigger --version, the x-trigger-cli-version header, the +// MCP server version, etc.) rather than all reporting the RC version. +// +// Sibling workspace: specifiers are relaxed to workspace:* so `pnpm pack` +// resolves them against the rewritten versions without range-validation +// errors. packages/python pins peerDependencies as workspace:^4.5.0-rc.4, +// which would otherwise be unsatisfiable once the sibling version changes. +// +// Note: pkg.pr.new PR #525 adds a built-in --previewVersion flag. Once that +// ships we could drop the version-rewrite half here, but we still want a +// pre-build stamp so updateVersion.ts picks up the preview version (the flag +// rewrites at pack time, which is too late for the baked VERSION constant). + +import { readdirSync, readFileSync, writeFileSync, existsSync } from "node:fs"; +import { join } from "node:path"; +import { execSync } from "node:child_process"; + +const PACKAGES_DIR = "packages"; +const DEP_SECTIONS = [ + "dependencies", + "devDependencies", + "peerDependencies", + "optionalDependencies", +]; + +function resolveSha() { + const sha = process.argv[2] || process.env.GITHUB_SHA; + if (sha) return sha; + try { + return execSync("git rev-parse HEAD", { encoding: "utf8" }).trim(); + } catch { + throw new Error( + "Could not determine commit SHA (pass as the first argument or set GITHUB_SHA)" + ); + } +} + +const sha = resolveSha().slice(0, 7); +const previewVersion = `0.0.0-preview-${sha}`; + +const dirs = readdirSync(PACKAGES_DIR, { withFileTypes: true }).filter((e) => + e.isDirectory() +); + +// First pass: collect every public package name so we know which workspace +// specifiers point at a sibling whose version we are about to change. +const publicNames = new Set(); +const manifests = []; +for (const dir of dirs) { + const pkgPath = join(PACKAGES_DIR, dir.name, "package.json"); + if (!existsSync(pkgPath)) continue; + const json = JSON.parse(readFileSync(pkgPath, "utf8")); + manifests.push({ pkgPath, json }); + if (!json.private && json.name) publicNames.add(json.name); +} + +// Second pass: stamp the version and relax sibling workspace specifiers. +let stamped = 0; +for (const { pkgPath, json } of manifests) { + if (json.private) continue; + json.version = previewVersion; + for (const section of DEP_SECTIONS) { + const deps = json[section]; + if (!deps) continue; + for (const [name, spec] of Object.entries(deps)) { + if (publicNames.has(name) && String(spec).startsWith("workspace:")) { + deps[name] = "workspace:*"; + } + } + } + writeFileSync(pkgPath, `${JSON.stringify(json, null, 2)}\n`); + stamped++; +} + +console.log(`Stamped ${stamped} public package(s) to ${previewVersion}`);