@lumenflow/cli 5.0.3 → 5.2.0

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.
Files changed (80) hide show
  1. package/dist/capacity-snapshot-emitter.js +10 -12
  2. package/dist/capacity-snapshot-emitter.js.map +1 -1
  3. package/dist/commands/integrate.js +9 -0
  4. package/dist/commands/integrate.js.map +1 -1
  5. package/dist/delegation-role-resolver.js +1 -1
  6. package/dist/delegation-role-resolver.js.map +1 -1
  7. package/dist/docs-generate-pack-reference.js +57 -15
  8. package/dist/docs-generate-pack-reference.js.map +1 -1
  9. package/dist/docs-sync.js +7 -3
  10. package/dist/docs-sync.js.map +1 -1
  11. package/dist/init-docs-scaffolder.js +18 -3
  12. package/dist/init-docs-scaffolder.js.map +1 -1
  13. package/dist/init-templates.js +307 -0
  14. package/dist/init-templates.js.map +1 -1
  15. package/dist/init.js +26 -6
  16. package/dist/init.js.map +1 -1
  17. package/dist/kernel-event-sync/lifecycle-emitters.js +13 -14
  18. package/dist/kernel-event-sync/lifecycle-emitters.js.map +1 -1
  19. package/dist/kernel-event-sync/narrow-emissions.js +7 -4
  20. package/dist/kernel-event-sync/narrow-emissions.js.map +1 -1
  21. package/dist/kernel-event-sync/software-delivery-emitters.js +91 -0
  22. package/dist/kernel-event-sync/software-delivery-emitters.js.map +1 -1
  23. package/dist/lumenflow-upgrade.js +86 -23
  24. package/dist/lumenflow-upgrade.js.map +1 -1
  25. package/dist/orchestrate-init-status.js +24 -11
  26. package/dist/orchestrate-init-status.js.map +1 -1
  27. package/dist/pack-install.js +27 -20
  28. package/dist/pack-install.js.map +1 -1
  29. package/dist/pack-publish.js +34 -26
  30. package/dist/pack-publish.js.map +1 -1
  31. package/dist/release.js +293 -146
  32. package/dist/release.js.map +1 -1
  33. package/dist/skills-projection.js +84 -0
  34. package/dist/skills-projection.js.map +1 -0
  35. package/dist/sync-templates.js +4 -4
  36. package/dist/sync-templates.js.map +1 -1
  37. package/dist/temp-dir-cleanup.js +112 -0
  38. package/dist/temp-dir-cleanup.js.map +1 -0
  39. package/dist/validate-agent-skills.js +4 -4
  40. package/dist/validate-agent-skills.js.map +1 -1
  41. package/dist/validate-agent-sync.js +109 -45
  42. package/dist/validate-agent-sync.js.map +1 -1
  43. package/dist/validate-skills-spec.js +2 -2
  44. package/dist/wu-done.js +1 -1
  45. package/dist/wu-done.js.map +1 -1
  46. package/dist/wu-spawn-prompt-builders.js +11 -34
  47. package/dist/wu-spawn-prompt-builders.js.map +1 -1
  48. package/dist/wu-spawn-strategy-resolver.js +4 -0
  49. package/dist/wu-spawn-strategy-resolver.js.map +1 -1
  50. package/package.json +11 -11
  51. package/packs/agent-runtime/manifest.ts +55 -4
  52. package/packs/agent-runtime/manifest.yaml +16 -6
  53. package/packs/agent-runtime/orchestration.ts +26 -1
  54. package/packs/agent-runtime/package.json +1 -1
  55. package/packs/agent-runtime/remote-controls/operations.ts +6 -0
  56. package/packs/agent-runtime/tool-impl/remote-controls.mock.ts +4 -0
  57. package/packs/agent-runtime/turn-lifecycle-events.ts +90 -1
  58. package/packs/sidekick/manifest.yaml +6 -0
  59. package/packs/sidekick/package.json +2 -1
  60. package/packs/sidekick/sidekick-events.ts +195 -18
  61. package/packs/sidekick/src/adapters/control-plane-bridge.adapter.ts +18 -10
  62. package/packs/sidekick/src/adapters/filesystem-bridge.adapter.ts +4 -0
  63. package/packs/sidekick/src/domain/channel.types.ts +34 -54
  64. package/packs/sidekick/src/ports/channel-bridge.port.ts +29 -12
  65. package/packs/sidekick/tool-impl/channel-tools.ts +47 -16
  66. package/packs/sidekick/tool-impl/system-tools.ts +4 -6
  67. package/packs/software-delivery/manifest.ts +94 -7
  68. package/packs/software-delivery/manifest.yaml +42 -5
  69. package/packs/software-delivery/package.json +1 -1
  70. package/packs/software-delivery/src/config/schemas/lumenflow-config-schema-types.ts +26 -2
  71. package/packs/software-delivery/src/config/workspace-reader.ts +67 -2
  72. package/packs/software-delivery/src/constants/wu-paths-constants.ts +26 -0
  73. package/packs/software-delivery/src/domain/orchestration.constants.ts +7 -9
  74. package/packs/software-delivery/src/domain/orchestration.schemas.ts +1 -2
  75. package/packs/software-delivery/src/domain/orchestration.types.ts +0 -2
  76. package/packs/software-delivery/src/state/wu-paths.ts +1 -1
  77. package/packs/software-delivery/tool-impl/wu-lifecycle-tools.ts +30 -0
  78. package/templates/core/AGENTS.md.template +12 -8
  79. package/templates/core/LUMENFLOW.md.template +14 -9
  80. package/templates/core/ai/onboarding/agent-invocation-guide.md.template +5 -0
