@smoothbricks/cli 0.11.14 → 0.11.15

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.
@@ -45,6 +45,22 @@ export declare function nxProjectChangelogArgs(pkg: ReleasePackageInfo, previous
45
45
  export declare function projectChangelogContents(result: ProjectChangelogLookupResult, projectName: string): string;
46
46
  export declare function githubReleaseLookupExists(tag: string, exitCode: number, stdout: string, stderr: string): boolean;
47
47
  export declare function githubReleaseCommandArgs(tag: string, notesFile: string, releaseExists: boolean, version: string): string[];
48
+ /**
49
+ * A module of the WORKSPACE's Nx, not of smoo's own dependency. smoo installs
50
+ * with its own `nx` in the global virtual store, where the workspace's
51
+ * node_modules is invisible: that Nx cannot resolve the workspace's
52
+ * `versionActions` plugin (`Unable to resolve the "versionActions"
53
+ * implementation ... "@smoothbricks/nx-plugin/version-actions"`), and it is
54
+ * a second Nx version driving one release. Every in-process Nx call for a
55
+ * workspace resolves from that workspace's root, exactly as `nx` on its PATH
56
+ * would.
57
+ */
58
+ interface WorkspaceNxModules {
59
+ 'src/utils/workspace-root.js': typeof import('nx/src/utils/workspace-root.js');
60
+ 'src/command-line/release/version.js': typeof import('nx/src/command-line/release/version.js');
61
+ 'src/command-line/release/changelog.js': typeof import('nx/src/command-line/release/changelog.js');
62
+ }
63
+ export declare function importWorkspaceNx<Subpath extends keyof WorkspaceNxModules>(root: string, subpath: Subpath): Promise<WorkspaceNxModules[Subpath]>;
48
64
  export declare function withNxWorkspaceRoot<T>(root: string, run: () => Promise<T>): Promise<T>;
49
65
  export {};
50
66
  //# sourceMappingURL=github-release.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"github-release.d.ts","sourceRoot":"","sources":["../../src/release/github-release.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,kBAAkB,EAAc,MAAM,WAAW,CAAC;AAYhE,UAAU,4BAA4B;IACpC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,6BAA6B,CAAC,OAAO,SAAS,kBAAkB,GAAG,kBAAkB;IACpG,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,wBAAsB,gCAAgC,CAAC,KAAK,EAAE,6BAA6B,GAAG,OAAO,CAAC,MAAM,CAAC,CAS5G;AAED,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,kBAAkB,EACvB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;EAc1G;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,4BAA4B,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAM1G;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAShH;AAED,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,OAAO,EACtB,OAAO,EAAE,MAAM,GACd,MAAM,EAAE,CAgBV;AAMD,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAmB5F"}
1
+ {"version":3,"file":"github-release.d.ts","sourceRoot":"","sources":["../../src/release/github-release.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,kBAAkB,EAAc,MAAM,WAAW,CAAC;AAYhE,UAAU,4BAA4B;IACpC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,6BAA6B,CAAC,OAAO,SAAS,kBAAkB,GAAG,kBAAkB;IACpG,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,wBAAsB,gCAAgC,CAAC,KAAK,EAAE,6BAA6B,GAAG,OAAO,CAAC,MAAM,CAAC,CAS5G;AAED,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,kBAAkB,EACvB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;EAc1G;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,4BAA4B,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAM1G;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAShH;AAED,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,OAAO,EACtB,OAAO,EAAE,MAAM,GACd,MAAM,EAAE,CAgBV;AAMD;;;;;;;;;GASG;AACH,UAAU,kBAAkB;IAC1B,6BAA6B,EAAE,cAAc,gCAAgC,CAAC,CAAC;IAC/E,qCAAqC,EAAE,cAAc,wCAAwC,CAAC,CAAC;IAC/F,uCAAuC,EAAE,cAAc,0CAA0C,CAAC,CAAC;CACpG;AAED,wBAAsB,iBAAiB,CAAC,OAAO,SAAS,MAAM,kBAAkB,EAC9E,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAMtC;AAED,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAmB5F"}
@@ -1,6 +1,8 @@
1
1
  import { mkdtemp, rm, writeFile } from 'node:fs/promises';
2
+ import { createRequire } from 'node:module';
2
3
  import { tmpdir } from 'node:os';
3
4
  import { join } from 'node:path';
5
+ import { pathToFileURL } from 'node:url';
4
6
  import { releaseTag } from './core.js';
5
7
  const nxRenderOnlyReleaseConfig = {
6
8
  changelog: {
@@ -13,7 +15,7 @@ const nxRenderOnlyReleaseConfig = {
13
15
  };
14
16
  export async function renderNxProjectChangelogContents(input) {
15
17
  return withNxWorkspaceRoot(input.root, async () => {
16
- const { createAPI } = await import('nx/src/command-line/release/changelog.js');
18
+ const { createAPI } = await importWorkspaceNx(input.root, 'src/command-line/release/changelog.js');
17
19
  const result = await createAPI(nxRenderOnlyReleaseConfig, false)(nxProjectChangelogArgs(input.pkg, input.previousTag, input.dryRun));
18
20
  return projectChangelogContents(result, input.pkg.projectName);
19
21
  });
@@ -84,8 +86,15 @@ export function githubReleaseCommandArgs(tag, notesFile, releaseExists, version)
84
86
  function isPrereleaseVersion(version) {
85
87
  return version.includes('-');
86
88
  }
89
+ export async function importWorkspaceNx(root, subpath) {
90
+ const resolved = createRequire(join(root, 'package.json')).resolve(`nx/${subpath}`);
91
+ // A dynamic specifier types as `any`; the map above is the declared shape of
92
+ // the module the workspace's Nx serves at that subpath.
93
+ const module = await import(pathToFileURL(resolved).href);
94
+ return module;
95
+ }
87
96
  export async function withNxWorkspaceRoot(root, run) {
88
- const workspaceRootModule = await import('nx/src/utils/workspace-root.js');
97
+ const workspaceRootModule = await importWorkspaceNx(root, 'src/utils/workspace-root.js');
89
98
  const previousWorkspaceRoot = workspaceRootModule.workspaceRoot;
90
99
  const previousEnvWorkspaceRoot = process.env.NX_WORKSPACE_ROOT_PATH;
91
100
  const previousCwd = process.cwd();
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/release/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/release/index.ts"],"names":[],"mappings":"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"}
@@ -22,7 +22,7 @@ import { bootstrapNpmPackages, NPM_BOOTSTRAP_DIST_TAG, NPM_BOOTSTRAP_VERSION, }
22
22
  import { resolveBuildInputPatterns } from './build-inputs.js';
23
23
  import { parseReleaseProjectSelection, releaseCandidatePackages, } from './candidates.js';
24
24
  import { classifyReleaseBranchPush, collectOwnedReleaseTagRecords, divergedReleaseBranchMessage, pendingReleaseTargets, releaseTag, releaseTagAliases, } from './core.js';
25
- import { createOrUpdateGithubRelease, githubReleaseLookupExists, renderNxProjectChangelogContents, withNxWorkspaceRoot, } from './github-release.js';
25
+ import { createOrUpdateGithubRelease, githubReleaseLookupExists, importWorkspaceNx, renderNxProjectChangelogContents, withNxWorkspaceRoot, } from './github-release.js';
26
26
  import { publishWithAuthDiagnostics } from './npm-auth.js';
27
27
  import { bumpStableReleaseToNext, collectRepairPlatformOutputs, completeReleaseAtHead as completeReleaseAtHeadWithShell, prepareReleaseCandidate, repairPendingTargets, runReleaseTag, runReleaseVersion, } from './orchestration.js';
28
28
  import { packReleaseTarball } from './pack.js';
@@ -520,8 +520,8 @@ async function runNxReleaseVersion(root, projects, bump, dryRun) {
520
520
  }
521
521
  async function runNxReleaseVersionPreview(root, projects, bump) {
522
522
  return withNxWorkspaceRoot(root, async () => {
523
- const { createAPI } = await import('nx/src/command-line/release/version.js');
524
- const { createAPI: createChangelogAPI } = await import('nx/src/command-line/release/changelog.js');
523
+ const { createAPI } = await importWorkspaceNx(root, 'src/command-line/release/version.js');
524
+ const { createAPI: createChangelogAPI } = await importWorkspaceNx(root, 'src/command-line/release/changelog.js');
525
525
  const projectNames = projects.split(',').filter(Boolean);
526
526
  const result = await createAPI({}, false)({
527
527
  specifier: bump === 'auto' ? undefined : bump,
@@ -140,14 +140,49 @@ devenv_path_and_caches() {
140
140
  }
141
141
 
142
142
  build_devenv_shell() {
143
+ local since
144
+ since="$(date +%s)"
143
145
  # One evaluation: the shell that captures the environment is the shell
144
146
  # build. A separate `devenv shell -- date` evaluated everything twice.
145
147
  persist_devenv_environment
148
+ if [ "${SMOO_HOST_RUNNER:-false}" != true ]; then
149
+ root_build_inputs "$since"
150
+ fi
146
151
  # Add repo-local tools only after the shell exists; cleanup steps use an
147
152
  # explicit PATH because failures before this point must still refresh caches.
148
153
  add_repo_paths
149
154
  }
150
155
 
156
+ # The store cache's post phase collects garbage down to the live roots before
157
+ # it saves, and the shell's own root (.devenv/gc/shell) covers only the build
158
+ # closure. What evaluation fetched and built - flake input sources, the
159
+ # import-from-derivation outputs devenv-nixpkgs-patched and cabal2nix-* - has
160
+ # no root, so it was collected on the miss run and every hit run rebuilt it
161
+ # (measured at ~80s of a 150s setup on hosted macOS, run 34427309827). devenv
162
+ # records no store paths in its eval cache, so the exact set is the paths this
163
+ # build registered: root them under .devenv/gc, which the devenv cache carries
164
+ # and /nix/var/nix/gcroots/auto (inside the store cache) points at.
165
+ root_build_inputs() {
166
+ local since="$1" gcdir="$PWD/.devenv/gc/build-inputs" paths
167
+ rm -rf "$gcdir"
168
+ mkdir -p "$gcdir"
169
+ # `nix path-info --all --json` is an object keyed by path on Nix >= 2.19 and
170
+ # an array of {path} objects before that.
171
+ paths="$(nix path-info --all --json 2>/dev/null | jq -r --argjson since "$since" '
172
+ (if type == "array" then map({key: .path, value: .}) | from_entries else . end)
173
+ | to_entries[] | select(.value.registrationTime >= $since) | select(.key | endswith(".drv") | not) | .key')"
174
+ if [ -z "$paths" ]; then
175
+ echo "no store paths registered since $since; nothing to root"
176
+ return 0
177
+ fi
178
+ # Store paths are installables; --out-link names the first link and numbers
179
+ # the rest, each an indirect gc root. Derivations are excluded: keep-derivations
180
+ # retains the ones whose outputs are rooted, and realising a .drv would build it.
181
+ # shellcheck disable=SC2086
182
+ nix build --out-link "$gcdir/path" $paths
183
+ echo "rooted $(printf '%s\n' "$paths" | wc -l | tr -d ' ') store paths registered during the shell build under $gcdir"
184
+ }
185
+
151
186
  # Workflow steps run their commands directly — never through `devenv shell` —
152
187
  # so nothing the shell hooks export survives this composite action on its own.
153
188
  # Persisting a hand-picked list here proved to be whack-a-mole: the TTSC
@@ -26,6 +26,9 @@
26
26
  * Failures aggregate so a single direnv reload surfaces every problem.
27
27
  * Error text names variables and exit codes only: secret values, provider
28
28
  * stdout/stderr, and command arguments are never echoed.
29
+ * A resolved value still reaches the environment of the install its caller
30
+ * runs next, so `maskSecretValues` redacts those values out of any captured
31
+ * child output that caller replays.
29
32
  *
30
33
  * The variable `smoo.remoteCache.tokenSecret` names is never resolved here,
31
34
  * nor anywhere at shell entry. A remote cache is an optimization, and shell
@@ -311,6 +314,69 @@ const runSecretCommand: SecretCommandRunner = async (argv) => {
311
314
  return stdout;
312
315
  };
313
316
 
317
+ /** '*', the byte a redacted secret leaves behind. */
318
+ const MASK_BYTE = 0x2a;
319
+
320
+ /**
321
+ * A copy of captured child output with every resolved secret value replaced
322
+ * by the same number of `*` bytes.
323
+ *
324
+ * setup-environment.ts puts the values this file resolves into the
325
+ * environment of the install it then runs, and replays that child's captured
326
+ * stdout/stderr verbatim when it fails. Whatever the child echoes — a prepare
327
+ * script printing its environment, a registry URL carrying an inline
328
+ * credential — would otherwise walk straight past the suppression the rest of
329
+ * this file maintains. Redaction is byte-exact and same-length, so the
330
+ * failure being reported stays legible and every offset in it still lines up.
331
+ * Output with nothing to redact is returned as it came in, so the path that
332
+ * has no secrets to hide copies nothing.
333
+ */
334
+ export function maskSecretValues(output: Uint8Array, values: Iterable<string>): Uint8Array {
335
+ const encoder = new TextEncoder();
336
+ let masked: Uint8Array | undefined;
337
+ for (const value of values) {
338
+ const needle = encoder.encode(value);
339
+ if (needle.length === 0 || needle.length > output.length) {
340
+ continue;
341
+ }
342
+ for (let from = 0; from + needle.length <= output.length; ) {
343
+ const at = indexOfBytes(masked ?? output, needle, from);
344
+ if (at < 0) {
345
+ break;
346
+ }
347
+ // `new Uint8Array`, not `.slice()`: a captured child's output arrives as
348
+ // a node Buffer, whose `slice` is `subarray` — a view. Masking through
349
+ // one would rewrite the caller's own captured bytes.
350
+ masked ??= new Uint8Array(output);
351
+ masked.fill(MASK_BYTE, at, at + needle.length);
352
+ from = at + needle.length;
353
+ }
354
+ }
355
+ return masked ?? output;
356
+ }
357
+
358
+ /** First occurrence of `needle` in `haystack` at or after `from`, or -1. */
359
+ function indexOfBytes(haystack: Uint8Array, needle: Uint8Array, from: number): number {
360
+ const first = needle[0];
361
+ if (first === undefined) {
362
+ return -1;
363
+ }
364
+ const last = haystack.length - needle.length;
365
+ for (let at = haystack.indexOf(first, from); at >= 0 && at <= last; at = haystack.indexOf(first, at + 1)) {
366
+ let matches = true;
367
+ for (let offset = 1; offset < needle.length; offset += 1) {
368
+ if (haystack[at + offset] !== needle[offset]) {
369
+ matches = false;
370
+ break;
371
+ }
372
+ }
373
+ if (matches) {
374
+ return at;
375
+ }
376
+ }
377
+ return -1;
378
+ }
379
+
314
380
  /**
315
381
  * The declared secrets an install needs, resolved before it runs. The cache
316
382
  * token is deliberately not among them: a remote cache is an optimization, so
@@ -4,7 +4,7 @@ import { mkdir, rmdir, stat } from 'node:fs/promises';
4
4
  import { createRequire } from 'node:module';
5
5
  import path from 'node:path';
6
6
  import { $ } from 'bun';
7
- import { resolveSecretEnvironment } from './secret-references.ts';
7
+ import { maskSecretValues, resolveSecretEnvironment } from './secret-references.ts';
8
8
 
9
9
  // DEVENV_ROOT is set by the devenv shell, which is how this script normally
10
10
  // runs. CI jobs that install dependencies without building that shell (the
@@ -24,6 +24,14 @@ class CapturedCommandError extends Error {
24
24
  }
25
25
  }
26
26
 
27
+ // Every value resolved out of smoo.secrets below. The install inherits them,
28
+ // so whatever a failing child echoed is redacted before its captured output
29
+ // is replayed. Declared above the first replay site (resolveProjectRoot's own
30
+ // failure) because module consts are not hoisted: a reference from there to a
31
+ // const declared further down would report a temporal-dead-zone error instead
32
+ // of the failure it was called to report.
33
+ const resolvedSecretValues: string[] = [];
34
+
27
35
  async function resolveProjectRoot(): Promise<string> {
28
36
  if (devenvRoot) {
29
37
  return path.resolve(`${devenvRoot}/../..`);
@@ -66,19 +74,11 @@ const TYPESCRIPT_API_VERSION = '6.0.3';
66
74
  process.chdir(projectRoot);
67
75
 
68
76
  try {
69
- // Provider-declared secrets (smoo.secrets) resolve before any install. The
70
- // values land in THIS process environment only — bun install, the prepare
71
- // scripts it runs, and every later child of this script inherit them (for
72
- // example .npmrc `${VAR}` auth); the direnv shell itself does not, which is
73
- // the point: this script must never act as a global shell export.
74
- for (const [name, value] of Object.entries(await resolveSecretEnvironment({ root: projectRoot }))) {
75
- process.env[name] = value;
76
- }
77
-
78
77
  // Bootstrap only: install deps + wire local git hooks/config.
79
78
  // Do not import workspace packages here — this script is what installs them,
80
79
  // and package resolution/Typia transforms are not available yet.
81
80
  if (process.env.CI) {
81
+ await resolveSecrets();
82
82
  // Concurrent devenv activations share one node_modules, so the CI
83
83
  // installs race exactly like local ones (EEXIST link failures under
84
84
  // parallel shells). Serialize them under the same setup lock. Nothing
@@ -118,24 +118,60 @@ try {
118
118
  process.exit(1);
119
119
  }
120
120
  } else {
121
- await installLocalDependencies();
121
+ // A local secret-resolution or install failure (a provider that is not
122
+ // signed in, an unpublished private package, a missing registry
123
+ // credential, a lockfile that needs `devenv update`) must not take the
124
+ // shell down with it: direnv drops the whole environment on a non-zero
125
+ // exit, and then bun, nx, op and every other tool needed to repair the
126
+ // install are gone too. Report it, finish what does not depend on the
127
+ // install, and load the shell. CI above stays strict.
128
+ const installError = await installLocalDependencies();
129
+ if (installError === undefined) {
130
+ // Pin unscoped typescript → API 6 for root and Bun's shared .bun hoist (Nx).
131
+ ensureTypeScriptApiPackage(projectRoot);
132
+ }
133
+ await applyWorkspaceGitConfig(projectRoot);
134
+ if (installError !== undefined) {
135
+ reportDegradedSetup(installError);
136
+ }
122
137
  }
123
-
124
- // Pin unscoped typescript → API 6 for root and Bun's shared .bun hoist (Nx).
125
- ensureTypeScriptApiPackage(projectRoot);
126
-
127
- await applyWorkspaceGitConfig(projectRoot);
128
138
  } catch (error) {
129
139
  reportSetupFailure(error);
130
140
  }
131
141
 
132
- async function installLocalDependencies(): Promise<void> {
142
+ /**
143
+ * Provider-declared secrets (smoo.secrets) resolve before any install. The
144
+ * values land in THIS process environment only — bun install, the prepare
145
+ * scripts it runs, and every later child of this script inherit them (for
146
+ * example .npmrc `${VAR}` auth); the direnv shell itself does not, which is
147
+ * the point: this script must never act as a global shell export.
148
+ */
149
+ async function resolveSecrets(): Promise<void> {
150
+ for (const [name, value] of Object.entries(await resolveSecretEnvironment({ root: projectRoot }))) {
151
+ process.env[name] = value;
152
+ resolvedSecretValues.push(value);
153
+ }
154
+ }
155
+
156
+ async function installLocalDependencies(): Promise<unknown> {
157
+ try {
158
+ await resolveSecrets();
159
+ } catch (error) {
160
+ return error;
161
+ }
133
162
  // bun install runs the root prepare script. Multiple concurrent direnv
134
163
  // activations can otherwise race while mutating the same files under
135
- // node_modules.
164
+ // node_modules. Nothing that exits the process may run inside the callback
165
+ // (see the CI branch), so the failure is returned, not thrown.
166
+ let installError: unknown;
136
167
  await withSetupLock(async () => {
137
- await runSetupCommand('bun install --no-summary', $`bun install --no-summary`);
168
+ try {
169
+ await runSetupCommand('bun install --no-summary', $`bun install --no-summary`);
170
+ } catch (error) {
171
+ installError = error;
172
+ }
138
173
  });
174
+ return installError;
139
175
  }
140
176
 
141
177
  function ensureTypeScriptApiPackage(root: string): void {
@@ -374,25 +410,44 @@ function isFileExistsError(error: unknown): boolean {
374
410
  }
375
411
 
376
412
  function reportSetupFailure(error: unknown): never {
413
+ describeFailure('ERROR', error);
414
+ process.exit(1);
415
+ }
416
+
417
+ function reportDegradedSetup(error: unknown): void {
418
+ describeFailure('WARNING', error);
419
+ console.error(
420
+ 'The shell is loaded WITHOUT installed dependencies so the tools to repair this stay available.\n' +
421
+ 'Fix the cause above (missing registry credential, unpublished package, stale lockfile → `devenv update`),\n' +
422
+ 'then run `bun install` or `direnv reload`.',
423
+ );
424
+ console.error('---');
425
+ }
426
+
427
+ function describeFailure(level: 'ERROR' | 'WARNING', error: unknown): void {
377
428
  if (error instanceof CapturedCommandError) {
378
- console.error(`--- ERROR: setup-environment.ts failed while running: ${error.command}`);
429
+ console.error(`--- ${level}: setup-environment.ts failed while running: ${error.command}`);
379
430
  console.error(`exit code: ${error.exitCode}`);
380
431
  } else {
381
- console.error(`--- ERROR: setup-environment.ts failed: ${error}`);
432
+ console.error(`--- ${level}: setup-environment.ts failed: ${error}`);
382
433
  }
383
434
  replayCapturedOutput(error);
384
435
  console.error('\n---');
385
- process.exit(1);
386
436
  }
387
437
 
388
438
  function replayCapturedOutput(error: unknown): void {
389
439
  if (!(error instanceof CapturedCommandError)) {
390
440
  return;
391
441
  }
392
- if (error.stdout.length > 0) {
393
- process.stdout.write(error.stdout);
442
+ // The install ran with every resolved secret in its environment, so its
443
+ // output is redacted before replay. Masking is same-length, which keeps the
444
+ // failure this replay exists to show intact.
445
+ const stdout = maskSecretValues(error.stdout, resolvedSecretValues);
446
+ const stderr = maskSecretValues(error.stderr, resolvedSecretValues);
447
+ if (stdout.length > 0) {
448
+ process.stdout.write(stdout);
394
449
  }
395
- if (error.stderr.length > 0) {
396
- process.stderr.write(error.stderr);
450
+ if (stderr.length > 0) {
451
+ process.stderr.write(stderr);
397
452
  }
398
453
  }
@@ -200,8 +200,8 @@ runs:
200
200
  /nix
201
201
  ~/.cache/nix
202
202
  # prettier-ignore
203
- primary-key: ${{ runner.os }}-${{ runner.arch }}-nix-v5-${{ hashFiles('tooling/direnv/devenv.yaml', 'tooling/direnv/devenv.nix', 'tooling/direnv/devenv.lock') }}
204
- restore-prefixes-first-match: ${{ runner.os }}-${{ runner.arch }}-nix-v5-
203
+ primary-key: ${{ runner.os }}-${{ runner.arch }}-nix-v6-${{ hashFiles('tooling/direnv/devenv.yaml', 'tooling/direnv/devenv.nix', 'tooling/direnv/devenv.lock') }}
204
+ restore-prefixes-first-match: ${{ runner.os }}-${{ runner.arch }}-nix-v6-
205
205
  gc-max-store-size: 1G
206
206
 
207
207
  - name: ⚡ Enable Cachix
@@ -236,4 +236,6 @@ runs:
236
236
  - name: 🐚 Build devenv shell
237
237
  shell: bash
238
238
  working-directory: ${{ env.DEVENV_WORKDIR }}/tooling/direnv
239
+ env:
240
+ SMOO_HOST_RUNNER: ${{ steps.runner-kind.outputs.host }}
239
241
  run: ./github-actions-bootstrap.sh build-shell
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smoothbricks/cli",
3
- "version": "0.11.14",
3
+ "version": "0.11.15",
4
4
  "type": "module",
5
5
  "description": "SmoothBricks monorepo automation CLI",
6
6
  "bin": {
@@ -64,7 +64,7 @@
64
64
  ],
65
65
  "dependencies": {
66
66
  "@arethetypeswrong/core": "^0.18.2",
67
- "@smoothbricks/nx-plugin": "0.4.8",
67
+ "@smoothbricks/nx-plugin": "0.4.9",
68
68
  "@smoothbricks/validation": "0.1.8",
69
69
  "commander": "^14.0.3",
70
70
  "make-synchronized": "^0.8.0",
@@ -387,3 +387,71 @@ describe('resolveSecretEnvironment', () => {
387
387
  }
388
388
  });
389
389
  });
390
+
391
+ /**
392
+ * Redaction is a byte operation on output the caller already captured, so the
393
+ * harness carries both directions as base64: a text-only round trip would
394
+ * hide exactly the invalid-UTF-8 bytes a failing install can emit.
395
+ */
396
+ const MASK_SCRIPT = `
397
+ const resolver = await import(Bun.argv[1]);
398
+ const { output, values } = JSON.parse(Bun.argv[2]);
399
+ const captured = Buffer.from(output, 'base64');
400
+ const masked = resolver.maskSecretValues(captured, values);
401
+ process.stdout.write(JSON.stringify({
402
+ masked: Buffer.from(masked).toString('base64'),
403
+ captured: captured.toString('base64'),
404
+ }));
405
+ `;
406
+
407
+ describe('maskSecretValues', () => {
408
+ const mask = async (output: Uint8Array, values: readonly string[]): Promise<{ masked: Buffer; captured: Buffer }> => {
409
+ const { stdout, stderr, exitCode } = await runInBootstrap(
410
+ MASK_SCRIPT,
411
+ JSON.stringify({ output: Buffer.from(output).toString('base64'), values }),
412
+ );
413
+ expect(stderr).toBe('');
414
+ expect(exitCode).toBe(0);
415
+ const value: unknown = JSON.parse(stdout);
416
+ if (
417
+ typeof value !== 'object' ||
418
+ value === null ||
419
+ !('masked' in value) ||
420
+ typeof value.masked !== 'string' ||
421
+ !('captured' in value) ||
422
+ typeof value.captured !== 'string'
423
+ ) {
424
+ throw new Error(`bootstrap harness returned a malformed envelope: ${stdout}`);
425
+ }
426
+ return { masked: Buffer.from(value.masked, 'base64'), captured: Buffer.from(value.captured, 'base64') };
427
+ };
428
+
429
+ it('replaces every occurrence and leaves the surrounding failure legible', async () => {
430
+ const output = Buffer.from('error: 401 for https://x:hunter2@registry/pkg\nretrying with hunter2\n');
431
+ const { masked, captured } = await mask(output, ['hunter2']);
432
+ expect(masked.toString()).toBe('error: 401 for https://x:*******@registry/pkg\nretrying with *******\n');
433
+ expect(masked.length).toBe(output.length);
434
+ // The caller keeps its captured bytes: redaction returns a copy, and a
435
+ // node Buffer's `slice` would have handed back a view of these.
436
+ expect(captured.toString()).toBe(output.toString());
437
+ });
438
+
439
+ it('redacts each declared value even when one is a prefix of another', async () => {
440
+ expect((await mask(Buffer.from('AB ABCD AB'), ['ABCD', 'AB'])).masked.toString()).toBe('** **** **');
441
+ });
442
+
443
+ it('survives a false start on the first byte', async () => {
444
+ expect((await mask(Buffer.from('aab aaab'), ['aab'])).masked.toString()).toBe('*** a***');
445
+ });
446
+
447
+ it('masks whole bytes and carries invalid UTF-8 through untouched', async () => {
448
+ const output = Buffer.concat([Buffer.from([0xff, 0xfe]), Buffer.from('kå'), Buffer.from([0x00])]);
449
+ const { masked } = await mask(output, ['kå']);
450
+ expect([...masked]).toEqual([0xff, 0xfe, 0x2a, 0x2a, 0x2a, 0x00]);
451
+ });
452
+
453
+ it('leaves output alone when nothing is declared or a value is empty', async () => {
454
+ expect((await mask(Buffer.from('nothing to hide'), [])).masked.toString()).toBe('nothing to hide');
455
+ expect((await mask(Buffer.from('nothing to hide'), [''])).masked.toString()).toBe('nothing to hide');
456
+ });
457
+ });
@@ -1,6 +1,8 @@
1
1
  import { mkdtemp, rm, writeFile } from 'node:fs/promises';
2
+ import { createRequire } from 'node:module';
2
3
  import { tmpdir } from 'node:os';
3
4
  import { join } from 'node:path';
5
+ import { pathToFileURL } from 'node:url';
4
6
  import type { ChangelogOptions } from 'nx/src/command-line/release/command-object.js';
5
7
  import type { NxReleaseConfiguration } from 'nx/src/config/nx-json.js';
6
8
  import { type ReleasePackageInfo, releaseTag } from './core.js';
@@ -34,7 +36,7 @@ export interface GithubReleaseWriteShell {
34
36
 
35
37
  export async function renderNxProjectChangelogContents(input: RenderNxProjectChangelogInput): Promise<string> {
36
38
  return withNxWorkspaceRoot(input.root, async () => {
37
- const { createAPI } = await import('nx/src/command-line/release/changelog.js');
39
+ const { createAPI } = await importWorkspaceNx(input.root, 'src/command-line/release/changelog.js');
38
40
  const result = await createAPI(
39
41
  nxRenderOnlyReleaseConfig,
40
42
  false,
@@ -124,8 +126,35 @@ function isPrereleaseVersion(version: string): boolean {
124
126
  return version.includes('-');
125
127
  }
126
128
 
129
+ /**
130
+ * A module of the WORKSPACE's Nx, not of smoo's own dependency. smoo installs
131
+ * with its own `nx` in the global virtual store, where the workspace's
132
+ * node_modules is invisible: that Nx cannot resolve the workspace's
133
+ * `versionActions` plugin (`Unable to resolve the "versionActions"
134
+ * implementation ... "@smoothbricks/nx-plugin/version-actions"`), and it is
135
+ * a second Nx version driving one release. Every in-process Nx call for a
136
+ * workspace resolves from that workspace's root, exactly as `nx` on its PATH
137
+ * would.
138
+ */
139
+ interface WorkspaceNxModules {
140
+ 'src/utils/workspace-root.js': typeof import('nx/src/utils/workspace-root.js');
141
+ 'src/command-line/release/version.js': typeof import('nx/src/command-line/release/version.js');
142
+ 'src/command-line/release/changelog.js': typeof import('nx/src/command-line/release/changelog.js');
143
+ }
144
+
145
+ export async function importWorkspaceNx<Subpath extends keyof WorkspaceNxModules>(
146
+ root: string,
147
+ subpath: Subpath,
148
+ ): Promise<WorkspaceNxModules[Subpath]> {
149
+ const resolved = createRequire(join(root, 'package.json')).resolve(`nx/${subpath}`);
150
+ // A dynamic specifier types as `any`; the map above is the declared shape of
151
+ // the module the workspace's Nx serves at that subpath.
152
+ const module: WorkspaceNxModules[Subpath] = await import(pathToFileURL(resolved).href);
153
+ return module;
154
+ }
155
+
127
156
  export async function withNxWorkspaceRoot<T>(root: string, run: () => Promise<T>): Promise<T> {
128
- const workspaceRootModule = await import('nx/src/utils/workspace-root.js');
157
+ const workspaceRootModule = await importWorkspaceNx(root, 'src/utils/workspace-root.js');
129
158
  const previousWorkspaceRoot = workspaceRootModule.workspaceRoot;
130
159
  const previousEnvWorkspaceRoot = process.env.NX_WORKSPACE_ROOT_PATH;
131
160
  const previousCwd = process.cwd();
@@ -88,6 +88,7 @@ import {
88
88
  import {
89
89
  createOrUpdateGithubRelease,
90
90
  githubReleaseLookupExists,
91
+ importWorkspaceNx,
91
92
  renderNxProjectChangelogContents,
92
93
  withNxWorkspaceRoot,
93
94
  } from './github-release.js';
@@ -881,8 +882,8 @@ async function runNxReleaseVersion(
881
882
 
882
883
  async function runNxReleaseVersionPreview(root: string, projects: string, bump: string): Promise<ReleasePackage[]> {
883
884
  return withNxWorkspaceRoot(root, async () => {
884
- const { createAPI } = await import('nx/src/command-line/release/version.js');
885
- const { createAPI: createChangelogAPI } = await import('nx/src/command-line/release/changelog.js');
885
+ const { createAPI } = await importWorkspaceNx(root, 'src/command-line/release/version.js');
886
+ const { createAPI: createChangelogAPI } = await importWorkspaceNx(root, 'src/command-line/release/changelog.js');
886
887
  const projectNames = projects.split(',').filter(Boolean);
887
888
  const result = await createAPI(
888
889
  {},