@smoothbricks/cli 0.11.13 → 0.11.14
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/cli.js +6 -1
- package/dist/monorepo/index.d.ts +2 -0
- package/dist/monorepo/index.d.ts.map +1 -1
- package/dist/monorepo/index.js +1 -1
- package/dist/monorepo/packed-package.d.ts +3 -3
- package/dist/monorepo/packed-package.d.ts.map +1 -1
- package/dist/monorepo/packed-package.js +11 -6
- package/dist/monorepo/packs/index.d.ts +8 -0
- package/dist/monorepo/packs/index.d.ts.map +1 -1
- package/dist/monorepo/packs/index.js +6 -5
- package/dist/monorepo/publish-workflow.js +5 -1
- package/managed/raw/tooling/direnv/devenv.smoo.nix +17 -13
- package/managed/raw/tooling/direnv/secret-references.ts +13 -125
- package/managed/templates/github/actions/setup-devenv/action.yml +15 -4
- package/package.json +2 -2
- package/src/cli.ts +10 -1
- package/src/monorepo/__tests__/publish-workflow.test.ts +3 -0
- package/src/monorepo/index.ts +6 -1
- package/src/monorepo/packed-package.ts +21 -6
- package/src/monorepo/packs/index.ts +14 -5
- package/src/monorepo/publish-workflow.ts +8 -1
- package/src/monorepo/secret-references.test.ts +0 -147
package/dist/cli.js
CHANGED
|
@@ -84,9 +84,14 @@ function buildProgram() {
|
|
|
84
84
|
.option('--fail-fast', 'stop after the first failing validation pack')
|
|
85
85
|
.option('--only-if-new-workspace-package', 'skip validation unless a new workspace package manifest is staged')
|
|
86
86
|
.option('--verbose', 'print validation progress and successful checks')
|
|
87
|
+
.option('--projects <names>', 'comma-separated Nx project names to build and pack-validate (a release selection); default: every project')
|
|
87
88
|
.action(async (options) => {
|
|
88
89
|
const { validateMonorepo } = await import('./monorepo/index.js');
|
|
89
|
-
|
|
90
|
+
const projects = options.projects
|
|
91
|
+
?.split(',')
|
|
92
|
+
.map((name) => name.trim())
|
|
93
|
+
.filter((name) => name.length > 0);
|
|
94
|
+
await validateMonorepo(await findRepoRoot(), { ...options, projects });
|
|
90
95
|
});
|
|
91
96
|
monorepo.command('update').action(async () => {
|
|
92
97
|
const { updateManagedFiles } = await import('./monorepo/index.js');
|
package/dist/monorepo/index.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface ValidateOptions {
|
|
|
9
9
|
onlyIfNewWorkspacePackage?: boolean;
|
|
10
10
|
fix?: boolean;
|
|
11
11
|
verbose?: boolean;
|
|
12
|
+
/** Nx project names to build and pack-validate; see MonorepoContext.projects. */
|
|
13
|
+
projects?: readonly string[];
|
|
12
14
|
}
|
|
13
15
|
export interface ValidateCommitMessageOptions {
|
|
14
16
|
fix?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/monorepo/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AA2BxD,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/monorepo/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AA2BxD,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,4BAA4B;IAC3C,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB;IACtC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAQpF;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBjG;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBpE;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBrG;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAElE;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,GAAE,4BAAiC,EAC1C,IAAI,SAAgB,GACnB,IAAI,CAoBN;AAED,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,0BAA+B,GAAG,MAAM,CAW3G;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAI5D;AAED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,uBAAuB,EAChC,KAAK,GAAE,qBAAoD,GAC1D,OAAO,CAAC,IAAI,CAAC,CA8Bf;AAED,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,CAAC"}
|
package/dist/monorepo/index.js
CHANGED
|
@@ -24,7 +24,7 @@ export async function validateMonorepo(root, options = {}) {
|
|
|
24
24
|
if (options.onlyIfNewWorkspacePackage && !(await hasNewWorkspacePackage(root))) {
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
|
-
const result = await runValidatePacks({ root, syncRuntime: false, verbose: options.verbose === true }, options);
|
|
27
|
+
const result = await runValidatePacks({ root, syncRuntime: false, verbose: options.verbose === true, projects: options.projects }, options);
|
|
28
28
|
if (result.failures > 0) {
|
|
29
29
|
const checkNoun = result.failedChecks === 1 ? 'check' : 'checks';
|
|
30
30
|
const problemNoun = result.failures === 1 ? 'problem' : 'problems';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare function validatePackedPublishablePackages(root: string): Promise<number>;
|
|
2
|
-
export declare function validatePackedPublishablePackagePublint(root: string): Promise<number>;
|
|
3
|
-
export declare function validatePackedPublishablePackageManifest(root: string): Promise<number>;
|
|
4
|
-
export declare function validatePackedPublishablePackageTypes(root: string): Promise<number>;
|
|
2
|
+
export declare function validatePackedPublishablePackagePublint(root: string, projects?: readonly string[]): Promise<number>;
|
|
3
|
+
export declare function validatePackedPublishablePackageManifest(root: string, projects?: readonly string[]): Promise<number>;
|
|
4
|
+
export declare function validatePackedPublishablePackageTypes(root: string, projects?: readonly string[]): Promise<number>;
|
|
5
5
|
//# sourceMappingURL=packed-package.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packed-package.d.ts","sourceRoot":"","sources":["../../src/monorepo/packed-package.ts"],"names":[],"mappings":"AAeA,wBAAsB,iCAAiC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMrF;
|
|
1
|
+
{"version":3,"file":"packed-package.d.ts","sourceRoot":"","sources":["../../src/monorepo/packed-package.ts"],"names":[],"mappings":"AAeA,wBAAsB,iCAAiC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMrF;AAQD,wBAAsB,uCAAuC,CAC3D,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,GAC3B,OAAO,CAAC,MAAM,CAAC,CAMjB;AAED,wBAAsB,wCAAwC,CAC5D,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,GAC3B,OAAO,CAAC,MAAM,CAAC,CAMjB;AAED,wBAAsB,qCAAqC,CACzD,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,GAC3B,OAAO,CAAC,MAAM,CAAC,CAMjB"}
|
|
@@ -15,23 +15,28 @@ export async function validatePackedPublishablePackages(root) {
|
|
|
15
15
|
(await validatePackedPublishablePackageManifest(root)) +
|
|
16
16
|
(await validatePackedPublishablePackageTypes(root)));
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
/** The publishable packages, narrowed to `projects` (Nx names) when given. */
|
|
19
|
+
function selectedPublishablePackages(root, projects) {
|
|
20
|
+
const all = listPublishablePackages(root);
|
|
21
|
+
return projects?.length ? all.filter((pkg) => projects.includes(pkg.projectName)) : all;
|
|
22
|
+
}
|
|
23
|
+
export async function validatePackedPublishablePackagePublint(root, projects) {
|
|
19
24
|
let failures = 0;
|
|
20
|
-
for (const pkg of
|
|
25
|
+
for (const pkg of selectedPublishablePackages(root, projects)) {
|
|
21
26
|
failures += await validatePackedPublishablePackageTool(root, pkg, validatePublint);
|
|
22
27
|
}
|
|
23
28
|
return failures;
|
|
24
29
|
}
|
|
25
|
-
export async function validatePackedPublishablePackageManifest(root) {
|
|
30
|
+
export async function validatePackedPublishablePackageManifest(root, projects) {
|
|
26
31
|
let failures = 0;
|
|
27
|
-
for (const pkg of
|
|
32
|
+
for (const pkg of selectedPublishablePackages(root, projects)) {
|
|
28
33
|
failures += await validatePackedPublishablePackageTool(root, pkg, validatePackedManifest);
|
|
29
34
|
}
|
|
30
35
|
return failures;
|
|
31
36
|
}
|
|
32
|
-
export async function validatePackedPublishablePackageTypes(root) {
|
|
37
|
+
export async function validatePackedPublishablePackageTypes(root, projects) {
|
|
33
38
|
let failures = 0;
|
|
34
|
-
for (const pkg of
|
|
39
|
+
for (const pkg of selectedPublishablePackages(root, projects)) {
|
|
35
40
|
failures += await validatePackedPublishablePackageTool(root, pkg, validateAttw);
|
|
36
41
|
}
|
|
37
42
|
return failures;
|
|
@@ -4,6 +4,14 @@ export interface MonorepoContext {
|
|
|
4
4
|
root: string;
|
|
5
5
|
syncRuntime: boolean;
|
|
6
6
|
verbose?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Nx project names the validation is about, when a caller knows: the
|
|
9
|
+
* release's candidates. The build phase and the packed-package packs scope
|
|
10
|
+
* to them; manifest-level packs still see the whole repository, because a
|
|
11
|
+
* package's hygiene does not depend on which packages ship today. Absent,
|
|
12
|
+
* everything is validated and everything is built.
|
|
13
|
+
*/
|
|
14
|
+
projects?: readonly string[];
|
|
7
15
|
}
|
|
8
16
|
export interface ValidatePackOptions {
|
|
9
17
|
failFast?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/monorepo/packs/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,cAAc,EAAsB,MAAM,mBAAmB,CAAC;AAO5E,OAAO,EAKL,KAAK,sBAAsB,EAS5B,MAAM,sBAAsB,CAAC;AAU9B,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/monorepo/packs/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,cAAc,EAAsB,MAAM,mBAAmB,CAAC;AAO5E,OAAO,EAKL,KAAK,sBAAsB,EAS5B,MAAM,sBAAsB,CAAC;AAU9B,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClD,WAAW,CAAC,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD,YAAY,CAAC,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1D,gBAAgB,CAAC,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IAClE,iBAAiB,CAAC,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;CACpE;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAChC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,mBAAmB,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;CAC9F;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AA8ID,0DAA0D;AAC1D,eAAO,MAAM,YAAY,gBAAQ,CAAC;AAElC,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAItE;AAED,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,eAAe,EACpB,OAAO,GAAE,mBAAwB,EACjC,KAAK,GAAE,oBAAyB,GAC/B,OAAO,CAAC,kBAAkB,CAAC,CA0B7B;AAwMD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAexG"}
|
|
@@ -118,19 +118,19 @@ const packs = [
|
|
|
118
118
|
{
|
|
119
119
|
name: 'packed-package-publint',
|
|
120
120
|
validatePostBuild(ctx) {
|
|
121
|
-
return validatePackedPublishablePackagePublint(ctx.root);
|
|
121
|
+
return validatePackedPublishablePackagePublint(ctx.root, ctx.projects);
|
|
122
122
|
},
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
name: 'packed-package-manifest',
|
|
126
126
|
validatePostBuild(ctx) {
|
|
127
|
-
return validatePackedPublishablePackageManifest(ctx.root);
|
|
127
|
+
return validatePackedPublishablePackageManifest(ctx.root, ctx.projects);
|
|
128
128
|
},
|
|
129
129
|
},
|
|
130
130
|
{
|
|
131
131
|
name: 'packed-package-types',
|
|
132
132
|
validatePostBuild(ctx) {
|
|
133
|
-
return validatePackedPublishablePackageTypes(ctx.root);
|
|
133
|
+
return validatePackedPublishablePackageTypes(ctx.root, ctx.projects);
|
|
134
134
|
},
|
|
135
135
|
},
|
|
136
136
|
{
|
|
@@ -272,9 +272,10 @@ async function runBuild(ctx, options = {}) {
|
|
|
272
272
|
if (options.verbose) {
|
|
273
273
|
printCheckHeading('build', true);
|
|
274
274
|
}
|
|
275
|
+
const args = ['run-many', '-t', 'build', ...(ctx.projects?.length ? ['-p', ctx.projects.join(',')] : [])];
|
|
275
276
|
const result = options.verbose
|
|
276
|
-
? { exitCode: await runStatus('nx',
|
|
277
|
-
: await runResult('nx',
|
|
277
|
+
? { exitCode: await runStatus('nx', args, ctx.root, false), stdout: '', stderr: '' }
|
|
278
|
+
: await runResult('nx', args, ctx.root);
|
|
278
279
|
const status = result.exitCode;
|
|
279
280
|
if (status !== 0) {
|
|
280
281
|
if (!options.verbose) {
|
|
@@ -441,7 +441,11 @@ function yamlLinesForStep(step, options) {
|
|
|
441
441
|
'include-hidden-files: true',
|
|
442
442
|
], 'failure()');
|
|
443
443
|
case PublishWorkflowStepKind.ValidateMonorepoConfig:
|
|
444
|
-
|
|
444
|
+
// Scoped to the release's candidates: the build phase and the packed-package
|
|
445
|
+
// checks cover what ships. Unscoped, validate compiled every project - on a
|
|
446
|
+
// release touching no Rust that was 3m30 of the cowshed CLI on the critical
|
|
447
|
+
// path, for a binary the release did not contain.
|
|
448
|
+
return conditionalRunStep(step, `smoo monorepo validate --projects "${githubExpression('steps.version.outputs.projects')}"`);
|
|
445
449
|
case PublishWorkflowStepKind.TagRelease:
|
|
446
450
|
return tagReleaseStepLines(step.name);
|
|
447
451
|
case PublishWorkflowStepKind.PublishRelease:
|
|
@@ -274,16 +274,16 @@
|
|
|
274
274
|
# 5. The shared setup-environment.ts bootstraps repository dependencies; a
|
|
275
275
|
# failure aborts shell entry instead of yielding a half-working shell.
|
|
276
276
|
# Repo-owned enterShell bodies merge after this prologue.
|
|
277
|
-
# 6. The declared Nx remote cache (`smoo.remoteCache`)
|
|
278
|
-
#
|
|
279
|
-
#
|
|
280
|
-
#
|
|
281
|
-
#
|
|
282
|
-
#
|
|
283
|
-
#
|
|
284
|
-
#
|
|
285
|
-
#
|
|
286
|
-
#
|
|
277
|
+
# 6. The declared Nx remote cache (`smoo.remoteCache`) is NOT resolved
|
|
278
|
+
# here. Shell entry happens on every direnv reload and every
|
|
279
|
+
# `devenv shell -- <command>`, and a provider command that runs then is a
|
|
280
|
+
# credential prompt on every one of them (1Password authorises per
|
|
281
|
+
# requesting process lineage, and this one is new each time). Nx reads
|
|
282
|
+
# NX_SELF_HOSTED_REMOTE_CACHE_SERVER and _ACCESS_TOKEN from the
|
|
283
|
+
# environment when it runs: CI injects them into the job, a developer
|
|
284
|
+
# exports the token once in the terminal that wants the cache (for
|
|
285
|
+
# example `op signin`, then the declared command), and every nested
|
|
286
|
+
# shell inherits it. Absent, Nx runs with the local cache only.
|
|
287
287
|
# 7. GOROOT is unset rather than set. With devenv's Go pinned to the patch
|
|
288
288
|
# release ttsc vendors, a GOROOT crossing cannot misfire on version at all,
|
|
289
289
|
# so this is belt-and-braces rather than the fix — it keeps the isolation
|
|
@@ -322,7 +322,12 @@
|
|
|
322
322
|
# swaps the SDK a build was compiled against.
|
|
323
323
|
# Nx's fallback includes HOME or TMPDIR, either of which can exceed the
|
|
324
324
|
# Unix socket limit in a checkout. Reuse devenv's short runtime directory,
|
|
325
|
-
#
|
|
325
|
+
# which is keyed on this checkout's devenv root and so is one per
|
|
326
|
+
# workspace. Unconditionally: an inherited value is another workspace's
|
|
327
|
+
# shell (a shed entered from the host, a second repository from the first,
|
|
328
|
+
# a subprocess of either), and one socket dir for two workspaces makes the
|
|
329
|
+
# daemon refuse whichever came second ("received a message from a
|
|
330
|
+
# different workspace"). Nobody supplies this deliberately.
|
|
326
331
|
(lib.mkBefore ''
|
|
327
332
|
cd "$DEVENV_ROOT/../.."
|
|
328
333
|
export PATH="$("$PWD/tooling/direnv/repo-path")"
|
|
@@ -349,8 +354,7 @@
|
|
|
349
354
|
export GOFLAGS="''${GOFLAGS:--trimpath}"
|
|
350
355
|
unset GOROOT
|
|
351
356
|
bun "$DEVENV_ROOT/setup-environment.ts" || exit $?
|
|
352
|
-
|
|
353
|
-
export NX_SOCKET_DIR="''${NX_SOCKET_DIR:-$DEVENV_RUNTIME/nx}"
|
|
357
|
+
export NX_SOCKET_DIR="$DEVENV_RUNTIME/nx"
|
|
354
358
|
mkdir -p "$NX_SOCKET_DIR"
|
|
355
359
|
${lib.optionalString pkgs.stdenv.isDarwin ''
|
|
356
360
|
unset CC CXX
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Provider-neutral local secret resolution for smoo-managed repositories
|
|
3
|
-
* the developer-shell half of the declared Nx remote cache.
|
|
2
|
+
* Provider-neutral local secret resolution for smoo-managed repositories.
|
|
4
3
|
*
|
|
5
4
|
* Reads the root package.json `smoo.secrets` map and `smoo.remoteCache` block
|
|
6
5
|
* — the bootstrap twins of `PackageSecretCommand`, `PackageSmooConfig.secrets`
|
|
7
6
|
* and `PackageRemoteCacheConfig` in packages/cli/src/lib/json.ts. This file is
|
|
8
7
|
* a managed raw script: it runs from `tooling/direnv/setup-environment.ts`
|
|
9
|
-
* BEFORE `bun install`,
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* anything this file would reject at runtime.
|
|
8
|
+
* BEFORE `bun install`, when no workspace package and no Typia transform exist
|
|
9
|
+
* yet, so both shapes are hand-validated here against exactly what json.ts
|
|
10
|
+
* declares. Keep them aligned; smoo's Typia validation fails the manifest at
|
|
11
|
+
* generation time for anything this file would reject at runtime.
|
|
14
12
|
*
|
|
15
13
|
* Routing per declared variable, in first-match order:
|
|
16
14
|
*
|
|
@@ -29,22 +27,14 @@
|
|
|
29
27
|
* Error text names variables and exit codes only: secret values, provider
|
|
30
28
|
* stdout/stderr, and command arguments are never echoed.
|
|
31
29
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* a
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* runs in the developer's shell, not in the setup child, and the export is
|
|
41
|
-
* limited to those two variables so `smoo.secrets` stays process-local. It
|
|
42
|
-
* prints nothing when the repository declares no cache, when the shell already
|
|
43
|
-
* carries a server (a CI job env is never overwritten), or when the declared
|
|
44
|
-
* token has no value — Nx accepts only 200 or 404 from a cache server, so a
|
|
45
|
-
* server it cannot authenticate to fails every task instead of missing. Why
|
|
46
|
-
* the cache is off is said on stderr; the exit status stays 0, because a
|
|
47
|
-
* missing cache credential must never keep a shell from opening.
|
|
30
|
+
* The variable `smoo.remoteCache.tokenSecret` names is never resolved here,
|
|
31
|
+
* nor anywhere at shell entry. A remote cache is an optimization, and shell
|
|
32
|
+
* entry happens on every direnv reload and every `devenv shell -- <command>`:
|
|
33
|
+
* a provider command run there is a credential prompt on each of them. Nx
|
|
34
|
+
* reads NX_SELF_HOSTED_REMOTE_CACHE_SERVER and _ACCESS_TOKEN from the
|
|
35
|
+
* environment it runs in — CI injects them, a developer exports the token
|
|
36
|
+
* once in the terminal that wants the cache — and every nested shell inherits
|
|
37
|
+
* them. Absent, Nx keeps to its local cache.
|
|
48
38
|
*/
|
|
49
39
|
import { existsSync, readFileSync } from 'node:fs';
|
|
50
40
|
import { join } from 'node:path';
|
|
@@ -135,10 +125,6 @@ function parseSecretSpec(name: string, spec: unknown): SecretSpec {
|
|
|
135
125
|
return { command: [first, ...rest] };
|
|
136
126
|
}
|
|
137
127
|
|
|
138
|
-
/** The two variables Nx reads for its self-hosted HTTP cache. */
|
|
139
|
-
const REMOTE_CACHE_SERVER = 'NX_SELF_HOSTED_REMOTE_CACHE_SERVER';
|
|
140
|
-
const REMOTE_CACHE_ACCESS_TOKEN = 'NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN';
|
|
141
|
-
|
|
142
128
|
/**
|
|
143
129
|
* The declared remote cache as a local shell needs it. `internalServer` is
|
|
144
130
|
* deliberately absent: it is the address a managed runner reaches inside its
|
|
@@ -353,85 +339,6 @@ export async function resolveSecretEnvironment(
|
|
|
353
339
|
});
|
|
354
340
|
}
|
|
355
341
|
|
|
356
|
-
/**
|
|
357
|
-
* What the shell should do about the declared remote cache. Each case is a
|
|
358
|
-
* value, not an exception: three of the four are ordinary, and only one is
|
|
359
|
-
* worth saying out loud.
|
|
360
|
-
*/
|
|
361
|
-
export type RemoteCacheOutcome =
|
|
362
|
-
| { readonly kind: 'undeclared' }
|
|
363
|
-
| { readonly kind: 'inherited' }
|
|
364
|
-
| { readonly kind: 'unavailable'; readonly reason: string }
|
|
365
|
-
| { readonly kind: 'exported'; readonly env: Readonly<Record<string, string>> };
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* The remote cache variables this shell should export, or why it exports none.
|
|
369
|
-
* An inherited server always wins, so a CI job env — which carries the address
|
|
370
|
-
* its own runners reach — is never overwritten by the public one. The token is
|
|
371
|
-
* taken from the environment when it is there, and otherwise from the one
|
|
372
|
-
* `smoo.secrets` entry that declares it — resolving that single variable, so
|
|
373
|
-
* no other declared secret ever reaches the shell. A repository that declares
|
|
374
|
-
* the cache token as a provider secret therefore pays that one command twice
|
|
375
|
-
* per shell entry, once here and once in the setup child's own aggregate
|
|
376
|
-
* resolution; an ambient token pays nothing.
|
|
377
|
-
*/
|
|
378
|
-
export async function resolveRemoteCacheOutcome(options: SecretResolutionOptions): Promise<RemoteCacheOutcome> {
|
|
379
|
-
const env = options.env ?? process.env;
|
|
380
|
-
const packageJson = readPackageJson(options.root);
|
|
381
|
-
const spec = parseSmooRemoteCache(packageJson);
|
|
382
|
-
if (spec === null) {
|
|
383
|
-
return { kind: 'undeclared' };
|
|
384
|
-
}
|
|
385
|
-
if (isNonemptyEnvValue(env[REMOTE_CACHE_SERVER])) {
|
|
386
|
-
return { kind: 'inherited' };
|
|
387
|
-
}
|
|
388
|
-
const ambient = env[spec.tokenSecret];
|
|
389
|
-
if (isNonemptyEnvValue(ambient)) {
|
|
390
|
-
return {
|
|
391
|
-
kind: 'exported',
|
|
392
|
-
env: { [REMOTE_CACHE_SERVER]: spec.server, [REMOTE_CACHE_ACCESS_TOKEN]: ambient },
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
const declared = parseSmooSecrets(packageJson)[spec.tokenSecret];
|
|
396
|
-
if (declared === undefined) {
|
|
397
|
-
return {
|
|
398
|
-
kind: 'unavailable',
|
|
399
|
-
reason: `${spec.tokenSecret} is unset and no smoo.secrets entry declares it, so ${spec.server} would be asked for cache entries with no credential`,
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
// A cache token is not registry routing, so the cowshed `.npmrc` exclusion
|
|
403
|
-
// cannot apply to it; every other rule in the header does.
|
|
404
|
-
const outcome = await routeSecret(spec.tokenSecret, declared, {
|
|
405
|
-
env,
|
|
406
|
-
registryIntentEnvs: new Set(),
|
|
407
|
-
run: options.runCommand ?? runSecretCommand,
|
|
408
|
-
});
|
|
409
|
-
switch (outcome.kind) {
|
|
410
|
-
case 'resolved':
|
|
411
|
-
return {
|
|
412
|
-
kind: 'exported',
|
|
413
|
-
env: { [REMOTE_CACHE_SERVER]: spec.server, [REMOTE_CACHE_ACCESS_TOKEN]: outcome.value },
|
|
414
|
-
};
|
|
415
|
-
case 'failed':
|
|
416
|
-
return { kind: 'unavailable', reason: `${spec.tokenSecret}: ${outcome.guidance}` };
|
|
417
|
-
case 'env-wins':
|
|
418
|
-
// Only reachable if an ambient value appeared between the check above
|
|
419
|
-
// and this call; the export happens on the next shell entry.
|
|
420
|
-
return { kind: 'unavailable', reason: `${spec.tokenSecret} was set after it was read` };
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* POSIX `export` lines for a shell to `eval`. Single quotes are the only
|
|
426
|
-
* quoting a value cannot escape from, so each value is single-quoted with its
|
|
427
|
-
* own quotes spliced out — a token is opaque bytes, never assumed shell-safe.
|
|
428
|
-
*/
|
|
429
|
-
export function shellExportLines(env: Readonly<Record<string, string>>): string {
|
|
430
|
-
return Object.entries(env)
|
|
431
|
-
.map(([name, value]) => `export ${name}='${value.replaceAll("'", "'\\''")}'\n`)
|
|
432
|
-
.join('');
|
|
433
|
-
}
|
|
434
|
-
|
|
435
342
|
function readPackageJson(root: string): unknown {
|
|
436
343
|
const packageJsonPath = join(root, 'package.json');
|
|
437
344
|
let text: string;
|
|
@@ -452,27 +359,8 @@ function readPackageJson(root: string): unknown {
|
|
|
452
359
|
}
|
|
453
360
|
return parsed;
|
|
454
361
|
}
|
|
455
|
-
|
|
456
362
|
function readNpmrcText(root: string): string | null {
|
|
457
363
|
const npmrcPath = join(root, '.npmrc');
|
|
458
364
|
if (!existsSync(npmrcPath)) return null;
|
|
459
365
|
return readFileSync(npmrcPath, 'utf8');
|
|
460
366
|
}
|
|
461
|
-
|
|
462
|
-
// Program mode, run by the managed devenv shell as
|
|
463
|
-
// `eval "$(bun "$DEVENV_ROOT/secret-references.ts" "$PWD")"`. stdout is
|
|
464
|
-
// therefore shell text and carries nothing else; everything a human should
|
|
465
|
-
// read goes to stderr, and the status stays 0 so a shell always opens.
|
|
466
|
-
if (import.meta.main) {
|
|
467
|
-
const root = Bun.argv[2] ?? process.cwd();
|
|
468
|
-
try {
|
|
469
|
-
const outcome = await resolveRemoteCacheOutcome({ root });
|
|
470
|
-
if (outcome.kind === 'exported') {
|
|
471
|
-
process.stdout.write(shellExportLines(outcome.env));
|
|
472
|
-
} else if (outcome.kind === 'unavailable') {
|
|
473
|
-
console.error(`smoo: Nx remote cache off — ${outcome.reason}`);
|
|
474
|
-
}
|
|
475
|
-
} catch (error) {
|
|
476
|
-
console.error(`smoo: Nx remote cache off — ${error instanceof Error ? error.message : String(error)}`);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
@@ -109,6 +109,11 @@ runs:
|
|
|
109
109
|
# stable path (a symlink would not do: Nix canonicalises) and devenv is
|
|
110
110
|
# evaluated from there; every later step still runs in the checkout, which
|
|
111
111
|
# is the same tree. DEVENV_WORKDIR names the path the devenv steps use.
|
|
112
|
+
# The mount target is the instance's own /work, not the shared bind: only
|
|
113
|
+
# the path's spelling must be stable, and a bind onto the idmapped cache
|
|
114
|
+
# dataset failed inside the job (move_mount: ENOENT) where a container-fs
|
|
115
|
+
# target does not. If the mount is refused the job goes on from the
|
|
116
|
+
# checkout and pays the evaluation: this is an optimisation, never a gate.
|
|
112
117
|
- name: 📂 Locate devenv state on the shared bind
|
|
113
118
|
if: steps.runner-kind.outputs.host == 'true'
|
|
114
119
|
shell: bash
|
|
@@ -119,13 +124,19 @@ runs:
|
|
|
119
124
|
run: |
|
|
120
125
|
lane=$(printf '%s' "$DEVENV_STATE_LANE" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9._-' '-')
|
|
121
126
|
root="/var/cache/ci/devenv/$DEVENV_STATE_REPO/${lane:-default}/$DEVENV_STATE_JOB"
|
|
122
|
-
mkdir -p "$root/.devenv" "$root/.direnv"
|
|
123
|
-
sudo mount --bind "$GITHUB_WORKSPACE" "$root/workspace"
|
|
127
|
+
mkdir -p "$root/.devenv" "$root/.direnv"
|
|
124
128
|
rm -rf tooling/direnv/.devenv tooling/direnv/.direnv
|
|
125
129
|
ln -s "$root/.devenv" tooling/direnv/.devenv
|
|
126
130
|
ln -s "$root/.direnv" tooling/direnv/.direnv
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
stable="/work/$DEVENV_STATE_REPO/${lane:-default}/$DEVENV_STATE_JOB"
|
|
132
|
+
if mkdir -p "$stable" && sudo mount --bind "$GITHUB_WORKSPACE" "$stable"; then
|
|
133
|
+
echo "DEVENV_WORKDIR=$stable" >> "$GITHUB_ENV"
|
|
134
|
+
echo "devenv state: $root (workspace bind-mounted at $stable)"
|
|
135
|
+
else
|
|
136
|
+
echo "DEVENV_WORKDIR=$GITHUB_WORKSPACE" >> "$GITHUB_ENV"
|
|
137
|
+
echo "::warning::stable devenv path unavailable (bind mount refused); evaluating from the checkout"
|
|
138
|
+
echo "devenv state: $root (workspace at $GITHUB_WORKSPACE)"
|
|
139
|
+
fi
|
|
129
140
|
|
|
130
141
|
- name: 📂 Devenv runs in the checkout
|
|
131
142
|
if: steps.runner-kind.outputs.host != 'true'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smoothbricks/cli",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.14",
|
|
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.8",
|
|
68
68
|
"@smoothbricks/validation": "0.1.8",
|
|
69
69
|
"commander": "^14.0.3",
|
|
70
70
|
"make-synchronized": "^0.8.0",
|
package/src/cli.ts
CHANGED
|
@@ -89,15 +89,24 @@ function buildProgram(): Command {
|
|
|
89
89
|
.option('--fail-fast', 'stop after the first failing validation pack')
|
|
90
90
|
.option('--only-if-new-workspace-package', 'skip validation unless a new workspace package manifest is staged')
|
|
91
91
|
.option('--verbose', 'print validation progress and successful checks')
|
|
92
|
+
.option(
|
|
93
|
+
'--projects <names>',
|
|
94
|
+
'comma-separated Nx project names to build and pack-validate (a release selection); default: every project',
|
|
95
|
+
)
|
|
92
96
|
.action(
|
|
93
97
|
async (options: {
|
|
94
98
|
fix?: boolean;
|
|
95
99
|
failFast?: boolean;
|
|
96
100
|
onlyIfNewWorkspacePackage?: boolean;
|
|
97
101
|
verbose?: boolean;
|
|
102
|
+
projects?: string;
|
|
98
103
|
}) => {
|
|
99
104
|
const { validateMonorepo } = await import('./monorepo/index.js');
|
|
100
|
-
|
|
105
|
+
const projects = options.projects
|
|
106
|
+
?.split(',')
|
|
107
|
+
.map((name) => name.trim())
|
|
108
|
+
.filter((name) => name.length > 0);
|
|
109
|
+
await validateMonorepo(await findRepoRoot(), { ...options, projects });
|
|
101
110
|
},
|
|
102
111
|
);
|
|
103
112
|
monorepo.command('update').action(async () => {
|
|
@@ -38,6 +38,9 @@ const codebaseWorkflowOptions: PublishWorkflowDefinitionOptions = {
|
|
|
38
38
|
describe('publish workflow definition', () => {
|
|
39
39
|
it('renders the checked-in local publish workflow copy', async () => {
|
|
40
40
|
const rendered = renderPublishWorkflowYaml(codebaseWorkflowOptions);
|
|
41
|
+
// validate builds and pack-checks the release's candidates, not every project:
|
|
42
|
+
// unscoped it compiled binaries the release did not contain.
|
|
43
|
+
expect(rendered).toContain('smoo monorepo validate --projects "${{ steps.version.outputs.projects }}"');
|
|
41
44
|
const packageRoot = join(import.meta.dir, '..', '..', '..');
|
|
42
45
|
await expect(readFile(join(packageRoot, '..', '..', '.github/workflows/publish.yml'), 'utf8')).resolves.toBe(
|
|
43
46
|
rendered,
|
package/src/monorepo/index.ts
CHANGED
|
@@ -47,6 +47,8 @@ export interface ValidateOptions {
|
|
|
47
47
|
onlyIfNewWorkspacePackage?: boolean;
|
|
48
48
|
fix?: boolean;
|
|
49
49
|
verbose?: boolean;
|
|
50
|
+
/** Nx project names to build and pack-validate; see MonorepoContext.projects. */
|
|
51
|
+
projects?: readonly string[];
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
export interface ValidateCommitMessageOptions {
|
|
@@ -85,7 +87,10 @@ export async function validateMonorepo(root: string, options: ValidateOptions =
|
|
|
85
87
|
if (options.onlyIfNewWorkspacePackage && !(await hasNewWorkspacePackage(root))) {
|
|
86
88
|
return;
|
|
87
89
|
}
|
|
88
|
-
const result = await runValidatePacks(
|
|
90
|
+
const result = await runValidatePacks(
|
|
91
|
+
{ root, syncRuntime: false, verbose: options.verbose === true, projects: options.projects },
|
|
92
|
+
options,
|
|
93
|
+
);
|
|
89
94
|
if (result.failures > 0) {
|
|
90
95
|
const checkNoun = result.failedChecks === 1 ? 'check' : 'checks';
|
|
91
96
|
const problemNoun = result.failures === 1 ? 'problem' : 'problems';
|
|
@@ -21,25 +21,40 @@ export async function validatePackedPublishablePackages(root: string): Promise<n
|
|
|
21
21
|
);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
/** The publishable packages, narrowed to `projects` (Nx names) when given. */
|
|
25
|
+
function selectedPublishablePackages(root: string, projects?: readonly string[]): PackageInfo[] {
|
|
26
|
+
const all = listPublishablePackages(root);
|
|
27
|
+
return projects?.length ? all.filter((pkg) => projects.includes(pkg.projectName)) : all;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function validatePackedPublishablePackagePublint(
|
|
31
|
+
root: string,
|
|
32
|
+
projects?: readonly string[],
|
|
33
|
+
): Promise<number> {
|
|
25
34
|
let failures = 0;
|
|
26
|
-
for (const pkg of
|
|
35
|
+
for (const pkg of selectedPublishablePackages(root, projects)) {
|
|
27
36
|
failures += await validatePackedPublishablePackageTool(root, pkg, validatePublint);
|
|
28
37
|
}
|
|
29
38
|
return failures;
|
|
30
39
|
}
|
|
31
40
|
|
|
32
|
-
export async function validatePackedPublishablePackageManifest(
|
|
41
|
+
export async function validatePackedPublishablePackageManifest(
|
|
42
|
+
root: string,
|
|
43
|
+
projects?: readonly string[],
|
|
44
|
+
): Promise<number> {
|
|
33
45
|
let failures = 0;
|
|
34
|
-
for (const pkg of
|
|
46
|
+
for (const pkg of selectedPublishablePackages(root, projects)) {
|
|
35
47
|
failures += await validatePackedPublishablePackageTool(root, pkg, validatePackedManifest);
|
|
36
48
|
}
|
|
37
49
|
return failures;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
|
-
export async function validatePackedPublishablePackageTypes(
|
|
52
|
+
export async function validatePackedPublishablePackageTypes(
|
|
53
|
+
root: string,
|
|
54
|
+
projects?: readonly string[],
|
|
55
|
+
): Promise<number> {
|
|
41
56
|
let failures = 0;
|
|
42
|
-
for (const pkg of
|
|
57
|
+
for (const pkg of selectedPublishablePackages(root, projects)) {
|
|
43
58
|
failures += await validatePackedPublishablePackageTool(root, pkg, validateAttw);
|
|
44
59
|
}
|
|
45
60
|
return failures;
|
|
@@ -36,6 +36,14 @@ export interface MonorepoContext {
|
|
|
36
36
|
root: string;
|
|
37
37
|
syncRuntime: boolean;
|
|
38
38
|
verbose?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Nx project names the validation is about, when a caller knows: the
|
|
41
|
+
* release's candidates. The build phase and the packed-package packs scope
|
|
42
|
+
* to them; manifest-level packs still see the whole repository, because a
|
|
43
|
+
* package's hygiene does not depend on which packages ship today. Absent,
|
|
44
|
+
* everything is validated and everything is built.
|
|
45
|
+
*/
|
|
46
|
+
projects?: readonly string[];
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
export interface ValidatePackOptions {
|
|
@@ -174,19 +182,19 @@ const packs: MonorepoPack[] = [
|
|
|
174
182
|
{
|
|
175
183
|
name: 'packed-package-publint',
|
|
176
184
|
validatePostBuild(ctx) {
|
|
177
|
-
return validatePackedPublishablePackagePublint(ctx.root);
|
|
185
|
+
return validatePackedPublishablePackagePublint(ctx.root, ctx.projects);
|
|
178
186
|
},
|
|
179
187
|
},
|
|
180
188
|
{
|
|
181
189
|
name: 'packed-package-manifest',
|
|
182
190
|
validatePostBuild(ctx) {
|
|
183
|
-
return validatePackedPublishablePackageManifest(ctx.root);
|
|
191
|
+
return validatePackedPublishablePackageManifest(ctx.root, ctx.projects);
|
|
184
192
|
},
|
|
185
193
|
},
|
|
186
194
|
{
|
|
187
195
|
name: 'packed-package-types',
|
|
188
196
|
validatePostBuild(ctx) {
|
|
189
|
-
return validatePackedPublishablePackageTypes(ctx.root);
|
|
197
|
+
return validatePackedPublishablePackageTypes(ctx.root, ctx.projects);
|
|
190
198
|
},
|
|
191
199
|
},
|
|
192
200
|
{
|
|
@@ -369,9 +377,10 @@ async function runBuild(ctx: MonorepoContext, options: ValidatePackOptions = {})
|
|
|
369
377
|
if (options.verbose) {
|
|
370
378
|
printCheckHeading('build', true);
|
|
371
379
|
}
|
|
380
|
+
const args = ['run-many', '-t', 'build', ...(ctx.projects?.length ? ['-p', ctx.projects.join(',')] : [])];
|
|
372
381
|
const result = options.verbose
|
|
373
|
-
? { exitCode: await runStatus('nx',
|
|
374
|
-
: await runResult('nx',
|
|
382
|
+
? { exitCode: await runStatus('nx', args, ctx.root, false), stdout: '', stderr: '' }
|
|
383
|
+
: await runResult('nx', args, ctx.root);
|
|
375
384
|
const status = result.exitCode;
|
|
376
385
|
if (status !== 0) {
|
|
377
386
|
if (!options.verbose) {
|
|
@@ -614,7 +614,14 @@ function yamlLinesForStep(step: PublishWorkflowStep, options: PublishWorkflowDef
|
|
|
614
614
|
'failure()',
|
|
615
615
|
);
|
|
616
616
|
case PublishWorkflowStepKind.ValidateMonorepoConfig:
|
|
617
|
-
|
|
617
|
+
// Scoped to the release's candidates: the build phase and the packed-package
|
|
618
|
+
// checks cover what ships. Unscoped, validate compiled every project - on a
|
|
619
|
+
// release touching no Rust that was 3m30 of the cowshed CLI on the critical
|
|
620
|
+
// path, for a binary the release did not contain.
|
|
621
|
+
return conditionalRunStep(
|
|
622
|
+
step,
|
|
623
|
+
`smoo monorepo validate --projects "${githubExpression('steps.version.outputs.projects')}"`,
|
|
624
|
+
);
|
|
618
625
|
case PublishWorkflowStepKind.TagRelease:
|
|
619
626
|
return tagReleaseStepLines(step.name);
|
|
620
627
|
case PublishWorkflowStepKind.PublishRelease:
|
|
@@ -387,150 +387,3 @@ describe('resolveSecretEnvironment', () => {
|
|
|
387
387
|
}
|
|
388
388
|
});
|
|
389
389
|
});
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* The remote cache half is exercised the way the managed shell runs it — the
|
|
393
|
-
* raw script as a program, its stdout `eval`-ed — because that round trip is
|
|
394
|
-
* the contract: shell text on stdout, guidance on stderr, and a status that
|
|
395
|
-
* never keeps a shell from opening.
|
|
396
|
-
*/
|
|
397
|
-
describe('remote cache shell export', () => {
|
|
398
|
-
const SERVER = 'https://nx-cache.example.net';
|
|
399
|
-
|
|
400
|
-
async function exportFor(root: string, env: Record<string, string>): Promise<{ stdout: string; stderr: string }> {
|
|
401
|
-
const proc = Bun.spawn({
|
|
402
|
-
cmd: ['bun', RAW_RESOLVER, root],
|
|
403
|
-
env: { PATH: process.env['PATH'], HOME: process.env['HOME'], ...env },
|
|
404
|
-
stdin: 'ignore',
|
|
405
|
-
stdout: 'pipe',
|
|
406
|
-
stderr: 'pipe',
|
|
407
|
-
});
|
|
408
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
409
|
-
new Response(proc.stdout).text(),
|
|
410
|
-
new Response(proc.stderr).text(),
|
|
411
|
-
proc.exited,
|
|
412
|
-
]);
|
|
413
|
-
// A cache is an optimization: nothing it does may fail shell entry.
|
|
414
|
-
expect(exitCode).toBe(0);
|
|
415
|
-
return { stdout, stderr };
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/** The value a shell ends up with after eval-ing the script's output. */
|
|
419
|
-
async function evaluated(root: string, env: Record<string, string>, name: string): Promise<string> {
|
|
420
|
-
const { stdout } = await exportFor(root, env);
|
|
421
|
-
const shell = Bun.spawnSync({
|
|
422
|
-
cmd: ['sh', '-c', `${stdout}printf %s "\${${name}:-}"`],
|
|
423
|
-
env: { PATH: process.env['PATH'] ?? '/usr/bin:/bin' },
|
|
424
|
-
});
|
|
425
|
-
expect(shell.exitCode).toBe(0);
|
|
426
|
-
return shell.stdout.toString();
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
it('exports the pair from an ambient token and nothing from an absent declaration', async () => {
|
|
430
|
-
await withFixture(
|
|
431
|
-
{ secrets: {}, remoteCache: { server: SERVER, tokenSecret: 'NX_REMOTE_CACHE_TOKEN' } },
|
|
432
|
-
async (root) => {
|
|
433
|
-
expect((await exportFor(root, { NX_REMOTE_CACHE_TOKEN: 'ambient-token' })).stdout).toBe(
|
|
434
|
-
`export NX_SELF_HOSTED_REMOTE_CACHE_SERVER='${SERVER}'\nexport NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN='ambient-token'\n`,
|
|
435
|
-
);
|
|
436
|
-
},
|
|
437
|
-
);
|
|
438
|
-
await withFixture({ secrets: {} }, async (root) => {
|
|
439
|
-
expect(await exportFor(root, { NX_REMOTE_CACHE_TOKEN: 'ambient-token' })).toEqual({ stdout: '', stderr: '' });
|
|
440
|
-
});
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
it('exports nothing and names the variable when the token has no value', async () => {
|
|
444
|
-
await withFixture(
|
|
445
|
-
{ secrets: {}, remoteCache: { server: SERVER, tokenSecret: 'NX_REMOTE_CACHE_TOKEN' } },
|
|
446
|
-
async (root) => {
|
|
447
|
-
const { stdout, stderr } = await exportFor(root, {});
|
|
448
|
-
// Half a pair is worse than none: Nx enables its cache on the server
|
|
449
|
-
// alone and then fails every task on 401.
|
|
450
|
-
expect(stdout).toBe('');
|
|
451
|
-
expect(stderr).toContain('NX_REMOTE_CACHE_TOKEN');
|
|
452
|
-
expect(stderr).toContain(SERVER);
|
|
453
|
-
},
|
|
454
|
-
);
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
it('reports a broken provider as a lost cache, never as a failed install', async () => {
|
|
458
|
-
await withFixture(
|
|
459
|
-
{
|
|
460
|
-
secrets: { NX_REMOTE_CACHE_TOKEN: { command: ['definitely-not-a-real-smoo-binary-xyz'] } },
|
|
461
|
-
remoteCache: { server: SERVER, tokenSecret: 'NX_REMOTE_CACHE_TOKEN' },
|
|
462
|
-
},
|
|
463
|
-
async (root) => {
|
|
464
|
-
const { stdout, stderr } = await exportFor(root, {});
|
|
465
|
-
expect(stdout).toBe('');
|
|
466
|
-
expect(stderr).toContain('NX_REMOTE_CACHE_TOKEN');
|
|
467
|
-
expect(stderr).toContain('remote cache off');
|
|
468
|
-
// Nothing here stopped an install, and the message may not say it did.
|
|
469
|
-
expect(stderr).not.toContain('dependencies were not installed');
|
|
470
|
-
},
|
|
471
|
-
);
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
it('leaves a server the environment already carries alone', async () => {
|
|
475
|
-
await withFixture(
|
|
476
|
-
{ secrets: {}, remoteCache: { server: SERVER, tokenSecret: 'NX_REMOTE_CACHE_TOKEN' } },
|
|
477
|
-
async (root) => {
|
|
478
|
-
// A CI job env names the address its own runners reach; the public
|
|
479
|
-
// origin must not replace it.
|
|
480
|
-
expect(
|
|
481
|
-
await exportFor(root, {
|
|
482
|
-
NX_SELF_HOSTED_REMOTE_CACHE_SERVER: 'http://10.89.0.1:8765',
|
|
483
|
-
NX_REMOTE_CACHE_TOKEN: 'ambient-token',
|
|
484
|
-
}),
|
|
485
|
-
).toEqual({ stdout: '', stderr: '' });
|
|
486
|
-
},
|
|
487
|
-
);
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
it('resolves a provider-declared token, and only that one secret', async () => {
|
|
491
|
-
await withFixture(
|
|
492
|
-
{
|
|
493
|
-
secrets: {
|
|
494
|
-
NX_REMOTE_CACHE_TOKEN: { command: emit('provider-token') },
|
|
495
|
-
SMOO_OTHER: { command: ['definitely-not-a-real-smoo-binary-xyz'] },
|
|
496
|
-
},
|
|
497
|
-
remoteCache: { server: SERVER, tokenSecret: 'NX_REMOTE_CACHE_TOKEN' },
|
|
498
|
-
},
|
|
499
|
-
async (root) => {
|
|
500
|
-
// The unrunnable sibling proves no other declared secret is resolved
|
|
501
|
-
// here, and that none of them reaches the shell.
|
|
502
|
-
const { stdout } = await exportFor(root, {});
|
|
503
|
-
expect(stdout).toContain("export NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN='provider-token'");
|
|
504
|
-
expect(stdout).not.toContain('SMOO_OTHER');
|
|
505
|
-
},
|
|
506
|
-
);
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
it('survives eval with a token full of shell metacharacters', async () => {
|
|
510
|
-
const hostile = `it's $(touch /tmp/smoo-cache-pwned) \`x\` "q" \\`;
|
|
511
|
-
await withFixture(
|
|
512
|
-
{ secrets: {}, remoteCache: { server: SERVER, tokenSecret: 'NX_REMOTE_CACHE_TOKEN' } },
|
|
513
|
-
async (root) => {
|
|
514
|
-
expect(
|
|
515
|
-
await evaluated(root, { NX_REMOTE_CACHE_TOKEN: hostile }, 'NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN'),
|
|
516
|
-
).toBe(hostile);
|
|
517
|
-
},
|
|
518
|
-
);
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
it('refuses a declaration Nx could not use, naming the field', async () => {
|
|
522
|
-
for (const remoteCache of [
|
|
523
|
-
{ server: `${SERVER}/`, tokenSecret: 'NX_REMOTE_CACHE_TOKEN' },
|
|
524
|
-
{ server: '', tokenSecret: 'NX_REMOTE_CACHE_TOKEN' },
|
|
525
|
-
{ server: SERVER, tokenSecret: 'has-dash' },
|
|
526
|
-
{ server: SERVER },
|
|
527
|
-
'https://nx-cache.example.net',
|
|
528
|
-
]) {
|
|
529
|
-
await withFixture({ secrets: {}, remoteCache }, async (root) => {
|
|
530
|
-
const { stdout, stderr } = await exportFor(root, { NX_REMOTE_CACHE_TOKEN: 'ambient-token' });
|
|
531
|
-
expect(stdout).toBe('');
|
|
532
|
-
expect(stderr).toContain('smoo.remoteCache');
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
});
|
|
536
|
-
});
|