package/dist/release.js CHANGED
@@ -26,11 +26,11 @@
26
26
  * WU-1074: Add release command for npm publishing
27
27
  */
28
28
  import { Command } from 'commander';
29
- import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync, } from 'node:fs';
29
+ import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync, } from 'node:fs';
30
30
  import { readFile, writeFile } from 'node:fs/promises';
31
- import { dirname, join } from 'node:path';
31
+ import { dirname, isAbsolute, join } from 'node:path';
32
32
  import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
33
- import { homedir, tmpdir } from 'node:os';
33
+ import { homedir } from 'node:os';
34
34
  import { execSync } from 'node:child_process';
35
35
  import { getGitForCwd } from '@lumenflow/core/git-adapter';
36
36
  import { die, createError, ErrorCodes } from '@lumenflow/core/error-handler';
@@ -38,6 +38,7 @@ import { ensureOnMain } from '@lumenflow/core/wu-helpers';
38
38
  import { REMOTES, FILE_SYSTEM, PKG_MANAGER } from '@lumenflow/core/wu-constants';
39
39
  import { withMicroWorktree } from '@lumenflow/core/micro-worktree';
40
40
  import { runCLI } from './cli-entry-point.js';
41
+ import { cleanupTempDir, createTrackedTempDir } from './temp-dir-cleanup.js';
41
42
  /** Log prefix for console output */
42
43
  const LOG_PREFIX = '[release]';
43
44
  /** Directory containing @lumenflow packages */
@@ -150,6 +151,104 @@ export const RELEASE_WU_TOOL = 'release';
150
151
  * Used as the operation identifier when creating the micro-worktree.
151
152
  */
152
153
  export const RELEASE_OPERATION_NAME = 'release';
