@dbx-tools/projen 0.6.40 → 0.6.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/release.ts CHANGED
@@ -11,7 +11,44 @@ import { applyTasks, taskScript, type DBXToolsNodeProject } from "./project.ts";
11
11
 
12
12
  const NODE_VERSION = "lts/*";
13
13
  const NPM_REGISTRY_URL = "https://registry.npmjs.org";
14
- const PNPM_VERSION = "10.33.0";
14
+ const BUN_VERSION = "1.3.14";
15
+
16
+ /**
17
+ * The `release` workflow's version-stamp + publish step, as a shell script.
18
+ *
19
+ * Bun has no `pnpm -r publish` equivalent, so this drives the engine's
20
+ * `tasks/publish.ts` (shipped in the engine tarball, run via bun): it reads the
21
+ * workspace members from the root `package.json`, stamps the tag version onto
22
+ * every manifest (rewriting `@dbx-tools/*` `workspace:*` sibling deps to
23
+ * `^<version>` so the tarballs resolve each other), then `bun publish`es each
24
+ * non-`private` package. `bun publish` substitutes `publishConfig` (the compiled
25
+ * `lib/` entry points) at pack time, runs `prepack` (compile) so `lib/` exists,
26
+ * and honors `NPM_CONFIG_PROVENANCE`.
27
+ *
28
+ * Two ways in: a pushed `<prefix>*` tag (the real release - `GITHUB_REF_NAME` is
29
+ * the version) and a manual `workflow_dispatch` (no tag, so a throwaway
30
+ * `0.0.0-dry.<run>` version is used and `--dry-run` is FORCED regardless of the
31
+ * input, since a dispatch never has a tag to publish as). The `dry_run` input
32
+ * (default true) is what lets a maintainer exercise the whole workflow - setup,
33
+ * install, stamp, compile, pack, validate - with nothing reaching npm.
34
+ */
35
+ function BUN_PUBLISH_SCRIPT(tagPrefix: string, excludeDirs: readonly string[]): string {
36
+ const script = "node_modules/@dbx-tools/projen/tasks/publish.ts";
37
+ const excludes = excludeDirs.map((dir) => ` --exclude ${dir}`).join("");
38
+ return [
39
+ 'if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then',
40
+ // A manual run has no tag: use a throwaway version and never really publish.
41
+ ' VERSION="0.0.0-dry.${GITHUB_RUN_NUMBER}"',
42
+ " DRY_RUN=--dry-run",
43
+ "else",
44
+ ` VERSION="\${GITHUB_REF_NAME#${tagPrefix}}"`,
45
+ // A tag push honors the input too, so a dry-run tag can be tested if wanted.
46
+ ' DRY_RUN="${DRY_RUN_INPUT}"',
47
+ "fi",
48
+ "chmod -R u+w . || true",
49
+ `bun ${script} "$VERSION"${excludes} $DRY_RUN`,
50
+ ].join("\n");
51
+ }
15
52
 
