@dbx-tools/projen 0.6.100 → 0.6.102
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/README.md +10 -4
- package/package.json +4 -4
- package/src/project-js.ts +4 -3
- package/src/release.ts +7 -6
- package/tasks/bump.ts +63 -50
- package/tasks/publish.ts +140 -38
package/README.md
CHANGED
|
@@ -262,12 +262,17 @@ a new package is covered without a re-synth. Work from the root:
|
|
|
262
262
|
|
|
263
263
|
`bump` also mirrors a release into local registries when the active clients are
|
|
264
264
|
pointed at loopback services. npm uses `npm config get registry` and publishes
|
|
265
|
-
to a local Verdaccio automatically.
|
|
265
|
+
to a local Verdaccio automatically. Publishable JavaScript members compile once
|
|
266
|
+
from the root in parallel, then upload through a bounded pool without rerunning
|
|
267
|
+
their `prepack` tasks. When synth already made the manifests and Bun workspace
|
|
268
|
+
lock release-current, publishing also skips redundant version stamping and the
|
|
269
|
+
lockfile reinstall. Python prefers uv's default index and only
|
|
266
270
|
treats a loopback `.../+simple/` URL as writable devpi; a read-only cache such as
|
|
267
271
|
proxpi (`.../index/`) is deliberately ignored. The task stamps every Python
|
|
268
272
|
member and its sibling dependencies to the release version, builds the workspace
|
|
269
273
|
with uv, then runs `devpi upload --from-dir` against the derived writable index.
|
|
270
|
-
|
|
274
|
+
The npm and Python mirrors run concurrently because they touch disjoint package
|
|
275
|
+
trees. Devpi client authentication remains in its normal `~/.devpi` state.
|
|
271
276
|
|
|
272
277
|
Use `--local-registry false` or `--local-pypi false` to disable either local
|
|
273
278
|
publish. An explicit `--local-pypi http://localhost:3141/user/index/` overrides
|
|
@@ -282,8 +287,9 @@ Members intentionally keep only the tasks that something OTHER than a human
|
|
|
282
287
|
invokes, so there is no second place to run the same thing:
|
|
283
288
|
|
|
284
289
|
- `compile` / `test` - what the root's `--filter '*'` delegation calls.
|
|
285
|
-
- `prepack` -
|
|
286
|
-
(27 of 33 members;
|
|
290
|
+
- `prepack` - standalone-publish safety that compiles one package before packing
|
|
291
|
+
(27 of 33 members; the workspace release driver compiles them from the root
|
|
292
|
+
and publishes with lifecycle scripts disabled).
|
|
287
293
|
- `watch` - a single-package `tsc --build -w`, for narrowing a long
|
|
288
294
|
edit/compile loop to one package.
|
|
289
295
|
- `build` / `install` / `install:ci` / `default` / `pre-compile` /
|
package/package.json
CHANGED
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@clack/prompts": "^1.7.0",
|
|
29
|
-
"@dbx-tools/core": "0.6.
|
|
30
|
-
"@dbx-tools/path": "0.6.
|
|
31
|
-
"@dbx-tools/shared-core": "0.6.
|
|
29
|
+
"@dbx-tools/core": "0.6.102",
|
|
30
|
+
"@dbx-tools/path": "0.6.102",
|
|
31
|
+
"@dbx-tools/shared-core": "0.6.102",
|
|
32
32
|
"commander": "^15.0.0",
|
|
33
33
|
"concurrently": "^10.0.3",
|
|
34
34
|
"constructs": "^10.6.0",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"main": "index.ts",
|
|
49
49
|
"license": "Apache-2.0",
|
|
50
|
-
"version": "0.6.
|
|
50
|
+
"version": "0.6.102",
|
|
51
51
|
"types": "index.ts",
|
|
52
52
|
"type": "module",
|
|
53
53
|
"exports": {
|
package/src/project-js.ts
CHANGED
|
@@ -383,9 +383,10 @@ function defaultProjectOptions(
|
|
|
383
383
|
// No `npm pack` step on any project. projen wires `package` into `build`, so
|
|
384
384
|
// `bunx projen build` would tarball all 36 manifests (root included) into
|
|
385
385
|
// gitignored `dist/js` on every CI run and never read them: publishing here
|
|
386
|
-
// is
|
|
387
|
-
// {@link applyCompiledPublish}
|
|
388
|
-
// means no projen Publisher exists to consume the
|
|
386
|
+
// is the workspace publish task compiling packages once from the root before
|
|
387
|
+
// `bun publish --ignore-scripts` packs them (see {@link applyCompiledPublish}),
|
|
388
|
+
// and `release: false` means no projen Publisher exists to consume the
|
|
389
|
+
// artifacts either.
|
|
389
390
|
package: false,
|
|
390
391
|
jest: false,
|
|
391
392
|
github: false,
|
package/src/release.ts
CHANGED
|
@@ -18,12 +18,13 @@ const BUN_VERSION = "1.3.14";
|
|
|
18
18
|
*
|
|
19
19
|
* Bun has no `pnpm -r publish` equivalent, so this drives the engine's
|
|
20
20
|
* `tasks/publish.ts` (shipped in the engine tarball, run via bun): it reads the
|
|
21
|
-
* workspace members from the root `package.json`,
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* and honors
|
|
21
|
+
* workspace members from the root `package.json`, ensures manifests and the Bun
|
|
22
|
+
* lock carry the tag version so `workspace:*` siblings resolve to it, then
|
|
23
|
+
* `bun publish`es each non-`private` package. The driver compiles publishable members once from the
|
|
24
|
+
* root, then runs a bounded pool of `bun publish --ignore-scripts` calls so the
|
|
25
|
+
* already-built packages upload concurrently. It also folds `publishConfig`'s
|
|
26
|
+
* compiled `lib/` entry points into each packed manifest and honors
|
|
27
|
+
* `NPM_CONFIG_PROVENANCE`.
|
|
27
28
|
*
|
|
28
29
|
* Two ways in: a pushed `<prefix>*` tag (the real release - `GITHUB_REF_NAME` is
|
|
29
30
|
* the version) and a manual `workflow_dispatch` (no tag, so a throwaway
|
package/tasks/bump.ts
CHANGED
|
@@ -179,7 +179,7 @@ program
|
|
|
179
179
|
)
|
|
180
180
|
.option("--python-root <path>", "Python workspace package root", "packages/py")
|
|
181
181
|
.action(
|
|
182
|
-
(opts: {
|
|
182
|
+
async (opts: {
|
|
183
183
|
level: Level;
|
|
184
184
|
prefix: string;
|
|
185
185
|
sibling: Sibling[];
|
|
@@ -257,8 +257,17 @@ program
|
|
|
257
257
|
// plus anything synth regenerated. Skip the commit when nothing changed.
|
|
258
258
|
git(["add", "-A"]);
|
|
259
259
|
const staged = git(["diff", "--cached", "--name-only"], true);
|
|
260
|
-
if (staged)
|
|
261
|
-
|
|
260
|
+
if (staged) {
|
|
261
|
+
git([
|
|
262
|
+
"commit",
|
|
263
|
+
"-m",
|
|
264
|
+
`chore(release): ${version}`,
|
|
265
|
+
"-m",
|
|
266
|
+
"🌸 Shipped with Kanna — https://kanna.sh",
|
|
267
|
+
"-m",
|
|
268
|
+
"Co-Authored-By: Kanna <noreply@kanna.sh>\nKanna-Agent: codex/databricks-gpt-5-6-sol",
|
|
269
|
+
]);
|
|
270
|
+
} else logger.info("nothing to commit");
|
|
262
271
|
}
|
|
263
272
|
|
|
264
273
|
if (opts.tag) {
|
|
@@ -284,32 +293,6 @@ program
|
|
|
284
293
|
if (opts.version === false && localRegistry) {
|
|
285
294
|
logger.info("skipped local publish (--no-version left package.json unbumped)");
|
|
286
295
|
}
|
|
287
|
-
if (publishToLocalRegistry) {
|
|
288
|
-
logger.info(`publishing ${version} to local registry ${localRegistry}`);
|
|
289
|
-
// Mirror the CI `release` workflow via the shared publish task: it
|
|
290
|
-
// re-affirms the release version on every workspace member (`bun pm pkg
|
|
291
|
-
// set`; the manifests already carry the shared `VERSION`, projen-owned, so
|
|
292
|
-
// it unlocks each briefly), then `bun publish`es each non-private one - and
|
|
293
|
-
// bun natively strips the `workspace:`/`catalog:` protocols in the packed
|
|
294
|
-
// tarball, resolving each to that version. `publish.ts` restores every
|
|
295
|
-
// manifest it touched at exit, so this leaves the worktree matching the
|
|
296
|
-
// release commit that was just pushed. Provenance is off for a local
|
|
297
|
-
// registry (no OIDC), so `NPM_CONFIG_PROVENANCE` is unset.
|
|
298
|
-
// `publish.ts` is this task's SIBLING in the engine's `tasks/` dir; resolve
|
|
299
|
-
// it off `import.meta.url` (works whether the engine is source-linked in-repo
|
|
300
|
-
// or installed under node_modules) rather than a repo-relative `tasks/...`
|
|
301
|
-
// that only exists inside `projen/`.
|
|
302
|
-
const publishScript = fileURLToPath(new URL("./publish.ts", import.meta.url));
|
|
303
|
-
exec.spawnSync("bun", [publishScript, version, "--registry", localRegistry], {
|
|
304
|
-
cwd: process.cwd(),
|
|
305
|
-
stdout: "inherit",
|
|
306
|
-
stderr: "inherit",
|
|
307
|
-
stdin: "ignore",
|
|
308
|
-
check: true,
|
|
309
|
-
});
|
|
310
|
-
logger.success(`published ${version} to ${localRegistry}`);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
296
|
// Scan EVERY active index (primary index-url + every extra-index-url,
|
|
314
297
|
// across uv and pip): auto-mode publishes to the first that is a loopback
|
|
315
298
|
// devpi +simple, so the corp proxy stays primary and a local devpi added
|
|
@@ -328,31 +311,61 @@ program
|
|
|
328
311
|
}
|
|
329
312
|
if (opts.version === false && localPypi) {
|
|
330
313
|
logger.info("skipped local Python publish (--no-version left packages unstamped)");
|
|
331
|
-
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// npm and Python touch disjoint package trees and registries. Start both
|
|
317
|
+
// mirrors together so local publishing takes the slower duration rather
|
|
318
|
+
// than the sum of both. Each child still owns its internal build/upload
|
|
319
|
+
// ordering and restores its temporary manifest edits on exit.
|
|
320
|
+
const localPublishes: Promise<unknown>[] = [];
|
|
321
|
+
if (publishToLocalRegistry) {
|
|
322
|
+
logger.info(`publishing ${version} to local registry ${localRegistry}`);
|
|
323
|
+
// The shared driver skips stamping/install when synth already made the
|
|
324
|
+
// workspace release-current, compiles publishable members once from the
|
|
325
|
+
// root, then publishes through a bounded pool. Temporary publishConfig
|
|
326
|
+
// edits are restored before this child exits.
|
|
327
|
+
const publishScript = fileURLToPath(new URL("./publish.ts", import.meta.url));
|
|
328
|
+
localPublishes.push(
|
|
329
|
+
exec
|
|
330
|
+
.spawn("bun", [publishScript, version, "--registry", localRegistry], {
|
|
331
|
+
cwd: process.cwd(),
|
|
332
|
+
stdout: "inherit",
|
|
333
|
+
stderr: "inherit",
|
|
334
|
+
stdin: "ignore",
|
|
335
|
+
check: true,
|
|
336
|
+
})
|
|
337
|
+
.then(() => logger.success(`published ${version} to ${localRegistry}`)),
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
if (opts.version && localPypi && existsSync(pythonRoot)) {
|
|
332
341
|
logger.info(`publishing Python ${version} to local devpi ${localPypi.publishUrl}`);
|
|
333
342
|
const publishPythonScript = fileURLToPath(new URL("./publish-python.ts", import.meta.url));
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
343
|
+
localPublishes.push(
|
|
344
|
+
exec
|
|
345
|
+
.spawn(
|
|
346
|
+
"bun",
|
|
347
|
+
[
|
|
348
|
+
publishPythonScript,
|
|
349
|
+
version,
|
|
350
|
+
"--root",
|
|
351
|
+
pythonRoot,
|
|
352
|
+
"--index-url",
|
|
353
|
+
localPypi.indexUrl,
|
|
354
|
+
"--publish-url",
|
|
355
|
+
localPypi.publishUrl,
|
|
356
|
+
],
|
|
357
|
+
{
|
|
358
|
+
cwd: process.cwd(),
|
|
359
|
+
stdout: "inherit",
|
|
360
|
+
stderr: "inherit",
|
|
361
|
+
stdin: "ignore",
|
|
362
|
+
check: true,
|
|
363
|
+
},
|
|
364
|
+
)
|
|
365
|
+
.then(() => logger.success(`published Python ${version} to ${localPypi.publishUrl}`)),
|
|
353
366
|
);
|
|
354
|
-
logger.success(`published Python ${version} to ${localPypi.publishUrl}`);
|
|
355
367
|
}
|
|
368
|
+
await Promise.all(localPublishes);
|
|
356
369
|
},
|
|
357
370
|
);
|
|
358
371
|
|
package/tasks/publish.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env -S bun
|
|
2
2
|
/**
|
|
3
3
|
* `bun tasks/publish.ts <version> [--registry <url>] [--exclude <dir>] [--dry-run]`
|
|
4
|
-
* -
|
|
5
|
-
* one with `bun publish`.
|
|
4
|
+
* - ensure every workspace member and the Bun lock carry the release version,
|
|
5
|
+
* then publish each non-private one with `bun publish`.
|
|
6
6
|
*
|
|
7
7
|
* Bun has no `pnpm -r publish`, so this loop is the recursive-publish stand-in.
|
|
8
8
|
* It leans on native bun for everything bun already does:
|
|
9
9
|
*
|
|
10
|
-
* - **version stamping
|
|
11
|
-
* native package.json editor - idempotent, edits only that
|
|
12
|
-
* git side effects; `bun pm version` is for bumping and
|
|
13
|
-
* version, so `pkg set` is the right tool for an exact
|
|
10
|
+
* - **version stamping**, when needed, is `bun pm pkg set version=<version>`
|
|
11
|
+
* per member (bun's native package.json editor - idempotent, edits only that
|
|
12
|
+
* dir's manifest, no git side effects; `bun pm version` is for bumping and
|
|
13
|
+
* errors on an unchanged version, so `pkg set` is the right tool for an exact
|
|
14
|
+
* release value);
|
|
14
15
|
* - **`workspace:` / `catalog:` rewriting** is NOT done here - `bun publish`
|
|
15
16
|
* strips both protocols in the PACKED tarball, resolving `workspace:*` to the
|
|
16
17
|
* sibling's version and `catalog:` to the root catalog entry. (Verified: a
|
|
@@ -18,7 +19,9 @@
|
|
|
18
19
|
* while the on-disk manifest keeps the protocols.) Setting each member's
|
|
19
20
|
* version first is the only prerequisite, so a sibling resolves the release
|
|
20
21
|
* version; the disk manifest already carries the workspace `VERSION`, and
|
|
21
|
-
* this makes doubly sure it matches the value being published
|
|
22
|
+
* this makes doubly sure it matches the value being published. When every
|
|
23
|
+
* manifest and Bun's workspace lock already carry the version, both this
|
|
24
|
+
* stamp and the lockfile refresh are skipped;
|
|
22
25
|
* - **`publishConfig` substitution** (compiled `lib/` entry points) is done
|
|
23
26
|
* HERE, by {@link applyPublishConfig}, NOT by bun: unlike pnpm/npm, `bun
|
|
24
27
|
* publish`/`bun pm pack` do NOT fold `publishConfig`'s `main`/`types`/`bin`/
|
|
@@ -28,10 +31,13 @@
|
|
|
28
31
|
* its `#!/usr/bin/env node` shebang, node chokes on the `.ts`
|
|
29
32
|
* (ERR_UNKNOWN_FILE_EXTENSION). We merge `publishConfig` onto the top-level
|
|
30
33
|
* manifest before packing so the tarball advertises the compiled `lib/` tree;
|
|
31
|
-
* -
|
|
32
|
-
*
|
|
34
|
+
* - **compiled output** is emitted once, before publishing, by one root-level
|
|
35
|
+
* filtered `bun run` that fans out to every publishable member in parallel.
|
|
36
|
+
* The later `bun publish --ignore-scripts` calls therefore pack the already
|
|
37
|
+
* compiled `lib/` trees instead of serially repeating each member's
|
|
38
|
+
* `prepack`. Packages retain their `prepack` task for standalone publishes.
|
|
33
39
|
*
|
|
34
|
-
* `--dry-run` forwards to `bun publish`: it packs + validates
|
|
40
|
+
* `--dry-run` forwards to `bun publish`: it packs + validates
|
|
35
41
|
* but uploads nothing, so the `release` workflow is testable end-to-end via a
|
|
36
42
|
* `workflow_dispatch` run without anything reaching npm. `--registry` targets a
|
|
37
43
|
* non-default registry (a local verdaccio); `--exclude <dir>` (repeatable,
|
|
@@ -55,6 +61,7 @@ import { chmodSync, existsSync, readFileSync, rmSync, statSync, writeFileSync }
|
|
|
55
61
|
import { dirname, join, resolve } from "node:path";
|
|
56
62
|
import { exec } from "@dbx-tools/core";
|
|
57
63
|
import { log } from "@dbx-tools/shared-core";
|
|
64
|
+
import ts from "typescript";
|
|
58
65
|
import { parse } from "yaml";
|
|
59
66
|
|
|
60
67
|
const logger = log.logger("dbx-tools:publish");
|
|
@@ -95,6 +102,42 @@ function workspaceMembers(root: string): string[] {
|
|
|
95
102
|
return (doc?.packages ?? []).map((m) => resolve(root, m));
|
|
96
103
|
}
|
|
97
104
|
|
|
105
|
+
/** Whether every workspace member already carries the requested release version. */
|
|
106
|
+
function manifestsMatchVersion(members: readonly string[], version: string): boolean {
|
|
107
|
+
return members.every((dir) => {
|
|
108
|
+
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")) as {
|
|
109
|
+
version?: string;
|
|
110
|
+
};
|
|
111
|
+
return pkg.version === version;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Whether Bun's workspace lock records the requested version for every member. */
|
|
116
|
+
function lockfileMatchesVersion(
|
|
117
|
+
root: string,
|
|
118
|
+
members: readonly string[],
|
|
119
|
+
version: string,
|
|
120
|
+
): boolean {
|
|
121
|
+
const lockfile = join(root, "bun.lock");
|
|
122
|
+
if (!existsSync(lockfile)) return false;
|
|
123
|
+
try {
|
|
124
|
+
const parsed = ts.parseConfigFileTextToJson(lockfile, readFileSync(lockfile, "utf8"));
|
|
125
|
+
if (parsed.error) return false;
|
|
126
|
+
const lock = parsed.config as {
|
|
127
|
+
workspaces?: Record<string, { version?: string }>;
|
|
128
|
+
};
|
|
129
|
+
return members.every((dir) => {
|
|
130
|
+
const relative = dir
|
|
131
|
+
.slice(resolve(root).length + 1)
|
|
132
|
+
.split("\\")
|
|
133
|
+
.join("/");
|
|
134
|
+
return lock.workspaces?.[relative]?.version === version;
|
|
135
|
+
});
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
98
141
|
/** Spawn `command` in `cwd` with `PATH` overridden, failing the task on non-zero. */
|
|
99
142
|
function run(cwd: string, command: string, args: string[], path: string): void {
|
|
100
143
|
exec.spawnSync(command, args, {
|
|
@@ -107,6 +150,37 @@ function run(cwd: string, command: string, args: string[], path: string): void {
|
|
|
107
150
|
});
|
|
108
151
|
}
|
|
109
152
|
|
|
153
|
+
/** Asynchronous counterpart used for bounded parallel stamping and publishing. */
|
|
154
|
+
async function runAsync(cwd: string, command: string, args: string[], path: string): Promise<void> {
|
|
155
|
+
await exec.spawn(command, args, {
|
|
156
|
+
cwd,
|
|
157
|
+
stdout: "inherit",
|
|
158
|
+
stderr: "inherit",
|
|
159
|
+
stdin: "ignore",
|
|
160
|
+
check: true,
|
|
161
|
+
env: { ...process.env, PATH: path },
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Run independent jobs with a small fixed worker pool. */
|
|
166
|
+
async function runConcurrent<T>(
|
|
167
|
+
values: readonly T[],
|
|
168
|
+
concurrency: number,
|
|
169
|
+
worker: (value: T) => Promise<void>,
|
|
170
|
+
): Promise<void> {
|
|
171
|
+
let next = 0;
|
|
172
|
+
const results = await Promise.allSettled(
|
|
173
|
+
Array.from({ length: Math.min(concurrency, values.length) }, async () => {
|
|
174
|
+
while (next < values.length) {
|
|
175
|
+
const value = values[next++];
|
|
176
|
+
await worker(value);
|
|
177
|
+
}
|
|
178
|
+
}),
|
|
179
|
+
);
|
|
180
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
181
|
+
if (failure?.status === "rejected") throw failure.reason;
|
|
182
|
+
}
|
|
183
|
+
|
|
110
184
|
/**
|
|
111
185
|
* Make a projen-readonly manifest writable so `bun pm pkg set` / `bun publish` can
|
|
112
186
|
* edit it, recording its original bytes + mode for {@link restoreManifests} the
|
|
@@ -173,12 +247,9 @@ function applyPublishConfig(pkgPath: string): void {
|
|
|
173
247
|
}
|
|
174
248
|
|
|
175
249
|
/**
|
|
176
|
-
* PATH with the workspace-root `node_modules/.bin` prepended.
|
|
177
|
-
* each package's
|
|
178
|
-
*
|
|
179
|
-
* `.bin`, not a per-package one, so without this the compile fails with
|
|
180
|
-
* `dax: tsc: command not found`. (In CI `bun install` already puts it there; this
|
|
181
|
-
* makes the task self-sufficient when invoked directly too.)
|
|
250
|
+
* PATH with the workspace-root `node_modules/.bin` prepended. The root-level
|
|
251
|
+
* compile reaches each package's projen/dax task, which resolves `tsc` off PATH;
|
|
252
|
+
* under the hoisted linker `tsc` lives only in the root `.bin`.
|
|
182
253
|
*/
|
|
183
254
|
function enrichedPath(root: string): string {
|
|
184
255
|
const binDir = join(root, "node_modules", ".bin");
|
|
@@ -196,6 +267,12 @@ if (!version) {
|
|
|
196
267
|
const registryIdx = rest.indexOf("--registry");
|
|
197
268
|
const registry = registryIdx >= 0 ? rest[registryIdx + 1] : undefined;
|
|
198
269
|
const dryRun = rest.includes("--dry-run");
|
|
270
|
+
const concurrencyIdx = rest.indexOf("--concurrency");
|
|
271
|
+
const parsedConcurrency = Number(concurrencyIdx >= 0 ? rest[concurrencyIdx + 1] : 4);
|
|
272
|
+
if (!Number.isInteger(parsedConcurrency) || parsedConcurrency < 1) {
|
|
273
|
+
throw new Error(`--concurrency must be a positive integer, got ${String(parsedConcurrency)}`);
|
|
274
|
+
}
|
|
275
|
+
const concurrency = parsedConcurrency;
|
|
199
276
|
// `--stamp-only`: set versions + refresh the lockfile, then STOP (no publish).
|
|
200
277
|
// The standalone `projen-release` uses this to version-stamp the workspace so its
|
|
201
278
|
// own `bun publish` (run separately, in `projen/`) resolves `workspace:*` siblings
|
|
@@ -218,26 +295,31 @@ const members = workspaceMembers(root)
|
|
|
218
295
|
.filter((dir) => existsSync(join(dir, "package.json")))
|
|
219
296
|
.filter((dir) => !excluded.has(resolve(root, dir).replace(`${resolve(root)}/`, "")));
|
|
220
297
|
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
logger.info(`
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
298
|
+
// Ensure every member carries the release version before packing. A normal bump
|
|
299
|
+
// already synthesized it everywhere; dry runs and standalone invocations may not
|
|
300
|
+
// have, so retain the native `bun pm pkg set` fallback for those paths.
|
|
301
|
+
if (manifestsMatchVersion(members, version)) {
|
|
302
|
+
logger.info(`all ${members.length} member manifests already carry ${version}`);
|
|
303
|
+
} else {
|
|
304
|
+
logger.info(`setting ${version} across ${members.length} members`);
|
|
305
|
+
await runConcurrent(members, concurrency, async (dir) => {
|
|
306
|
+
unlockManifest(join(dir, "package.json"));
|
|
307
|
+
await runAsync(dir, "bun", ["pm", "pkg", "set", `version=${version}`], path);
|
|
308
|
+
});
|
|
229
309
|
}
|
|
230
310
|
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
// does. Without this a `workspace:*` dep could publish against a stale resolved
|
|
236
|
-
// version instead of the one just set.
|
|
311
|
+
// Ensure the lockfile resolves each `workspace:*` to the release version.
|
|
312
|
+
// `bun publish`/`pm pack` reads workspace versions from the LOCKFILE, not just
|
|
313
|
+
// live manifests. A normal bump's synth/install already makes it current; after
|
|
314
|
+
// fallback stamping, deleting it before install is what forces re-resolution.
|
|
237
315
|
const lockfile = join(root, "bun.lock");
|
|
238
|
-
if (
|
|
239
|
-
logger.info(
|
|
240
|
-
|
|
316
|
+
if (lockfileMatchesVersion(root, members, version)) {
|
|
317
|
+
logger.info(`workspace lock already resolves members at ${version}`);
|
|
318
|
+
} else {
|
|
319
|
+
if (existsSync(lockfile)) rmSync(lockfile);
|
|
320
|
+
logger.info("refreshing lockfile so workspace deps resolve to the release version");
|
|
321
|
+
run(root, "bun", ["install"], path);
|
|
322
|
+
}
|
|
241
323
|
|
|
242
324
|
if (stampOnly) {
|
|
243
325
|
logger.success(`stamped ${members.length} members @ ${version} (no publish)`);
|
|
@@ -247,14 +329,16 @@ if (stampOnly) {
|
|
|
247
329
|
const publishArgs = [
|
|
248
330
|
"--access",
|
|
249
331
|
"public",
|
|
332
|
+
"--ignore-scripts",
|
|
250
333
|
...(registry ? ["--registry", registry] : []),
|
|
251
334
|
...(dryRun ? ["--dry-run"] : []),
|
|
252
335
|
];
|
|
253
|
-
|
|
336
|
+
const publishable: Array<{ dir: string; name: string; compile: boolean }> = [];
|
|
254
337
|
for (const dir of members) {
|
|
255
338
|
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")) as {
|
|
256
339
|
name?: string;
|
|
257
340
|
private?: boolean;
|
|
341
|
+
scripts?: Record<string, string>;
|
|
258
342
|
};
|
|
259
343
|
if (pkg.private) {
|
|
260
344
|
logger.info(`skip private ${pkg.name ?? dirname(dir)}`);
|
|
@@ -265,8 +349,26 @@ for (const dir of members) {
|
|
|
265
349
|
const manifestPath = join(dir, "package.json");
|
|
266
350
|
unlockManifest(manifestPath);
|
|
267
351
|
applyPublishConfig(manifestPath);
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
352
|
+
publishable.push({
|
|
353
|
+
dir,
|
|
354
|
+
name: pkg.name ?? dirname(dir),
|
|
355
|
+
compile: Boolean(pkg.scripts && typeof pkg.scripts === "object" && "prepack" in pkg.scripts),
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const compiled = publishable.filter((pkg) => pkg.compile);
|
|
360
|
+
if (compiled.length > 0) {
|
|
361
|
+
logger.info(`compiling ${compiled.length} publishable packages from the workspace root`);
|
|
362
|
+
run(root, "bun", ["run", ...compiled.flatMap((pkg) => ["--filter", pkg.name]), "compile"], path);
|
|
271
363
|
}
|
|
272
|
-
|
|
364
|
+
|
|
365
|
+
logger.info(
|
|
366
|
+
`${dryRun ? "dry-run packing" : "publishing"} ${publishable.length} packages with concurrency ${concurrency}`,
|
|
367
|
+
);
|
|
368
|
+
await runConcurrent(publishable, concurrency, async ({ dir, name }) => {
|
|
369
|
+
logger.info(`${dryRun ? "dry-run publishing" : "publishing"} ${name} @ ${version}`);
|
|
370
|
+
await runAsync(dir, "bun", ["publish", ...publishArgs], path);
|
|
371
|
+
});
|
|
372
|
+
logger.success(
|
|
373
|
+
`${dryRun ? "dry-run: packed" : "published"} ${publishable.length} packages @ ${version}`,
|
|
374
|
+
);
|