154
+ /**
155
+ * WU-2854: Env var that overrides the release micro-worktree base directory.
156
+ *
157
+ * Mirrors the WU-2826/WU-2850 pattern exported by `lumenflow-upgrade.ts`.
158
+ * Operators can point the release worktree at a specific location (for
159
+ * example a tmpfs-backed scratch disk or a fast NVMe mount) when the
160
+ * default XDG cache target is inappropriate for their environment.
161
+ */
162
+ export const RELEASE_WORKTREE_DIR_ENV = 'LUMENFLOW_RELEASE_WORKTREE_DIR';
163
+ /**
164
+ * WU-2854: XDG Base Directory Specification env var. When set by the user
165
+ * we respect it as the cache root; otherwise we fall back to
166
+ * `${HOME}/.cache`. Kept lowercase in our own code for lint parity with
167
+ * `lumenflow-upgrade.ts`.
168
+ */
169
+ const XDG_CACHE_HOME_ENV = 'XDG_CACHE_HOME';
170
+ /**
171
+ * WU-2854: Sub-path under the cache root where per-version release
172
+ * worktrees live. The `${version}` leaf keeps concurrent releases — or a
173
+ * release retried after a crash — from clashing on the same directory
174
+ * while still being trivially inspectable on disk.
175
+ */
176
+ const RELEASE_WORKTREE_CACHE_SUBDIR = 'lumenflow/release-worktree';
177
+ /**
178
+ * WU-2854: Resolve the user cache root (XDG-compliant). When
179
+ * `XDG_CACHE_HOME` is set to an absolute non-empty path we use it
180
+ * verbatim; otherwise we default to `${HOME}/.cache`.
181
+ *
182
+ * Duplicated locally instead of imported from `lumenflow-upgrade.ts` to
183
+ * keep the release.ts blast radius narrow for WU-2854. A future refactor
184
+ * can extract a shared `xdg-cache-paths` module once a third consumer
185
+ * appears (tracking note: if `withMicroWorktree` itself moves off `/tmp`
186
+ * for *all* operations, consolidate there).
187
+ */
188
+ export function resolveReleaseCacheHome() {
189
+ const xdg = process.env[XDG_CACHE_HOME_ENV];
190
+ const trimmed = typeof xdg === 'string' ? xdg.trim() : '';
191
+ if (trimmed.length > 0 && isAbsolute(trimmed)) {
192
+ return trimmed;
193
+ }
194
+ return join(homedir(), '.cache');
195
+ }
196
+ /**
197
+ * WU-2854: Resolve the micro-worktree base directory for `pnpm release`.
198
+ *
199
+ * Priority:
200
+ * 1. `LUMENFLOW_RELEASE_WORKTREE_DIR` env var (when set and non-empty)
201
+ * 2. `${XDG_CACHE_HOME:-${HOME}/.cache}/lumenflow/release-worktree/${version}/`
202
+ *
203
+ * The v5.1.0 cut hit `ERR_PNPM_ENOSPC` during `pnpm install
204
+ * --frozen-lockfile` inside `/tmp/release-*` on a host with tmpfs `/tmp`.
205
+ * `withMicroWorktree` creates its directory via `mkdtempSync(join(tmpdir(),
206
+ * ...))`, so routing `TMPDIR` at this path before the call is the
207
+ * least-invasive way to land the ~1000 dep install footprint on the
208
+ * user's home partition without touching `@lumenflow/core`.
209
+ *
210
+ * Ensures the directory exists so the downstream `mkdtempSync` call inside
211
+ * `withMicroWorktree` does not ENOENT on first run.
212
+ */
213
+ export function resolveReleaseWorktreeDir(version) {
214
+ const override = process.env[RELEASE_WORKTREE_DIR_ENV];
215
+ const trimmed = typeof override === 'string' ? override.trim() : '';
216
+ const dir = trimmed.length > 0
217
+ ? isAbsolute(trimmed)
218
+ ? trimmed
219
+ : join(process.cwd(), trimmed)
220
+ : join(resolveReleaseCacheHome(), RELEASE_WORKTREE_CACHE_SUBDIR, version);
221
+ try {
222
+ mkdirSync(dir, { recursive: true });
223
+ }
224
+ catch {
225
+ // Best-effort — if we cannot create it, downstream will surface a
226
+ // clearer error. Returning the path keeps the helper deterministic.
227
+ }
228
+ return dir;
229
+ }
230
+ /**
231
+ * WU-2854: Remove the release worktree directory after a successful run.
232
+ *
233
+ * Release worktrees are strictly disposable (version bump + build + pack +
234
+ * publish — no state worth preserving across runs) so we always clean up
235
+ * on success. Failure branches intentionally retain the directory so the
236
+ * operator can inspect residue (partial `pnpm install`, failed publish
237
+ * logs, etc.) before the next attempt reclaims the space.
238
+ *
239
+ * Always best-effort: residual removal errors must never fail an
240
+ * otherwise successful release. Mirrors `cleanupUpgradeWorktreeDir` from
241
+ * `lumenflow-upgrade.ts` (WU-2850).
242
+ */
243
+ export function cleanupReleaseWorktreeDir(dir) {
244
+ try {
245
+ rmSync(dir, { recursive: true, force: true });
246
+ }
247
+ catch {
248
+ // Intentional swallow — cleanup is an optimisation, not a correctness
249
+ // requirement. The next run will reuse/recreate the path.
250
+ }
251
+ }
153
252
  /**
154
253
  * Build a micro-worktree ID from a release version (WU-2219)
155
254
  *
@@ -998,46 +1097,62 @@ const NPM_404_TOKEN = '404';
998
1097
  * Throws via `die` on failure. Opt-in via `--verify-consumer-install`.
999
1098
  */