16
53
  interface PublishWorkflow {
17
54
  readonly name: string;
@@ -24,17 +61,18 @@ interface PublishWorkflow {
24
61
  function publishSetupSteps(): JobStep[] {
25
62
  return [
26
63
  { name: "Checkout", uses: "actions/checkout@v6", with: { "fetch-depth": 0 } },
27
- { name: "Setup pnpm", uses: "pnpm/action-setup@v5", with: { version: PNPM_VERSION } },
64
+ { name: "Setup Bun", uses: "oven-sh/setup-bun@v2", with: { "bun-version": BUN_VERSION } },
28
65
  {
29
66
  name: "Setup Node.js",
30
67
  uses: "actions/setup-node@v6",
31
68
  // setup-node writes the temporary npmrc that maps NODE_AUTH_TOKEN onto
32
69
  // npmjs. Omitting this leaves the secret in the environment but gives npm
33
70
  // no registry-scoped auth entry, and every publish fails with ENEEDAUTH.
71
+ // (Bun installs deps; publishing still goes through `npm publish`.)
34
72
  with: { "node-version": NODE_VERSION, "registry-url": NPM_REGISTRY_URL },
35
73
  },
36
- // The lockfile is intentionally untracked and may be absent or stale in CI.
37
- { name: "Install", run: "pnpm install --no-frozen-lockfile" },
74
+ // Bun's install; the lockfile may be absent or stale in CI so it is not frozen.
75
+ { name: "Install", run: "bun install" },
38
76
  ];
39
77
  }
40
78
 
@@ -160,12 +198,30 @@ export class DBXToolsRelease extends Component {
160
198
  // the publish job below overrides it with the `id-token` it needs.
161
199
  workflow.file?.addOverride("permissions", { contents: "read" });
162
200
  workflow.on({ push: { tags: [`${tagPrefix}*`] } });
201
+ // Manual trigger for testing the workflow WITHOUT reaching npm: a
202
+ // `workflow_dispatch` run has no tag, so the publish script forces
203
+ // `--dry-run` (pack + validate only). The `dry_run` input (default true)
204
+ // additionally lets a tag push be dry-run on demand.
205
+ workflow.file?.addOverride("on.workflow_dispatch", {
206
+ inputs: {
207
+ dry_run: {
208
+ description: "Pack and validate but do not upload to npm",
209
+ type: "boolean",
210
+ default: true,
211
+ },
212
+ },
213
+ });
163
214
  workflow.addJob("publish", {
164
215
  runsOn: ["ubuntu-latest"],
165
216
  // `id-token: write` lets npm mint the OIDC token for provenance attestation.
166
217
  permissions: { contents: JobPermission.READ, idToken: JobPermission.WRITE },
167
218
  timeoutMinutes: 30,
168
- env: { CI: "true" },
219
+ // `DRY_RUN_INPUT` is `--dry-run` when the dispatch input is true, else empty;
220
+ // the publish script also FORCES it on any `workflow_dispatch` run.
221
+ env: {
222
+ CI: "true",
223
+ DRY_RUN_INPUT: "${{ github.event.inputs.dry_run == 'true' && '--dry-run' || '' }}",
224
+ },
169
225
  steps: [...publishSetupSteps(), ...steps],
170
226
  ...(workingDirectory ? { defaults: { run: { workingDirectory } } } : {}),
171
227
  });
@@ -182,26 +238,25 @@ export class DBXToolsRelease extends Component {
182
238
  name: "release",
183
239
  tagPrefix: this.tagPrefix,
184
240
  steps: [
185
- // The pushed tag is the version: `<prefix>1.2.3` -> `1.2.3`. Set it on
186
- // every package (manifests are projen-readonly, so unlock them first).
241
+ // The pushed tag is the version: `<prefix>1.2.3` -> `1.2.3`. Stamp it on
242
+ // every workspace package (manifests are projen-readonly, so unlock
243
+ // first), rewriting `workspace:*` sibling deps to `^<version>` so the
244
+ // published tarballs resolve each other. `bun publish` honors
245
+ // `publishConfig` (compiled `lib/` entry points) and provenance.
187
246
  {
188
- name: "Set version from tag",
189
- run: [
190
- `VERSION="\${GITHUB_REF_NAME#${this.tagPrefix}}"`,
191
- "chmod -R u+w . || true",
192
- 'pnpm -r exec npm version "$VERSION" --no-git-tag-version --allow-same-version',
193
- ].join("\n"),
194
- },
195
- {
196
- name: "Publish to npm",
197
- // `pnpm -r publish` publishes every non-private package,
198
- // rewriting `workspace:*` deps to the published version. Provenance is
199
- // opt-in (omitted from each package's `publishConfig` so local
200
- // publishes work); CI turns it on here via `npm_config_provenance`.
201
- run: "pnpm -r publish --no-git-checks --access public",
247
+ name: "Set version from tag and publish",
248
+ // Exclude every standalone-release dir (e.g. `projen`): it publishes on
249
+ // its own `<prefix>-v*` tag via its own workflow, not the main `v*` one.
250
+ run: BUN_PUBLISH_SCRIPT(
251
+ this.tagPrefix,
252
+ this.standaloneReleases.map((s) => s.directory),
253
+ ),
202
254
  env: {
255
+ // `bun publish` authenticates via NPM_CONFIG_TOKEN (not NODE_AUTH_TOKEN,
256
+ // which is the `npm publish` convention). Set both so either tool works.
257
+ NPM_CONFIG_TOKEN: "${{ secrets.NPM_TOKEN }}",
203
258
  NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}",
204
- npm_config_provenance: "true",
259
+ NPM_CONFIG_PROVENANCE: "true",
205
260
  },
206
261
  },
207
262
  ],
@@ -210,34 +265,55 @@ export class DBXToolsRelease extends Component {
210
265
 
211
266
  /**
212
267
  * Emit a {@link StandaloneRelease}'s workflow: push `<prefix>1.2.3` and the
213
- * single package in `directory` (a non-workspace-member project) is published
214
- * at 1.2.3 via `npm pack` + `npm publish`. Its `package.json` is
215
- * projen-generated read-only, so it is unlocked before `npm version` rewrites
216
- * it. No bump math - the pushed tag IS the published version.
268
+ * single package in `directory` is published at 1.2.3 via `bun publish`.
269
+ *
270
+ * `directory` (e.g. `projen/`) is a WORKSPACE MEMBER whose `@dbx-tools/*` deps
271
+ * are `workspace:*`. `bun publish` resolves those to whatever version its
272
+ * SIBLINGS carry (via the lockfile), so before publishing we set the version on
273
+ * the package AND its in-scope siblings, then refresh the lockfile - otherwise
274
+ * the published engine would depend on the siblings' on-disk `0.0.0`. The
275
+ * `Install` step already ran `bun install` from the repo root (the member
276
+ * subdir walks up to it), so the workspace is linked. The manifests are
277
+ * projen-readonly, hence the `chmod`. A manual `workflow_dispatch` run has no
278
+ * tag, so it uses a throwaway version and forces `--dry-run` (nothing to npm).
217
279
  */
218
280
  private authorStandaloneReleaseWorkflow(
219
281
  project: DBXToolsNodeProject,
220
282
  { name, directory, tagPrefix }: StandaloneRelease,
221
283
  ): void {
284
+ // The engine's release also stamps its in-scope siblings so `bun publish`
285
+ // resolves their `workspace:*` to the release version. Stamping the WHOLE
286
+ // workspace is simplest and harmless (only `directory` is published here).
287
+ const stampScript = "node_modules/@dbx-tools/projen/tasks/publish.ts";
222
288
  this.authorPublishWorkflow(project, {
223
289
  name,
224
290
  tagPrefix,
225
- workingDirectory: directory,
226
291
  steps: [
227
- // The pushed tag is the version: `<prefix>1.2.3` -> `1.2.3`. package.json
228
- // is projen-generated read-only, so unlock it before `npm version` writes.
229
292
  {
230
- name: "Set version from tag",
293
+ name: "Set version from tag and publish",
231
294
  run: [
232
- "chmod u+w package.json",
233
- `npm version "\${GITHUB_REF_NAME#${tagPrefix}}" --no-git-tag-version --allow-same-version`,
295
+ 'if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then',
296
+ ' VERSION="0.0.0-dry.${GITHUB_RUN_NUMBER}"',
297
+ " DRY_RUN=--dry-run",
298
+ "else",
299
+ ` VERSION="\${GITHUB_REF_NAME#${tagPrefix}}"`,
300
+ ' DRY_RUN="${DRY_RUN_INPUT}"',
301
+ "fi",
302
+ "chmod -R u+w . || true",
303
+ // Set the version across every member + refresh the lockfile (the
304
+ // publish task's stamp phase), so bun resolves the engine's
305
+ // `workspace:*` sibling deps to the release version at pack time.
306
+ `bun ${stampScript} "$VERSION" --stamp-only`,
307
+ // Then publish ONLY the standalone directory.
308
+ `cd ${directory} && bun publish --access public $DRY_RUN`,
234
309
  ].join("\n"),
235
- },
236
- { name: "Pack", run: "pnpm pack --pack-destination dist/js" },
237
- {
238
- name: "Publish to npm",
239
- run: "npm publish dist/js/*.tgz --access public",
240
- env: { NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}" },
310
+ env: {
311
+ // `bun publish` authenticates via NPM_CONFIG_TOKEN (not NODE_AUTH_TOKEN,
312
+ // which is the `npm publish` convention). Set both so either tool works.
313
+ NPM_CONFIG_TOKEN: "${{ secrets.NPM_TOKEN }}",
314
+ NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}",
315
+ NPM_CONFIG_PROVENANCE: "true",
316
+ },
241
317
  },
242
318
  ],
243
319
  });
package/src/tags.ts CHANGED
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import type { IMixin as ConstructsMixin } from "constructs";
17
17
  import { javascript } from "projen";
18
+ import { BunBuildFile, BunDevServerFile, BunfigFile } from "./bun-app.ts";
18
19
  import { create } from "./mixin.ts";
19
20
  import {
20
21
  addPackageFiles,
@@ -25,7 +26,6 @@ import {
25
26
  srcModuleExports,
26
27
  } from "./project.ts";
27
28
  import * as projectPredicate from "./project-predicate.ts";
28
- import { ViteConfigFile } from "./vite.ts";
29
29
 
30
30
  /** Node compiler options: ES2022 lib + node types, deliberately no DOM. */
31
31
  const NODE_COMPILER_OPTIONS: javascript.TypeScriptCompilerOptions = {
@@ -81,30 +81,42 @@ export const PACKAGE_TAG_MIXINS = {
81
81
  "./package.json": "./package.json",
82
82
  });
83
83
  }),
84
- // `app`: a full browser app built + served by Vite (needs an `index.html`
85
- // entry). Self-contained React app: React + DOM lib + JSX + the vite toolchain
86
- // and app tasks (`dev`/`build`/`preview`). `build` resets the compile task, so
87
- // `compile` bundles with vite rather than `tsc`.
84
+ // `app`: a full browser app built + served by BUN (needs an `index.html`
85
+ // entry). Self-contained React app: React + DOM lib + JSX + bun's fullstack
86
+ // dev server (`dev.ts`, `Bun.serve` + HMR) and production bundle (`build.ts`,
87
+ // `Bun.build`). Tailwind v4 is compiled by `bun-plugin-tailwind` (wired in the
88
+ // generated `bunfig.toml`). No Vite.
88
89
  app: create(projectPredicate.hasTag("app"), (p) => {
89
90
  p.addDeps("react@catalog:", "react-dom@catalog:");
90
91
  p.addDevDeps(
91
- "vite@catalog:",
92
- "@vitejs/plugin-react@catalog:",
93
92
  "@types/react@catalog:",
94
93
  "@types/react-dom@catalog:",
94
+ // The Tailwind plugin the dev server + build load. Tailwind itself is a
95
+ // catalog dep the app declares (it also owns the Tailwind entry CSS).
96
+ "bun-plugin-tailwind@catalog:",
95
97
  );
96
98
  applyCompilerOptions(p, {
97
99
  target: "ES2022",
98
100
  lib: [...DOM_LIB],
99
101
  jsx: javascript.TypeScriptJsxMode.REACT_JSX,
100
- types: ["vite/client"],
102
+ // `@types/bun` (a root/subproject devDep) supplies the `Bun.*` globals the
103
+ // generated `dev.ts`/`build.ts` use; no `vite/client`.
104
+ types: ["bun"],
105
+ // `@/` -> `src/` alias, resolved by both tsc and bun's bundler (bun reads
106
+ // tsconfig `paths`). Replaces the old Vite `resolve.alias` for `@`.
107
+ baseUrl: ".",
108
+ paths: { "@/*": ["./src/*"] },
101
109
  });
110
+ // bun runs the generated scripts directly. `build` resets the compile task so
111
+ // `compile` bundles with `Bun.build` rather than `tsc`.
102
112
  applyTasks(p, {
103
- dev: { exec: "vite" },
104
- build: { exec: "vite build" },
105
- preview: { exec: "vite preview" },
113
+ dev: { exec: "bun dev.ts" },
114
+ build: { exec: "bun build.ts" },
115
+ preview: { exec: "bun dev.ts" },
106
116
  });
107
- new ViteConfigFile(p);
117
+ new BunfigFile(p);
118
+ new BunDevServerFile(p);
119
+ new BunBuildFile(p);
108
120
  // An app has a single root entry, not a component library's subpaths - so it
109
121
  // replaces the `ui` tag's `./react`/`./styles.css` surface with a `.` root.
110
122
  applyExports(p, {
@@ -147,9 +159,11 @@ export const PACKAGE_TAG_MIXINS = {
147
159
  ...NODE_COMPILER_OPTIONS,
148
160
  experimentalDecorators: true,
149
161
  });
162
+ // bun runs the server `.ts` directly (native TS, no tsx). `--watch` restarts
163
+ // on change - the tsx-watch replacement.
150
164
  applyTasks(p, {
151
- dev: { exec: "tsx watch src/server.ts" },
152
- start: { exec: "tsx src/server.ts" },
165
+ dev: { exec: "bun --watch src/server.ts" },
166
+ start: { exec: "bun src/server.ts" },
153
167
  });
154
168
  }),
155
169
  node: create(projectPredicate.hasTag("node"), (p) => {
package/tasks/bump.ts CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env -S npx tsx
1
+ #!/usr/bin/env -S bun
2
2
  /**
3
3
  * `projen bump` - synth, compute the next release version, then (by default)
4
4
  * commit, tag, and push it. Pushing the tag is what triggers the release
@@ -11,9 +11,9 @@
11
11
  * - the local `package.json` version,
12
12
  * then incremented by `--level` (patch | minor | major; default patch).
13
13
  *
14
- * `--sibling <dir>:<tagPrefix>` (repeatable) releases a standalone in-repo
15
- * project - one that is NOT a pnpm workspace member, so `pnpm -r` cannot see it -
16
- * at the SAME version as the root, in the same run: its manifest is stamped, its
14
+ * `--sibling <dir>:<tagPrefix>` (repeatable) releases an in-repo project that
15
+ * publishes on its OWN tag namespace (e.g. `projen/`, tagged `projen-v*`) at the
16
+ * SAME version as the root, in the same run: its manifest version is stamped, its
17
17
  * `<tagPrefix><version>` tag is cut and pushed (triggering its own workflow), and
18
18
  * it is included in the local-registry publish. Taking the base version from
19
19
  * every prefix at once is what keeps the two in lockstep: the engine sat at
@@ -32,7 +32,7 @@
32
32
  *
33
33
  * `--local-registry <value>` publishes the just-tagged version to a LOCAL
34
34
  * registry (e.g. a verdaccio) right after the git tag is pushed - so a local
35
- * `pnpm run bump` both fires the GitHub release (public npm) and populates your
35
+ * `bun run bump` both fires the GitHub release (public npm) and populates your
36
36
  * local registry. Values:
37
37
  * - `auto` (default): publish only when `npm config get registry` is a
38
38
  * loopback host (`localhost` / `127.0.0.0/8` / `::1`); otherwise skip.
@@ -44,15 +44,10 @@ import { log, net } from "@dbx-tools/shared-core";
44
44
  import { Command, Option } from "commander";
45
45
  import { chmodSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
46
46
  import { resolve } from "node:path";
47
+ import { fileURLToPath } from "node:url";
47
48
 
48
49
  const logger = log.logger("projen:bump");
49
50
  const LEVELS = ["patch", "minor", "major"] as const;
50
- const DEPENDENCY_FIELDS = [
51
- "dependencies",
52
- "devDependencies",
53
- "optionalDependencies",
54
- "peerDependencies",
55
- ] as const;
56
51
  type Level = (typeof LEVELS)[number];
57
52
 
58
53
  /** A standalone in-repo project released alongside the root, on its own tag prefix. */
@@ -126,39 +121,30 @@ function readPackageVersion(pkgPath: string): [number, number, number] {
126
121
  }
127
122
 
128
123
  /**
129
- * Write `version` into a manifest projen owns. Those are emitted read-only, so
130
- * the write is bracketed by a chmod; the mode is restored afterwards to leave the
131
- * tree exactly as synth left it.
124
+ * Write ONLY the `version` field into a manifest projen owns (read-only, so
125
+ * bracketed by a chmod that restores the mode). Used for the ROOT and the
126
+ * standalone `projen/` sibling so the committed release marks the version.
127
+ *
128
+ * It deliberately does NOT rewrite `@scope/*` dep ranges. `projen/` is a workspace
129
+ * member with `workspace:*` sibling deps; baking a `^version` for the just-bumped
130
+ * (not-yet-published) version into the COMMITTED manifest breaks the release
131
+ * workflow's initial `bun install`, which checks out that commit before anything
132
+ * is published. Sibling versions are resolved transiently at publish time instead:
133
+ * `projen-release` runs `tasks/publish.ts --stamp-only` (set versions + refresh
134
+ * lockfile), then `bun publish` in `projen/` strips `workspace:*` to those versions.
132
135
  */
133
- function writeManifestVersion(pkgPath: string, version: string, dependencyScope?: string): void {
136
+ function writeManifestVersion(pkgPath: string, version: string): void {
134
137
  const { mode } = statSync(pkgPath);
135
138
  chmodSync(pkgPath, mode | 0o200);
136
139
  try {
137
140
  const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as Record<string, unknown>;
138
141
  pkg.version = version;
139
- if (dependencyScope) {
140
- for (const field of DEPENDENCY_FIELDS) {
141
- const dependencies = pkg[field];
142
- if (!dependencies || typeof dependencies !== "object") continue;
143
- for (const name of Object.keys(dependencies)) {
144
- if (name.startsWith(dependencyScope)) {
145
- (dependencies as Record<string, string>)[name] = `^${version}`;
146
- }
147
- }
148
- }
149
- }
150
142
  writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
151
143
  } finally {
152
144
  chmodSync(pkgPath, mode);
153
145
  }
154
146
  }
155
147
 
156
- /** Scoped package prefix (`@scope/`) shared by the root's release packages. */
157
- function releaseDependencyScope(pkgPath: string): string | undefined {
158
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as { name?: string };
159
- return /^@[^/]+\//.exec(pkg.name ?? "")?.[0];
160
- }
161
-
162
148
  /**
163
149
  * Resolve the `--local-registry` value to a registry URL to publish to, or
164
150
  * `undefined` to skip. `false` skips; a URL is used as-is; `auto` uses the
@@ -217,7 +203,6 @@ program
217
203
  }) => {
218
204
  const pkgPath = resolve(process.cwd(), "package.json");
219
205
  if (!existsSync(pkgPath)) throw new Error(`no package.json in ${process.cwd()}`);
220
- const dependencyScope = releaseDependencyScope(pkgPath);
221
206
 
222
207
  const siblings = opts.sibling.map((s) => ({ ...s, pkgPath: resolve(s.dir, "package.json") }));
223
208
  for (const s of siblings) {
@@ -228,7 +213,7 @@ program
228
213
  // manifests, workspace file, tasks, ...) rather than a stale one.
229
214
  if (opts.synth) {
230
215
  logger.info("synthesizing (projen)");
231
- exec.spawnSync("pnpm", ["exec", "projen"], {
216
+ exec.spawnSync("bun", [".projenrc.ts"], {
232
217
  cwd: process.cwd(),
233
218
  stdout: "inherit",
234
219
  stderr: "inherit",
@@ -265,7 +250,7 @@ program
265
250
 
266
251
  if (opts.version) {
267
252
  writeManifestVersion(pkgPath, version);
268
- for (const s of siblings) writeManifestVersion(s.pkgPath, version, dependencyScope);
253
+ for (const s of siblings) writeManifestVersion(s.pkgPath, version);
269
254
  const also = siblings.length ? ` (and ${siblings.map((s) => s.dir).join(", ")})` : "";
270
255
  logger.info(`wrote version ${version} to package.json${also}`);
271
256
  }
@@ -304,57 +289,26 @@ program
304
289
  }
305
290
  if (publishToLocalRegistry) {
306
291
  logger.info(`publishing ${version} to local registry ${localRegistry}`);
307
- const runIn = (cwd: string, command: string, args: string[]) =>
308
- exec.spawnSync(command, args, {
309
- cwd,
310
- stdout: "inherit",
311
- stderr: "inherit",
312
- stdin: "ignore",
313
- check: true,
314
- });
315
- const runInRepo = (command: string, args: string[]) => runIn(process.cwd(), command, args);
316
- // Each package keeps `version: 0.0.0` on disk (projen owns the
317
- // manifest); the root bump above only touched the root. Mirror the CI
318
- // `release` workflow: stamp the release version on EVERY package (they're
319
- // projen-readonly, so unlock first) so `pnpm -r publish` publishes them
320
- // as `version` (and rewrites `workspace:*` sibling pins to it) instead of
321
- // `0.0.0`. No restore needed - the next `projen` synth rewrites these
322
- // manifests back to `0.0.0`; the release version lives in the git tag.
323
- runInRepo("chmod", ["-R", "u+w", "."]);
324
- runInRepo("pnpm", [
325
- "-r",
326
- "exec",
327
- "npm",
328
- "version",
329
- version,
330
- "--no-git-tag-version",
331
- "--allow-same-version",
332
- ]);
333
- // Provenance is opt-in (see `.projenrc.ts`): the generated
334
- // `publishConfig` omits it, so local (verdaccio) publishes never try to
335
- // attest. CI turns it on with `npm_config_provenance=true`.
336
- runInRepo("pnpm", [
337
- "-r",
338
- "publish",
339
- "--registry",
340
- localRegistry,
341
- "--no-git-checks",
342
- "--access",
343
- "public",
344
- ]);
345
- // `pnpm -r` cannot see a sibling (not a workspace member), so publish each
346
- // one on its own. Skipping this is what left a local registry serving a
347
- // current CLI against a months-old engine.
348
- for (const s of siblings) {
349
- runIn(s.dir, "pnpm", [
350
- "publish",
351
- "--registry",
352
- localRegistry,
353
- "--no-git-checks",
354
- "--access",
355
- "public",
356
- ]);
357
- }
292
+ // Mirror the CI `release` workflow via the shared publish task: it sets
293
+ // the release version on every workspace member (`bun pm pkg set`; they
294
+ // keep `0.0.0` on disk, projen-owned, so it unlocks each briefly), then
295
+ // `bun publish`es each non-private one - and bun natively strips the
296
+ // `workspace:`/`catalog:` protocols in the packed tarball, resolving each
297
+ // to the version just set. The next `projen` synth restores the manifests
298
+ // to `0.0.0`; the release version lives in the git tag. Provenance is off
299
+ // for a local registry (no OIDC), so `NPM_CONFIG_PROVENANCE` is unset.
300
+ // `publish.ts` is this task's SIBLING in the engine's `tasks/` dir; resolve
301
+ // it off `import.meta.url` (works whether the engine is source-linked in-repo
302
+ // or installed under node_modules) rather than a repo-relative `tasks/...`
303
+ // that only exists inside `projen/`.
304
+ const publishScript = fileURLToPath(new URL("./publish.ts", import.meta.url));
305
+ exec.spawnSync("bun", [publishScript, version, "--registry", localRegistry], {
306
+ cwd: process.cwd(),
307
+ stdout: "inherit",
308
+ stderr: "inherit",
309
+ stdin: "ignore",
310
+ check: true,
311
+ });
358
312
  logger.success(`published ${version} to ${localRegistry}`);
359
313
  }
360
314
 
@@ -364,7 +318,7 @@ program
364
318
  // manifest finish the bump in lockstep.
365
319
  if (opts.version) {
366
320
  writeManifestVersion(pkgPath, version);
367
- for (const s of siblings) writeManifestVersion(s.pkgPath, version, dependencyScope);
321
+ for (const s of siblings) writeManifestVersion(s.pkgPath, version);
368
322
  logger.info(`synchronized release manifests at ${version}`);
369
323
  }
370
324
  },