@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.
- package/dist/release/github-release.d.ts +16 -0
- package/dist/release/github-release.d.ts.map +1 -1
- package/dist/release/github-release.js +11 -2
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +3 -3
- package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +35 -0
- package/managed/raw/tooling/direnv/secret-references.ts +66 -0
- package/managed/raw/tooling/direnv/setup-environment.ts +81 -26
- package/managed/templates/github/actions/setup-devenv/action.yml +4 -2
- package/package.json +2 -2
- package/src/monorepo/secret-references.test.ts +68 -0
- package/src/release/github-release.ts +31 -2
- package/src/release/index.ts +3 -2
|
@@ -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":"
|
|
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
|
|
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
|
|
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":"AA8BA,OAAO,EACL,oBAAoB,EACpB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,WAAW,EACX,kBAAkB,GACnB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,gBAAgB,IAAI,0BAA0B,EAC9C,yBAAyB,IAAI,mCAAmC,EAChE,KAAK,kBAAkB,EACvB,KAAK,6BAA6B,EAClC,KAAK,iCAAiC,EACtC,KAAK,kBAAkB,EACvB,qBAAqB,EACrB,2BAA2B,EAC3B,6BAA6B,EAC7B,KAAK,MAAM,IAAI,gBAAgB,EAC/B,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EACL,KAAK,2BAA2B,EAIjC,MAAM,6BAA6B,CAAC;AAQrC,OAAO,EAQL,KAAK,kBAAkB,EAGxB,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/release/index.ts"],"names":[],"mappings":"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"}
|
package/dist/release/index.js
CHANGED
|
@@ -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
|
|
524
|
-
const { createAPI: createChangelogAPI } = await
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(`---
|
|
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(`---
|
|
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
|
-
|
|
393
|
-
|
|
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 (
|
|
396
|
-
process.stderr.write(
|
|
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-
|
|
204
|
-
restore-prefixes-first-match: ${{ runner.os }}-${{ runner.arch }}-nix-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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();
|
package/src/release/index.ts
CHANGED
|
@@ -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
|
|
885
|
-
const { createAPI: createChangelogAPI } = await
|
|
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
|
{},
|