1000
1099
  export function verifyConsumerInstall(packageJsonPaths, options = {}) {
1001
- const tarballDir = options.tarballDir ?? mkdtempSync(join(tmpdir(), 'lumenflow-release-tarballs-'));
1002
- const consumerDir = mkdtempSync(join(tmpdir(), 'lumenflow-release-consumer-'));
1003
- console.log(`${LOG_PREFIX} [${CONSUMER_INSTALL_LABEL}] Packing ${packageJsonPaths.length} packages into ${tarballDir}`);
1004
- const tarballs = [];
1005
- for (const packageJsonPath of packageJsonPaths) {
1006
- const manifest = readPackageManifest(packageJsonPath);
1007
- const packageName = manifest.name;
1008
- if (!packageName) {
1009
- continue;
1010
- }
1011
- const tarballPath = packWorkspacePackage(packageName, tarballDir, options.cwd);
1012
- tarballs.push({ packageName, tarballPath });
1013
- }
1014
- // Seed the consumer directory with a minimal package.json so `npm install`
1015
- // has somewhere to write node_modules.
1016
- writeFileSync(join(consumerDir, PACKAGE_JSON_FILENAME), JSON.stringify({ name: 'lumenflow-release-consumer-probe', private: true }));
1017
- const installArgs = tarballs.map((t) => t.tarballPath).join(' ');
1018
- const installCommand = `npm install --no-save --no-audit --no-fund ${installArgs}`;
1019
- let output = '';
1100
+ // WU-2859: /tmp/lumenflow-* scratch dirs must be cleaned up on success
1101
+ // AND failure. Previously these leaked on every release preflight and on
1102
+ // every npm-install failure, filling tmpfs during parallel wu:prep waves.
1103
+ const callerProvidedTarballDir = options.tarballDir !== undefined;
1104
+ const tarballDir = callerProvidedTarballDir
1105
+ ? options.tarballDir
1106
+ : createTrackedTempDir('lumenflow-release-tarballs-');
1107
+ const consumerDir = createTrackedTempDir('lumenflow-release-consumer-');
1020
1108
  try {
1021
- output = execSync(installCommand, {
1022
- cwd: consumerDir,
1023
- stdio: ['ignore', 'pipe', 'pipe'],
1024
- encoding: FILE_SYSTEM.ENCODING,
1025
- });
1109
+ console.log(`${LOG_PREFIX} [${CONSUMER_INSTALL_LABEL}] Packing ${packageJsonPaths.length} packages into ${tarballDir}`);
1110
+ const tarballs = [];
1111
+ for (const packageJsonPath of packageJsonPaths) {
1112
+ const manifest = readPackageManifest(packageJsonPath);
1113
+ const packageName = manifest.name;
1114
+ if (!packageName) {
1115
+ continue;
1116
+ }
1117
+ const tarballPath = packWorkspacePackage(packageName, tarballDir, options.cwd);
1118
+ tarballs.push({ packageName, tarballPath });
1119
+ }
1120
+ // Seed the consumer directory with a minimal package.json so `npm install`
1121
+ // has somewhere to write node_modules.
1122
+ writeFileSync(join(consumerDir, PACKAGE_JSON_FILENAME), JSON.stringify({ name: 'lumenflow-release-consumer-probe', private: true }));
1123
+ const installArgs = tarballs.map((t) => t.tarballPath).join(' ');
1124
+ const installCommand = `npm install --no-save --no-audit --no-fund ${installArgs}`;
1125
+ let output = '';
1126
+ try {
1127
+ output = execSync(installCommand, {
1128
+ cwd: consumerDir,
1129
+ stdio: ['ignore', 'pipe', 'pipe'],
1130
+ encoding: FILE_SYSTEM.ENCODING,
1131
+ });
1132
+ }
1133
+ catch (error) {
1134
+ const message = error instanceof Error ? error.message : String(error);
1135
+ die(`${CONSUMER_INSTALL_FAILURE_HEADER}\n\n` +
1136
+ `Scratch consumer dir: ${consumerDir}\n` +
1137
+ `Tarball dir: ${tarballDir}\n\n` +
1138
+ `${message}`);
1139
+ }
1140
+ if (output.includes(NPM_404_TOKEN)) {
1141
+ die(`${CONSUMER_INSTALL_FAILURE_HEADER}\n\n` +
1142
+ `Scratch consumer dir: ${consumerDir}\n` +
1143
+ `Tarball dir: ${tarballDir}\n\n` +
1144
+ `npm install printed a 404:\n${output}`);
1145
+ }
1146
+ console.log(`${LOG_PREFIX} ✅ [${CONSUMER_INSTALL_LABEL}] Consumer install succeeded for ${tarballs.length} packages`);
1147
+ }
1148
+ finally {
1149
+ // Caller-provided tarball dirs are cleaned up by the caller; our contract
1150
+ // is that any dir we created here is removed before returning.
1151
+ if (!callerProvidedTarballDir) {
1152
+ cleanupTempDir(tarballDir);
1153
+ }
1154
+ cleanupTempDir(consumerDir);
1026
1155
  }
1027
- catch (error) {
1028
- const message = error instanceof Error ? error.message : String(error);
1029
- die(`${CONSUMER_INSTALL_FAILURE_HEADER}\n\n` +
1030
- `Scratch consumer dir: ${consumerDir}\n` +
1031
- `Tarball dir: ${tarballDir}\n\n` +
1032
- `${message}`);
1033
- }
1034
- if (output.includes(NPM_404_TOKEN)) {
1035
- die(`${CONSUMER_INSTALL_FAILURE_HEADER}\n\n` +
1036
- `Scratch consumer dir: ${consumerDir}\n` +
1037
- `Tarball dir: ${tarballDir}\n\n` +
1038
- `npm install printed a 404:\n${output}`);
1039
- }
1040
- console.log(`${LOG_PREFIX} ✅ [${CONSUMER_INSTALL_LABEL}] Consumer install succeeded for ${tarballs.length} packages`);
1041
1156
  }
