Skip to content

Commit 005d7e0

Browse files
authored
chore: add pkg.pr.new preview package releases (#3806)
## 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@<sha> ``` 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-<sha>`. 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](stackblitz-labs/pkg.pr.new#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`).
1 parent 139eede commit 005d7e0

4 files changed

Lines changed: 185 additions & 0 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: 📦 Preview packages (pkg.pr.new)
2+
3+
# Publishes installable preview builds of the public @trigger.dev/* packages
4+
# for every push to a branch, via https://pkg.pr.new. These are NOT published
5+
# to npm — pkg.pr.new serves them by commit SHA and drops install instructions
6+
# in a comment on the associated PR, e.g.
7+
# npm i https://pkg.pr.new/@trigger.dev/sdk@<sha>
8+
#
9+
# Prerequisites:
10+
# - The pkg.pr.new GitHub App must be installed on triggerdotdev/trigger.dev
11+
# (https://github.com/apps/pkg-pr-new). Publishing fails until it is.
12+
#
13+
# Fork note: pkg.pr.new authenticates with a GitHub Actions OIDC token, which
14+
# GitHub does not issue to pull_request workflows from forks. This `push`
15+
# trigger therefore covers branches pushed to this repo (the core team), not
16+
# external fork PRs. Adding fork coverage would require a workflow_run two-stage
17+
# setup.
18+
19+
on:
20+
push:
21+
branches-ignore:
22+
- main
23+
- changeset-release/main
24+
paths:
25+
- "package.json"
26+
- "packages/**"
27+
- "pnpm-lock.yaml"
28+
- "pnpm-workspace.yaml"
29+
- "turbo.json"
30+
- ".github/workflows/preview-packages.yml"
31+
- "scripts/stamp-preview-version.mjs"
32+
- "scripts/updateVersion.ts"
33+
34+
concurrency:
35+
group: preview-packages-${{ github.ref }}
36+
cancel-in-progress: true
37+
38+
permissions:
39+
contents: read
40+
id-token: write # OIDC token used by pkg.pr.new to authenticate the publish
41+
42+
jobs:
43+
publish:
44+
name: Build and publish previews
45+
runs-on: ubuntu-latest
46+
if: github.repository == 'triggerdotdev/trigger.dev'
47+
steps:
48+
- name: ⬇️ Checkout repo
49+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
50+
with:
51+
fetch-depth: 0
52+
persist-credentials: false
53+
54+
- name: ⎔ Setup pnpm
55+
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
56+
with:
57+
version: 10.33.2
58+
59+
- name: ⎔ Setup node
60+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
61+
with:
62+
node-version: 20.20.0
63+
cache: "pnpm"
64+
65+
- name: 📥 Install dependencies
66+
run: pnpm install --frozen-lockfile
67+
68+
- name: 📀 Generate Prisma client
69+
run: pnpm run generate
70+
71+
# Stamp a unique 0.0.0-preview-<sha> version before building so it can't
72+
# collide with real npm versions and so updateVersion.ts bakes it into the
73+
# runtime VERSION constant. See scripts/stamp-preview-version.mjs.
74+
- name: 🏷️ Stamp preview version
75+
run: node scripts/stamp-preview-version.mjs
76+
env:
77+
GITHUB_SHA: ${{ github.sha }}
78+
79+
- name: 🔨 Build packages
80+
run: pnpm run build --filter "@trigger.dev/*" --filter "trigger.dev"
81+
82+
- name: 🚀 Publish previews to pkg.pr.new
83+
run: pnpm exec pkg-pr-new publish --pnpm --compact --commentWithSha './packages/*'

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"autoprefixer": "^10.4.12",
6060
"eslint-plugin-turbo": "^2.0.4",
6161
"lefthook": "^1.11.3",
62+
"pkg-pr-new": "0.0.75",
6263
"pkg-types": "1.1.3",
6364
"prettier": "^3.0.0",
6465
"tsx": "^3.7.1",

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/stamp-preview-version.mjs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Rewrites the version of every public packages/* package to a unique,
2+
// preview-only semver (0.0.0-preview-<sha>) BEFORE the build runs.
3+
//
4+
// Why this exists:
5+
// pkg.pr.new serves preview builds by commit SHA but does NOT change the
6+
// package.json "version" field (as of 0.0.75). If a preview is published
7+
// while package.json still says e.g. 4.5.0-rc.4, a consumer who installs the
8+
// preview pins 4.5.0-rc.4 to the pkg.pr.new tarball in their lockfile/cache,
9+
// and a later `npm i @trigger.dev/sdk@4.5.0-rc.4` from npm can resolve to the
10+
// stale preview. See stackblitz-labs/pkg.pr.new#250 and #390.
11+
//
12+
// A 0.0.0- prefix can never satisfy a real semver range, so the collision
13+
// becomes structurally impossible (the same convention React/Next canaries
14+
// use).
15+
//
16+
// Running BEFORE the build also means scripts/updateVersion.ts bakes this
17+
// same preview version into the runtime VERSION constant, so previews are
18+
// self-identifying (trigger --version, the x-trigger-cli-version header, the
19+
// MCP server version, etc.) rather than all reporting the RC version.
20+
//
21+
// Sibling workspace: specifiers are relaxed to workspace:* so `pnpm pack`
22+
// resolves them against the rewritten versions without range-validation
23+
// errors. packages/python pins peerDependencies as workspace:^4.5.0-rc.4,
24+
// which would otherwise be unsatisfiable once the sibling version changes.
25+
//
26+
// Note: pkg.pr.new PR #525 adds a built-in --previewVersion flag. Once that
27+
// ships we could drop the version-rewrite half here, but we still want a
28+
// pre-build stamp so updateVersion.ts picks up the preview version (the flag
29+
// rewrites at pack time, which is too late for the baked VERSION constant).
30+
31+
import { readdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
32+
import { join } from "node:path";
33+
import { execSync } from "node:child_process";
34+
35+
const PACKAGES_DIR = "packages";
36+
const DEP_SECTIONS = [
37+
"dependencies",
38+
"devDependencies",
39+
"peerDependencies",
40+
"optionalDependencies",
41+
];
42+
43+
function resolveSha() {
44+
const sha = process.argv[2] || process.env.GITHUB_SHA;
45+
if (sha) return sha;
46+
try {
47+
return execSync("git rev-parse HEAD", { encoding: "utf8" }).trim();
48+
} catch {
49+
throw new Error(
50+
"Could not determine commit SHA (pass as the first argument or set GITHUB_SHA)"
51+
);
52+
}
53+
}
54+
55+
const sha = resolveSha().slice(0, 7);
56+
const previewVersion = `0.0.0-preview-${sha}`;
57+
58+
const dirs = readdirSync(PACKAGES_DIR, { withFileTypes: true }).filter((e) =>
59+
e.isDirectory()
60+
);
61+
62+
// First pass: collect every public package name so we know which workspace
63+
// specifiers point at a sibling whose version we are about to change.
64+
const publicNames = new Set();
65+
const manifests = [];
66+
for (const dir of dirs) {
67+
const pkgPath = join(PACKAGES_DIR, dir.name, "package.json");
68+
if (!existsSync(pkgPath)) continue;
69+
const json = JSON.parse(readFileSync(pkgPath, "utf8"));
70+
manifests.push({ pkgPath, json });
71+
if (!json.private && json.name) publicNames.add(json.name);
72+
}
73+
74+
// Second pass: stamp the version and relax sibling workspace specifiers.
75+
let stamped = 0;
76+
for (const { pkgPath, json } of manifests) {
77+
if (json.private) continue;
78+
json.version = previewVersion;
79+
for (const section of DEP_SECTIONS) {
80+
const deps = json[section];
81+
if (!deps) continue;
82+
for (const [name, spec] of Object.entries(deps)) {
83+
if (publicNames.has(name) && String(spec).startsWith("workspace:")) {
84+
deps[name] = "workspace:*";
85+
}
86+
}
87+
}
88+
writeFileSync(pkgPath, `${JSON.stringify(json, null, 2)}\n`);
89+
stamped++;
90+
}
91+
92+
console.log(`Stamped ${stamped} public package(s) to ${previewVersion}`);

0 commit comments

Comments
 (0)