1042
1157
  /**
1043
1158
  * Pack a workspace package into a tarball on disk and return the absolute path.
@@ -1241,115 +1356,147 @@ export async function executeReleaseInMicroWorktree(opts) {
1241
1356
  }
1242
1357
  // Exit changeset pre mode if active (read-only check, write to micro-worktree)
1243
1358
  const changesetPreActive = isInChangesetPreMode(mainCwd);
1359
+ // WU-2854: Retarget the micro-worktree base dir off tmpfs /tmp (default on
1360
+ // many Debian/Ubuntu cloud hosts) onto the user's home partition.
1361
+ // `withMicroWorktree` resolves its worktree path via
1362
+ // `mkdtempSync(join(tmpdir(), …))`, so setting TMPDIR for the duration of
1363
+ // the call is the least-invasive way to steer the worktree location
1364
+ // without modifying @lumenflow/core. The default target is the user's
1365
+ // XDG cache (${XDG_CACHE_HOME:-$HOME/.cache}/lumenflow/release-worktree/
1366
+ // <version>/). Consumers can override via LUMENFLOW_RELEASE_WORKTREE_DIR.
1367
+ // Mirrors WU-2850 pattern used by lumenflow-upgrade.ts.
1368
+ const releaseWorktreeDir = resolveReleaseWorktreeDir(version);
1369
+ /* eslint-disable sonarjs/publicly-writable-directories -- WU-2854: target is user-owned XDG cache dir, not /tmp. */
1370
+ const originalTmpdir = process.env.TMPDIR;
1371
+ process.env.TMPDIR = releaseWorktreeDir;
1372
+ let releaseTransactionSucceeded = false;
1244
1373
  // ── Micro-worktree: all writes happen here ──────────────────────────
1245
- await withReleaseEnv(async () => {
1246
- await withMicroWorktree({
1247
- operation: RELEASE_OPERATION_NAME,
1248
- id: buildReleaseWorktreeId(version),
1249
- logPrefix: LOG_PREFIX,
1250
- execute: async ({ worktreePath }) => {
1251
- // Resolve package paths relative to micro-worktree
1252
- const worktreePackagePaths = findPackageJsonPaths(worktreePath);
1253
- const worktreePackageDirs = worktreePackagePaths.map((path) => dirname(path));
1254
- // Exit changeset pre mode in micro-worktree if it was active on main
1255
- if (changesetPreActive) {
1256
- console.log(`${LOG_PREFIX} Detected changeset pre-release mode, exiting...`);
1257
- exitChangesetPreMode(worktreePath);
1258
- console.log(`${LOG_PREFIX} ✅ Exited changeset pre mode`);
1259
- }
1260
- // Phase 1: Materialize dist symlinks and build
1261
- const distPreparation = ensureDistPathsMaterialized(worktreePackageDirs, {
1262
- skipBuild,
1263
- dryRun: false,
1264
- });
1265
- if (distPreparation.materializedCount > 0) {
1266
- console.log(`${LOG_PREFIX} [${PRE_FLIGHT_LABEL}] Materialized ${distPreparation.materializedCount} symlinked dist directories`);
1267
- }
1268
- // WU-2221: Install deps in micro-worktree (no node_modules by default)
1269
- runCommand(`${PKG_MANAGER} install --frozen-lockfile`, {
1270
- label: 'install',
1271
- cwd: worktreePath,
1272
- });
1273
- console.log(`${LOG_PREFIX} ✅ Dependencies installed in micro-worktree`);
1274
- if (!skipBuild) {
1275
- runCommand(`${PKG_MANAGER} build`, { label: 'build', cwd: worktreePath });
1276
- console.log(`${LOG_PREFIX} ✅ Build complete`);
1277
- }
1278
- else {
1279
- console.log(`${LOG_PREFIX} Skipping build (--skip-build)`);
1280
- }
1281
- // Phase 2: Version bump (inside micro-worktree)
1282
- console.log(`${LOG_PREFIX} Bumping versions to ${version}...`);
1283
- await updatePackageVersions(worktreePackagePaths, version);
1284
- const versionPolicyPath = await updateVersionPolicy(version, worktreePath);
1285
- if (versionPolicyPath) {
1286
- console.log(`${LOG_PREFIX} ✅ Updated ${VERSION_POLICY_RELATIVE_PATH}`);
1287
- }
1288
- console.log(`${LOG_PREFIX} ✅ Versions updated to ${version}`);
1289
- // Phase 2b: Regenerate Starlight changelog manifest (WU-2570).
1290
- // The generator reads currentVersion from the freshly bumped
1291
- // version-policy.yaml, so it must run AFTER updateVersionPolicy
1292
- // and BEFORE the release commit is built, otherwise the tagged
1293
- // commit carries a stale currentVersion and the live docs site
1294
- // displays the previous release.
1295
- const changelogAbsPath = join(worktreePath, CHANGELOG_RELATIVE_PATH);
1296
- const changelogExisted = existsSync(changelogAbsPath);
1297
- if (changelogExisted) {
1298
- regenerateChangelog(worktreePath);
1299
- console.log(`${LOG_PREFIX} ✅ Regenerated ${CHANGELOG_RELATIVE_PATH}`);
1300
- }
1301
- // Phase 3: Validate packed artifacts after the version bump and before
1302
- // any commit, tag, or optional npm publish step.
1303
- validateReleaseArtifactsForPublish(worktreePackagePaths, false, worktreePath);
1304
- // Phase 3b (WU-2825): Static preflight — every publishable package's
1305
- // @lumenflow/* deps must either be in the current publish set or
1306
- // already available on the npm registry. Catches private:true leaks
1307
- // (the v5.0.1 @lumenflow/host regression) before publish.
1308
- assertPublishSetResolvable(worktreePackagePaths);
1309
- // Phase 3c (WU-2825): Optional dynamic check — pack all tarballs
1310
- // and run `npm install` in a scratch consumer dir to catch ERESOLVE,
1311
- // exports-map drift, and peer conflicts that the static check cannot
1312
- // see.
1313
- if (opts.verifyConsumerInstall) {
1314
- verifyConsumerInstall(worktreePackagePaths, { cwd: worktreePath });
1315
- }
1316
- // Phase 4: Publish to npm (from micro-worktree)
1317
- if (shouldPublishPackages) {
1318
- console.log(`${LOG_PREFIX} Publishing packages to npm...`);
1319
- runCommand(`${PKG_MANAGER} -r publish --access public --no-git-checks`, {
1320
- label: 'publish',
1374
+ try {
1375
+ await withReleaseEnv(async () => {
1376
+ await withMicroWorktree({
1377
+ operation: RELEASE_OPERATION_NAME,
1378
+ id: buildReleaseWorktreeId(version),
1379
+ logPrefix: LOG_PREFIX,
1380
+ execute: async ({ worktreePath }) => {
1381
+ // Resolve package paths relative to micro-worktree
1382
+ const worktreePackagePaths = findPackageJsonPaths(worktreePath);
1383
+ const worktreePackageDirs = worktreePackagePaths.map((path) => dirname(path));
1384
+ // Exit changeset pre mode in micro-worktree if it was active on main
1385
+ if (changesetPreActive) {
1386
+ console.log(`${LOG_PREFIX} Detected changeset pre-release mode, exiting...`);
1387
+ exitChangesetPreMode(worktreePath);
1388
+ console.log(`${LOG_PREFIX} ✅ Exited changeset pre mode`);
1389
+ }
1390
+ // Phase 1: Materialize dist symlinks and build
1391
+ const distPreparation = ensureDistPathsMaterialized(worktreePackageDirs, {
1392
+ skipBuild,
1393
+ dryRun: false,
1394
+ });
1395
+ if (distPreparation.materializedCount > 0) {
1396
+ console.log(`${LOG_PREFIX} [${PRE_FLIGHT_LABEL}] Materialized ${distPreparation.materializedCount} symlinked dist directories`);
1397
+ }
1398
+ // WU-2221: Install deps in micro-worktree (no node_modules by default)
1399
+ runCommand(`${PKG_MANAGER} install --frozen-lockfile`, {
1400
+ label: 'install',
1321
1401
  cwd: worktreePath,
1322
1402
  });
1323
- console.log(`${LOG_PREFIX} ✅ Packages published to npm`);
1324
- }
1325
- else {
1326
- console.log(`${LOG_PREFIX} Skipping npm publish (--skip-publish)`);
1327
- }
1328
- // Build list of modified files for commit
1329
- const relativePaths = worktreePackagePaths.map((p) => p.replace(worktreePath + '/', ''));
1330
- // Include version-policy.yaml if it exists
1331
- if (existsSync(join(worktreePath, VERSION_POLICY_RELATIVE_PATH))) {
1332
- relativePaths.push(VERSION_POLICY_RELATIVE_PATH);
1333
- }
1334
- // Include changelog.json if it was regenerated (WU-2570).
1335
- // The file is always regenerated when it existed before the bump,
1336
- // so only check the pre-bump existence flag — not whether the file
1337
- // is dirty in git — to keep the commit deterministic.
1338
- if (changelogExisted) {
1339
- relativePaths.push(CHANGELOG_RELATIVE_PATH);
1340
- }
1341
- // Include changeset pre.json removal if applicable
1342
- if (changesetPreActive) {
1343
- const changesetPrePath = join(CHANGESET_DIR, CHANGESET_PRE_JSON);
1344
- relativePaths.push(changesetPrePath);
1345
- }
1346
- return {
1347
- commitMessage: buildCommitMessage(version),
1348
- files: relativePaths,
1349
- };
1350
- },
1403
+ console.log(`${LOG_PREFIX} ✅ Dependencies installed in micro-worktree`);
1404
+ if (!skipBuild) {
1405
+ runCommand(`${PKG_MANAGER} build`, { label: 'build', cwd: worktreePath });
1406
+ console.log(`${LOG_PREFIX} Build complete`);
1407
+ }
1408
+ else {
1409
+ console.log(`${LOG_PREFIX} Skipping build (--skip-build)`);
1410
+ }
1411
+ // Phase 2: Version bump (inside micro-worktree)
1412
+ console.log(`${LOG_PREFIX} Bumping versions to ${version}...`);
1413
+ await updatePackageVersions(worktreePackagePaths, version);
1414
+ const versionPolicyPath = await updateVersionPolicy(version, worktreePath);
1415
+ if (versionPolicyPath) {
1416
+ console.log(`${LOG_PREFIX} Updated ${VERSION_POLICY_RELATIVE_PATH}`);
1417
+ }
1418
+ console.log(`${LOG_PREFIX} ✅ Versions updated to ${version}`);
1419
+ // Phase 2b: Regenerate Starlight changelog manifest (WU-2570).
1420
+ // The generator reads currentVersion from the freshly bumped
1421
+ // version-policy.yaml, so it must run AFTER updateVersionPolicy
1422
+ // and BEFORE the release commit is built, otherwise the tagged
1423
+ // commit carries a stale currentVersion and the live docs site
1424
+ // displays the previous release.
1425
+ const changelogAbsPath = join(worktreePath, CHANGELOG_RELATIVE_PATH);
1426
+ const changelogExisted = existsSync(changelogAbsPath);
1427
+ if (changelogExisted) {
1428
+ regenerateChangelog(worktreePath);
1429
+ console.log(`${LOG_PREFIX} ✅ Regenerated ${CHANGELOG_RELATIVE_PATH}`);
1430
+ }
1431
+ // Phase 3: Validate packed artifacts after the version bump and before
1432
+ // any commit, tag, or optional npm publish step.
1433
+ validateReleaseArtifactsForPublish(worktreePackagePaths, false, worktreePath);
1434
+ // Phase 3b (WU-2825): Static preflight — every publishable package's
1435
+ // @lumenflow/* deps must either be in the current publish set or
1436
+ // already available on the npm registry. Catches private:true leaks
1437
+ // (the v5.0.1 @lumenflow/host regression) before publish.
1438
+ assertPublishSetResolvable(worktreePackagePaths);
1439
+ // Phase 3c (WU-2825): Optional dynamic check — pack all tarballs
1440
+ // and run `npm install` in a scratch consumer dir to catch ERESOLVE,
1441
+ // exports-map drift, and peer conflicts that the static check cannot
1442
+ // see.
1443
+ if (opts.verifyConsumerInstall) {
1444
+ verifyConsumerInstall(worktreePackagePaths, { cwd: worktreePath });
1445
+ }
1446
+ // Phase 4: Publish to npm (from micro-worktree)
1447
+ if (shouldPublishPackages) {
1448
+ console.log(`${LOG_PREFIX} Publishing packages to npm...`);
1449
+ runCommand(`${PKG_MANAGER} -r publish --access public --no-git-checks`, {
1450
+ label: 'publish',
1451
+ cwd: worktreePath,
1452
+ });
1453
+ console.log(`${LOG_PREFIX} ✅ Packages published to npm`);
1454
+ }
1455
+ else {
1456
+ console.log(`${LOG_PREFIX} Skipping npm publish (--skip-publish)`);
1457
+ }
1458
+ // Build list of modified files for commit
1459
+ const relativePaths = worktreePackagePaths.map((p) => p.replace(worktreePath + '/', ''));
1460
+ // Include version-policy.yaml if it exists
1461
+ if (existsSync(join(worktreePath, VERSION_POLICY_RELATIVE_PATH))) {
1462
+ relativePaths.push(VERSION_POLICY_RELATIVE_PATH);
1463
+ }
1464
+ // Include changelog.json if it was regenerated (WU-2570).
1465
+ // The file is always regenerated when it existed before the bump,
1466
+ // so only check the pre-bump existence flag — not whether the file
1467
+ // is dirty in git — to keep the commit deterministic.
1468
+ if (changelogExisted) {
1469
+ relativePaths.push(CHANGELOG_RELATIVE_PATH);
1470
+ }
1471
+ // Include changeset pre.json removal if applicable
1472
+ if (changesetPreActive) {
1473
+ const changesetPrePath = join(CHANGESET_DIR, CHANGESET_PRE_JSON);
1474
+ relativePaths.push(changesetPrePath);
1475
+ }
1476
+ return {
1477
+ commitMessage: buildCommitMessage(version),
1478
+ files: relativePaths,
1479
+ };
1480
+ },
1481
+ });
1351
1482
  });
1352
- });
1483
+ releaseTransactionSucceeded = true;
1484
+ }
1485
+ finally {
1486
+ if (originalTmpdir === undefined) {
1487
+ delete process.env.TMPDIR;
1488
+ }
1489
+ else {
1490
+ process.env.TMPDIR = originalTmpdir;
1491
+ }
1492
+ // WU-2854: Only clean up on success. Preserving the directory on
1493
+ // failure lets operators inspect residue (partial install, failed
1494
+ // publish logs) before the next attempt reclaims the space.
1495
+ if (releaseTransactionSucceeded) {
1496
+ cleanupReleaseWorktreeDir(releaseWorktreeDir);
1497
+ }
1498
+ }
1499
+ /* eslint-enable sonarjs/publicly-writable-directories */
1353
1500
  // ── Post micro-worktree: tag and push tag ─────────────────────────
1354
1501
  // The micro-worktree has already merged the version bump commit to main
1355
1502
  // and pushed to origin. Now create and push the git tag.