@smoothbricks/cli 0.11.15 → 0.11.16
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/lib/json.d.ts +9 -0
- package/dist/lib/json.d.ts.map +1 -1
- package/dist/lib/json.js +65 -53
- package/dist/monorepo/managed-files.d.ts +9 -0
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +28 -2
- package/dist/monorepo/publish-workflow.d.ts.map +1 -1
- package/dist/monorepo/publish-workflow.js +52 -2
- package/dist/release/index.d.ts +1 -1
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +26 -4
- package/dist/release/private-npm.d.ts +5 -0
- package/dist/release/private-npm.d.ts.map +1 -1
- package/dist/release/private-npm.js +26 -5
- package/managed/raw/tooling/direnv/devenv.smoo.nix +4 -2
- package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +4 -1
- package/managed/raw/tooling/direnv/toolchain-stamp.ts +88 -0
- package/managed/templates/github/actions/setup-devenv/action.yml +1 -1
- package/package.json +2 -2
- package/src/lib/json.ts +9 -0
- package/src/monorepo/__tests__/publish-workflow.test.ts +54 -0
- package/src/monorepo/managed-files.test.ts +26 -0
- package/src/monorepo/managed-files.ts +36 -2
- package/src/monorepo/publish-workflow.ts +70 -2
- package/src/release/__tests__/private-npm-status.test.ts +37 -0
- package/src/release/__tests__/private-npm-workflow.test.ts +20 -0
- package/src/release/index.ts +27 -2
- package/src/release/private-npm.ts +30 -5
|
@@ -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;
|
|
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
|
@@ -15,7 +15,8 @@ import { parseNxJsonText, parseNxProjectJsonText, parsePackageJsonText, } from '
|
|
|
15
15
|
import { decode, run, runInteractiveStatus, runResult, runText } from '../lib/run.js';
|
|
16
16
|
import { getWorkspacePackages, listPublicReleasePackages, listReleasePackages, readPackageJson, repositoryInfo, } from '../lib/workspace.js';
|
|
17
17
|
export { assertPackedArtifact, releasePack, resolvePackClosure, } from './pack.js';
|
|
18
|
-
export { assertPrivatePackage, checkPrivatePackage, isPrivatePackage, npmPackageExists as npmPackageExistsOnRegistry, npmPublishedVersionExists as npmPublishedVersionExistsOnRegistry, privateNpmPublishArgs, privateNpmUserconfigContent, publishPrivateWithDiagnostics, requirePrivateNpmRegistry, resolvePrivateNpmRegistry, selectPublishDestination, selectRegistryForPackage, withPrivateNpmUserconfig, } from './private-npm.js';
|
|
18
|
+
export { assertPrivatePackage, checkPrivatePackage, isPrivatePackage, npmPackageExists as npmPackageExistsOnRegistry, npmPublishedVersionExists as npmPublishedVersionExistsOnRegistry, privateNpmPublishArgs, privateNpmTokenEnvForMode, privateNpmUserconfigContent, publishPrivateWithDiagnostics, requirePrivateNpmRegistry, resolvePrivateNpmRegistry, selectPublishDestination, selectRegistryForPackage, withPrivateNpmUserconfig, } from './private-npm.js';
|
|
19
|
+
import { PLATFORM_TARGET_GLOBS } from '@smoothbricks/nx-plugin/workspace-config-policy';
|
|
19
20
|
import { ciApiContext } from '../github-ci/api.js';
|
|
20
21
|
import { readProjectTargets } from '../nx/index.js';
|
|
21
22
|
import { bootstrapNpmPackages, NPM_BOOTSTRAP_DIST_TAG, NPM_BOOTSTRAP_VERSION, } from './bootstrap-npm-packages.js';
|
|
@@ -26,7 +27,7 @@ import { createOrUpdateGithubRelease, githubReleaseLookupExists, importWorkspace
|
|
|
26
27
|
import { publishWithAuthDiagnostics } from './npm-auth.js';
|
|
27
28
|
import { bumpStableReleaseToNext, collectRepairPlatformOutputs, completeReleaseAtHead as completeReleaseAtHeadWithShell, prepareReleaseCandidate, repairPendingTargets, runReleaseTag, runReleaseVersion, } from './orchestration.js';
|
|
28
29
|
import { packReleaseTarball } from './pack.js';
|
|
29
|
-
import { assertPrivatePackage, npmPackageExists as npmPackageExistsOnRegistry, npmPublishedVersionExists as npmPublishedVersionExistsOnRegistry, privateNpmPublishArgs, publishPrivateWithDiagnostics, selectPublishDestination, selectRegistryForPackage, withPrivateNpmUserconfig, } from './private-npm.js';
|
|
30
|
+
import { assertPrivatePackage, npmPackageExists as npmPackageExistsOnRegistry, npmPublishedVersionExists as npmPublishedVersionExistsOnRegistry, privateNpmPublishArgs, privateNpmTokenEnvForMode, publishPrivateWithDiagnostics, selectPublishDestination, selectRegistryForPackage, withPrivateNpmUserconfig, } from './private-npm.js';
|
|
30
31
|
import { retagUnpublished } from './retag-unpublished.js';
|
|
31
32
|
import { forgejoAuthHeaders, forgejoReleaseLookupExists, resolveSourceReleaseEndpoint, resolveSourceRepository, sourceReleaseTokenEnvNames, sourceReleaseUrl, } from './source-release.js';
|
|
32
33
|
export async function releaseVersion(root, options) {
|
|
@@ -426,7 +427,12 @@ async function publishPrivatePackedPackage(root, pkg, tag, dryRun, registry) {
|
|
|
426
427
|
}
|
|
427
428
|
await withPrivateNpmUserconfig(registry, 'publish', async (userconfig) => {
|
|
428
429
|
await publishPrivateWithDiagnostics(pkg, {
|
|
429
|
-
publish: () => runNpm(root, args, {
|
|
430
|
+
publish: () => runNpm(root, args, {
|
|
431
|
+
NPM_CONFIG_USERCONFIG: userconfig,
|
|
432
|
+
// Env config outranks a committed project `.npmrc` auth line; see
|
|
433
|
+
// npmStatusEnv in private-npm.ts for why that matters here.
|
|
434
|
+
[`npm_config_${registry.authKey}`]: process.env[privateNpmTokenEnvForMode(registry, 'publish')] ?? '',
|
|
435
|
+
}),
|
|
430
436
|
// The open publish userconfig already authenticates this registry,
|
|
431
437
|
// so the failure probe reuses it. Opening a second read-mode
|
|
432
438
|
// userconfig here demanded a read credential the publishing job need
|
|
@@ -435,6 +441,7 @@ async function publishPrivatePackedPackage(root, pkg, tag, dryRun, registry) {
|
|
|
435
441
|
versionExists: () => npmPublishedVersionExistsOnRegistry(root, pkg.name, pkg.version, {
|
|
436
442
|
registry: registry.registry,
|
|
437
443
|
userconfig,
|
|
444
|
+
credential: { authKey: registry.authKey, tokenEnv: privateNpmTokenEnvForMode(registry, 'publish') },
|
|
438
445
|
}),
|
|
439
446
|
log: (message) => console.log(message),
|
|
440
447
|
error: (message) => console.error(message),
|
|
@@ -782,6 +789,17 @@ function releaseRepairShell(root, platformOutputs) {
|
|
|
782
789
|
const sourceSha = await gitHead(root);
|
|
783
790
|
const outputs = platformOutputs.map((base) => join(base, sourceSha));
|
|
784
791
|
if (outputs.length === 0) {
|
|
792
|
+
// No producer job handed outputs over, so this runner is the producer:
|
|
793
|
+
// build the platform legs at the checked-out ref. `build` alone gives a
|
|
794
|
+
// package only the legs its own graph depends on — the host one and
|
|
795
|
+
// whatever it declares — and the release gate demands the whole declared
|
|
796
|
+
// native closure, so a foreign leg it never asked for was simply absent.
|
|
797
|
+
console.log('Repair pending releases: building cross-platform outputs on this runner.');
|
|
798
|
+
await githubCiNxRunMany(root, {
|
|
799
|
+
targets: PLATFORM_TARGET_GLOBS.join(','),
|
|
800
|
+
projects: releasePackageProjects(packages),
|
|
801
|
+
allowEmptyProjects: true,
|
|
802
|
+
});
|
|
785
803
|
return;
|
|
786
804
|
}
|
|
787
805
|
console.log(`Repair pending releases: applying cross-platform outputs from ${outputs.join(', ')}.`);
|
|
@@ -1366,7 +1384,11 @@ async function npmVersionExists(root, name, version) {
|
|
|
1366
1384
|
assertPublicStatusAllowed(root, name);
|
|
1367
1385
|
return npmPublishedVersionExistsOnRegistry(root, name, version);
|
|
1368
1386
|
}
|
|
1369
|
-
return withPrivateNpmUserconfig(registry, 'read', (userconfig) => npmPublishedVersionExistsOnRegistry(root, name, version, {
|
|
1387
|
+
return withPrivateNpmUserconfig(registry, 'read', (userconfig) => npmPublishedVersionExistsOnRegistry(root, name, version, {
|
|
1388
|
+
registry: registry.registry,
|
|
1389
|
+
userconfig,
|
|
1390
|
+
credential: { authKey: registry.authKey, tokenEnv: privateNpmTokenEnvForMode(registry, 'read') },
|
|
1391
|
+
}));
|
|
1370
1392
|
}
|
|
1371
1393
|
async function npmPackageExists(root, name) {
|
|
1372
1394
|
const registry = selectRegistryForPackage(root, name);
|
|
@@ -103,6 +103,11 @@ export declare function withPrivateNpmUserconfig<T>(resolved: PrivateNpmRegistry
|
|
|
103
103
|
export interface NpmStatusOptions {
|
|
104
104
|
registry?: string;
|
|
105
105
|
userconfig?: string;
|
|
106
|
+
/** Path-scoped auth key and token env, so the credential can ride env config. */
|
|
107
|
+
credential?: {
|
|
108
|
+
authKey: string;
|
|
109
|
+
tokenEnv: string;
|
|
110
|
+
};
|
|
106
111
|
}
|
|
107
112
|
/**
|
|
108
113
|
* Failure-aware registry status: only a genuine not-found result means absent.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"private-npm.d.ts","sourceRoot":"","sources":["../../src/release/private-npm.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAe,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAE3E,OAAO,EAGL,KAAK,WAAW,EAIjB,MAAM,qBAAqB,CAAC;AAE7B,uEAAuE;AACvE,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC;AAE5E,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,iCAAiC,GACzC,sBAAsB,GACtB,iBAAiB,GACjB,iBAAiB,GACjB,eAAe,CAAC;AAEpB,MAAM,WAAW,6BAA6B;IAC5C,IAAI,EAAE,iCAAiC,CAAC;IACxC,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,KAAK,cAAc,GAAG,MAAM,CAAC,kBAAkB,EAAE,6BAA6B,CAAC,CAAC;AAMhF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAwFtE;AAoFD;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,MAAM,GAAG,uBAAuB,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"private-npm.d.ts","sourceRoot":"","sources":["../../src/release/private-npm.ts"],"names":[],"mappings":"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"}
|
|
@@ -179,7 +179,12 @@ export function resolvePrivateNpmWorkflowConfig(root) {
|
|
|
179
179
|
return undefined;
|
|
180
180
|
}
|
|
181
181
|
const npmrcEnv = npmrcAuthTokenEnv(root, declared.scope);
|
|
182
|
-
|
|
182
|
+
// A publishing workspace reads the registry too: every release after the
|
|
183
|
+
// first checks the previous tag's durable state, and that check runs in the
|
|
184
|
+
// producer jobs that never see the publish credential. A declared read token
|
|
185
|
+
// therefore always renders; only the .npmrc fallback stays consumer-only, so
|
|
186
|
+
// a publish token is never promoted into a read role by inference.
|
|
187
|
+
const readTokenEnv = declared.readTokenEnv ?? (consumes ? npmrcEnv : undefined);
|
|
183
188
|
const publishTokenEnv = publishes ? (declared.publishTokenEnv ?? npmrcEnv) : undefined;
|
|
184
189
|
if (!readTokenEnv && !publishTokenEnv) {
|
|
185
190
|
return undefined;
|
|
@@ -312,8 +317,24 @@ export async function withPrivateNpmUserconfig(resolved, mode, fn) {
|
|
|
312
317
|
await rm(dir, { recursive: true, force: true });
|
|
313
318
|
}
|
|
314
319
|
}
|
|
315
|
-
|
|
316
|
-
|
|
320
|
+
/**
|
|
321
|
+
* npm config precedence is cli > env > project `.npmrc` > userconfig. A
|
|
322
|
+
* repository that commits its own `//host/path:_authToken=${SOME_ENV}` line
|
|
323
|
+
* therefore OVERRIDES the userconfig this module writes, and when that env is
|
|
324
|
+
* unset npm sends the unexpanded value: the registry answers 401 on reads with
|
|
325
|
+
* a perfectly good read credential, naming nothing. So the credential also
|
|
326
|
+
* rides as env config, which the project file cannot outrank, and the token
|
|
327
|
+
* value stays out of every file on disk.
|
|
328
|
+
*/
|
|
329
|
+
function npmStatusEnv(userconfig, credential) {
|
|
330
|
+
if (!userconfig)
|
|
331
|
+
return undefined;
|
|
332
|
+
const env = { NPM_CONFIG_USERCONFIG: userconfig };
|
|
333
|
+
const token = credential ? process.env[credential.tokenEnv] : undefined;
|
|
334
|
+
if (credential && token) {
|
|
335
|
+
env[`npm_config_${credential.authKey}`] = token;
|
|
336
|
+
}
|
|
337
|
+
return env;
|
|
317
338
|
}
|
|
318
339
|
function npmCommandFailedMessage(npmArgs, exitCode, stdout = '', stderr = '') {
|
|
319
340
|
const detail = `${stdout}\n${stderr}`.trim();
|
|
@@ -330,7 +351,7 @@ export async function npmPublishedVersionExists(root, name, version, options = {
|
|
|
330
351
|
if (options.registry) {
|
|
331
352
|
args.push('--registry', options.registry);
|
|
332
353
|
}
|
|
333
|
-
const result = await runResult('npm', args, root, npmStatusEnv(options.userconfig));
|
|
354
|
+
const result = await runResult('npm', args, root, npmStatusEnv(options.userconfig, options.credential));
|
|
334
355
|
if (result.exitCode === 0) {
|
|
335
356
|
return true;
|
|
336
357
|
}
|
|
@@ -345,7 +366,7 @@ export async function npmPackageExists(root, name, options = {}) {
|
|
|
345
366
|
if (options.registry) {
|
|
346
367
|
args.push('--registry', options.registry);
|
|
347
368
|
}
|
|
348
|
-
const result = await runResult('npm', args, root, npmStatusEnv(options.userconfig));
|
|
369
|
+
const result = await runResult('npm', args, root, npmStatusEnv(options.userconfig, options.credential));
|
|
349
370
|
if (result.exitCode === 0) {
|
|
350
371
|
return true;
|
|
351
372
|
}
|
|
@@ -372,9 +372,11 @@
|
|
|
372
372
|
esac
|
|
373
373
|
''}
|
|
374
374
|
'')
|
|
375
|
-
# Epilogue
|
|
376
|
-
#
|
|
375
|
+
# Epilogue, after every project step (a project's own enterShell may resolve
|
|
376
|
+
# SDKROOT/compilers; the toolchain identity must see the final values). The
|
|
377
|
+
# stamp logic lives in toolchain-stamp.ts; see its header for why.
|
|
377
378
|
(lib.mkAfter ''
|
|
379
|
+
bun "$DEVENV_ROOT/toolchain-stamp.ts" || exit $?
|
|
378
380
|
if [ -n "$DEVENV_SHELL_PWD" ]; then
|
|
379
381
|
cd "$DEVENV_SHELL_PWD"
|
|
380
382
|
fi
|
|
@@ -145,7 +145,10 @@ build_devenv_shell() {
|
|
|
145
145
|
# One evaluation: the shell that captures the environment is the shell
|
|
146
146
|
# build. A separate `devenv shell -- date` evaluated everything twice.
|
|
147
147
|
persist_devenv_environment
|
|
148
|
-
|
|
148
|
+
# Only the ephemeral-runner path of setup-devenv opts in: the scan walks the
|
|
149
|
+
# whole store, which on a developer host or a host runner is large and not
|
|
150
|
+
# ours to root.
|
|
151
|
+
if [ "${SMOO_ROOT_BUILD_INPUTS:-false}" = true ]; then
|
|
149
152
|
root_build_inputs "$since"
|
|
150
153
|
fi
|
|
151
154
|
# Add repo-local tools only after the shell exists; cleanup steps use an
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Invalidate cmake build-script caches when the toolchain changes.
|
|
4
|
+
*
|
|
5
|
+
* cmake-rs keeps CMAKE_OSX_SYSROOT and the compilers in CMakeCache.txt from
|
|
6
|
+
* the FIRST configure and only clears a build directory when the source path
|
|
7
|
+
* moves. Every C/C++ build script's cache under target/ therefore encodes
|
|
8
|
+
* whichever toolchain the shell had when it was first configured, and it
|
|
9
|
+
* outlives every later fix to that shell: a nix-store sysroot cached on one
|
|
10
|
+
* day kept failing `ld: library 'c++' not found` after the shell stopped
|
|
11
|
+
* exporting it. Nx does not see this state either — it hashes inputs, not
|
|
12
|
+
* what a cache under target/ remembers.
|
|
13
|
+
*
|
|
14
|
+
* Runs at the end of shell entry, after the project's own enterShell resolved
|
|
15
|
+
* SDKROOT and the compilers. It records the toolchain identity the shell
|
|
16
|
+
* settled on and, when that identity differs from the recorded one, removes
|
|
17
|
+
* exactly the cargo build-script directories that hold a CMakeCache.txt, so
|
|
18
|
+
* cargo re-runs those scripts against the current toolchain. Nothing else
|
|
19
|
+
* under target/ is touched; a checkout without target/ has nothing to do.
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
22
|
+
import { join, resolve } from 'node:path';
|
|
23
|
+
import { $ } from 'bun';
|
|
24
|
+
|
|
25
|
+
const root = resolve(`${process.env.DEVENV_ROOT ?? process.cwd()}/../..`);
|
|
26
|
+
const target = join(root, 'target');
|
|
27
|
+
if (existsSync(target)) {
|
|
28
|
+
const stamp = join(target, '.toolchain-stamp');
|
|
29
|
+
const identity = await toolchainIdentity();
|
|
30
|
+
const recorded = existsSync(stamp) ? readFileSync(stamp, 'utf8') : null;
|
|
31
|
+
if (recorded !== identity) {
|
|
32
|
+
const removed = removeCmakeBuildScriptDirs(target);
|
|
33
|
+
if (removed > 0) {
|
|
34
|
+
console.error(`devenv: toolchain identity changed; removed ${removed} cmake build-script cache(s) under target/`);
|
|
35
|
+
}
|
|
36
|
+
writeFileSync(stamp, identity);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function toolchainIdentity(): Promise<string> {
|
|
41
|
+
const rustc = await $`rustc -Vv`.quiet().nothrow().text();
|
|
42
|
+
const cc = (await $`cc --version`.quiet().nothrow().text()).split('\n')[0] ?? '';
|
|
43
|
+
return [
|
|
44
|
+
rustc.trim(),
|
|
45
|
+
`SDKROOT=${process.env.SDKROOT ?? ''}`,
|
|
46
|
+
`DEVELOPER_DIR=${process.env.DEVELOPER_DIR ?? ''}`,
|
|
47
|
+
cc,
|
|
48
|
+
].join('\n');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Removes every cargo build-script directory (`…/build/<crate>-<hash>`) whose
|
|
53
|
+
* `out/build/CMakeCache.txt` exists; returns the count. Only the `build/`
|
|
54
|
+
* directories cargo creates under a profile are scanned - never the compiled
|
|
55
|
+
* artifacts beside them - and the scan completes before anything is removed.
|
|
56
|
+
*/
|
|
57
|
+
function removeCmakeBuildScriptDirs(target: string): number {
|
|
58
|
+
const stale: string[] = [];
|
|
59
|
+
for (const buildDir of cargoBuildDirs(target, 0)) {
|
|
60
|
+
for (const entry of readdirSync(buildDir, { withFileTypes: true })) {
|
|
61
|
+
if (entry.isDirectory() && existsSync(join(buildDir, entry.name, 'out', 'build', 'CMakeCache.txt'))) {
|
|
62
|
+
stale.push(join(buildDir, entry.name));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
for (const dir of stale) {
|
|
67
|
+
rmSync(dir, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
return stale.length;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** `target/[<lane>/][<triple>/]<profile>/build` - at most four levels below target/. */
|
|
73
|
+
function* cargoBuildDirs(dir: string, depth: number): Generator<string> {
|
|
74
|
+
if (depth > 4) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
78
|
+
if (!entry.isDirectory()) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const path = join(dir, entry.name);
|
|
82
|
+
if (entry.name === 'build' && depth > 0) {
|
|
83
|
+
yield path;
|
|
84
|
+
} else if (entry.name !== 'deps' && entry.name !== 'incremental' && entry.name !== '.fingerprint') {
|
|
85
|
+
yield* cargoBuildDirs(path, depth + 1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -237,5 +237,5 @@ runs:
|
|
|
237
237
|
shell: bash
|
|
238
238
|
working-directory: ${{ env.DEVENV_WORKDIR }}/tooling/direnv
|
|
239
239
|
env:
|
|
240
|
-
|
|
240
|
+
SMOO_ROOT_BUILD_INPUTS: ${{ steps.runner-kind.outputs.host != 'true' }}
|
|
241
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.16",
|
|
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.10",
|
|
68
68
|
"@smoothbricks/validation": "0.1.8",
|
|
69
69
|
"commander": "^14.0.3",
|
|
70
70
|
"make-synchronized": "^0.8.0",
|
package/src/lib/json.ts
CHANGED
|
@@ -77,6 +77,15 @@ export interface PackageSmooGithub {
|
|
|
77
77
|
/** Toolchain environment on the Linux foreign-platform producer, such as an SDK path. */
|
|
78
78
|
env?: StringMap;
|
|
79
79
|
};
|
|
80
|
+
/**
|
|
81
|
+
* Platform target families the RELEASE workflow must not produce, as target
|
|
82
|
+
* globs (`*-macos`, `*-ios`, `*-linux`). A repository whose Nx graph carries
|
|
83
|
+
* a family it does not release from `publish.yml` — because another workflow
|
|
84
|
+
* owns it, or because the leg is not production yet — names it here, and the
|
|
85
|
+
* publish workflow renders without that job instead of carrying a leg whose
|
|
86
|
+
* failure blocks every release.
|
|
87
|
+
*/
|
|
88
|
+
releasePlatformFamiliesExcluded?: string[];
|
|
80
89
|
/**
|
|
81
90
|
* Sibling source checkouts a repo path-depends on (Cargo path
|
|
82
91
|
* dependencies, nix path inputs). The CI workflow clones each entry beside
|
|
@@ -101,6 +101,60 @@ describe('publish workflow definition', () => {
|
|
|
101
101
|
expect(rendered).toContain("default: ''");
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
it('a private forge release with cross-built Apple targets publishes from one job', () => {
|
|
105
|
+
const rendered = renderPublishWorkflowYaml({
|
|
106
|
+
repoName: 'axe.sc/axe',
|
|
107
|
+
actionsProvider: 'forgejo',
|
|
108
|
+
runsOn: ['nixos-latest-x64', 'self-hosted'],
|
|
109
|
+
platformTargetGlobs: ['*-macos', '*-linux'],
|
|
110
|
+
macosPlatformArchitectures: ['arm64'],
|
|
111
|
+
platformProducer: { kind: 'linux-cross', preflight: 'sh scripts/prepare-macos-sdk.sh', env: { AXE_CROSS: '1' } },
|
|
112
|
+
privateNpm: { scope: '@axe.sc', readTokenEnv: 'READ_ENV', publishTokenEnv: 'PUBLISH_ENV' },
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// One job: no producer job to hand outputs over from, so no transfer at all.
|
|
116
|
+
const jobsYaml = rendered.slice(rendered.indexOf('jobs:\n') + 'jobs:\n'.length);
|
|
117
|
+
expect(jobsYaml.match(/^ {2}[a-z][a-z0-9-]*:$/gm)).toEqual([' publish:']);
|
|
118
|
+
// The hand-off steps specifically; the failure-only trace-DB upload stays.
|
|
119
|
+
expect(rendered).not.toContain('Upload validated release state');
|
|
120
|
+
expect(rendered).not.toContain('Upload validated build outputs');
|
|
121
|
+
expect(rendered).not.toContain('Upload macOS platform outputs');
|
|
122
|
+
expect(rendered).not.toContain('Download candidate artifacts');
|
|
123
|
+
expect(rendered).not.toContain('git bundle');
|
|
124
|
+
// It still builds the Apple targets, with the producer's toolchain env and
|
|
125
|
+
// its preflight ahead of setup.
|
|
126
|
+
expect(rendered).toContain('--targets "*-macos"');
|
|
127
|
+
expect(rendered).toMatch(/^ {6}AXE_CROSS: ['"]1['"]$/m);
|
|
128
|
+
expect(rendered).toContain('Check cross-platform toolchain prerequisites');
|
|
129
|
+
expect(rendered.indexOf('Check cross-platform toolchain prerequisites')).toBeLessThan(
|
|
130
|
+
rendered.indexOf('Setup Nix/devenv'),
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('keeps the publishing hand-off when provenance needs the hosted runner', () => {
|
|
135
|
+
const publicWithMac = renderPublishWorkflowYaml({
|
|
136
|
+
repoName: '@smoothbricks/codebase',
|
|
137
|
+
platformTargetGlobs: ['*-macos', '*-linux'],
|
|
138
|
+
macosPlatformArchitectures: ['arm64'],
|
|
139
|
+
platformProducer: { kind: 'linux-cross', preflight: 'sh prepare.sh' },
|
|
140
|
+
});
|
|
141
|
+
const forgePublicWithMac = renderPublishWorkflowYaml({
|
|
142
|
+
repoName: '@smoothbricks/codebase',
|
|
143
|
+
actionsProvider: 'forgejo',
|
|
144
|
+
runsOn: ['nixos-latest-x64', 'self-hosted'],
|
|
145
|
+
platformTargetGlobs: ['*-macos', '*-linux'],
|
|
146
|
+
macosPlatformArchitectures: ['arm64'],
|
|
147
|
+
platformProducer: { kind: 'linux-cross', preflight: 'sh prepare.sh' },
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// No private registry declared: packages go to npmjs, which mints
|
|
151
|
+
// provenance from the publishing job and refuses a self-hosted runner.
|
|
152
|
+
for (const rendered of [publicWithMac, forgePublicWithMac]) {
|
|
153
|
+
expect(rendered).toContain('publish-on-linux:');
|
|
154
|
+
expect(rendered).toContain('Download candidate artifacts');
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
104
158
|
it('preserves the single Ubuntu job and renders no artifact transfer when no Apple targets exist', () => {
|
|
105
159
|
const current = renderPublishWorkflowYaml({ repoName: '@smoothbricks/codebase' });
|
|
106
160
|
const explicitlyEmpty = renderPublishWorkflowYaml({
|
|
@@ -20,10 +20,12 @@ import {
|
|
|
20
20
|
managedFileTargetsForTest,
|
|
21
21
|
platformTargetGlobsForTest,
|
|
22
22
|
reinsertInlineLocalBlocksForTest,
|
|
23
|
+
releasePlatformTargetGlobsFor,
|
|
23
24
|
renderManagedWorkflowForTest,
|
|
24
25
|
splitLocalSectionForTest,
|
|
25
26
|
validateDevenvModuleImport,
|
|
26
27
|
} from './managed-files.js';
|
|
28
|
+
import { renderPublishWorkflowYaml } from './publish-workflow.js';
|
|
27
29
|
|
|
28
30
|
const MANAGED = '# managed content\npath merge=driver\n';
|
|
29
31
|
|
|
@@ -457,6 +459,7 @@ const context = (overrides: Partial<ManagedFileContext>): ManagedFileContext =>
|
|
|
457
459
|
nodeModulesCacheKey: 'key',
|
|
458
460
|
repoName: '@scope/repo',
|
|
459
461
|
platformTargetGlobs: [],
|
|
462
|
+
releasePlatformTargetGlobs: [],
|
|
460
463
|
macosPlatformArchitectures: [],
|
|
461
464
|
...overrides,
|
|
462
465
|
});
|
|
@@ -565,3 +568,26 @@ describe('CI workflow rendering by repo shape', () => {
|
|
|
565
568
|
expect(rendered).toContain('GIT_CRYPT_KEY_B64: ${{ secrets.GIT_CRYPT_KEY_B64 }}');
|
|
566
569
|
});
|
|
567
570
|
});
|
|
571
|
+
|
|
572
|
+
describe('release platform families', () => {
|
|
573
|
+
it('an excluded family is not produced by the release workflow, and the rest still are', () => {
|
|
574
|
+
expect(releasePlatformTargetGlobsFor(['*-macos', '*-ios', '*-linux'], ['*-macos', '*-ios'])).toEqual(['*-linux']);
|
|
575
|
+
expect(releasePlatformTargetGlobsFor(PLATFORM_TARGET_GLOBS, undefined)).toEqual([...PLATFORM_TARGET_GLOBS]);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it('excluding every Apple family renders the single-job Linux publish shape', () => {
|
|
579
|
+
const globs = releasePlatformTargetGlobsFor(['*-macos', '*-linux'], ['*-macos']);
|
|
580
|
+
const rendered = renderPublishWorkflowYaml({
|
|
581
|
+
repoName: 'axe.sc/axe',
|
|
582
|
+
platformTargetGlobs: globs,
|
|
583
|
+
macosPlatformArchitectures: [],
|
|
584
|
+
runsOn: ['nixos-latest-x64', 'self-hosted'],
|
|
585
|
+
actionsProvider: 'forgejo',
|
|
586
|
+
});
|
|
587
|
+
expect(rendered).not.toContain('cross-platform:');
|
|
588
|
+
expect(rendered).not.toContain('macos-platform:');
|
|
589
|
+
expect(rendered).not.toContain('SDKROOT');
|
|
590
|
+
expect(rendered).not.toContain('--targets "*-macos"');
|
|
591
|
+
expect(rendered).toContain(' publish:');
|
|
592
|
+
});
|
|
593
|
+
});
|
|
@@ -170,6 +170,8 @@ export interface ManagedFileContext {
|
|
|
170
170
|
nodeModulesCacheKey: string;
|
|
171
171
|
repoName: string;
|
|
172
172
|
platformTargetGlobs: string[];
|
|
173
|
+
/** Platform families the release workflow produces; excluded families are absent. */
|
|
174
|
+
releasePlatformTargetGlobs: string[];
|
|
173
175
|
macosPlatformArchitectures: string[];
|
|
174
176
|
/** Declared private-npm opt-in from the root smoo config; absent means fully public. */
|
|
175
177
|
privateNpm?: PackagePrivateNpmConfig;
|
|
@@ -221,6 +223,12 @@ const managedFiles: ManagedFile[] = [
|
|
|
221
223
|
source: 'tooling/direnv/secret-references.ts',
|
|
222
224
|
target: 'tooling/direnv/secret-references.ts',
|
|
223
225
|
},
|
|
226
|
+
{
|
|
227
|
+
kind: 'raw',
|
|
228
|
+
source: 'tooling/direnv/toolchain-stamp.ts',
|
|
229
|
+
target: 'tooling/direnv/toolchain-stamp.ts',
|
|
230
|
+
executable: true,
|
|
231
|
+
},
|
|
224
232
|
{
|
|
225
233
|
kind: 'raw',
|
|
226
234
|
source: 'tooling/direnv/devenv.smoo.nix',
|
|
@@ -409,7 +417,7 @@ function getManagedContent(file: ManagedFile, context: ManagedFileContext): stri
|
|
|
409
417
|
release: context.hasReleasePackages,
|
|
410
418
|
deployProvider: context.productionDeployProvider,
|
|
411
419
|
repoName: context.repoName,
|
|
412
|
-
platformTargetGlobs: context.
|
|
420
|
+
platformTargetGlobs: context.releasePlatformTargetGlobs,
|
|
413
421
|
macosPlatformArchitectures: context.macosPlatformArchitectures,
|
|
414
422
|
runsOn: context.ciRunsOn,
|
|
415
423
|
macosRunsOn: context.macosRunsOn,
|
|
@@ -451,6 +459,14 @@ async function getManagedFileContext(root: string): Promise<ManagedFileContext>
|
|
|
451
459
|
const productionDeploy = deployTargetInfoFromProjects(nxProjects, 'production');
|
|
452
460
|
const targetNames = targetNamesFromProjects(nxProjects);
|
|
453
461
|
const platformTargetGlobs = platformTargetGlobsForTest(targetNames);
|
|
462
|
+
// The release workflow produces every platform family the graph carries
|
|
463
|
+
// EXCEPT the ones the repository excludes. An excluded family renders no job
|
|
464
|
+
// at all rather than a job whose failure blocks every release; the workflow
|
|
465
|
+
// that does own it says so itself.
|
|
466
|
+
const releasePlatformTargetGlobs = releasePlatformTargetGlobsFor(
|
|
467
|
+
platformTargetGlobs,
|
|
468
|
+
github?.releasePlatformFamiliesExcluded,
|
|
469
|
+
);
|
|
454
470
|
const privateNpm = resolvePrivateNpmWorkflowConfig(root);
|
|
455
471
|
// Cache registry identity plus the lockfile, never token values: a scope
|
|
456
472
|
// URL change in the committed .npmrc must invalidate the dependency cache,
|
|
@@ -479,10 +495,13 @@ async function getManagedFileContext(root: string): Promise<ManagedFileContext>
|
|
|
479
495
|
nodeModulesCacheKey,
|
|
480
496
|
repoName,
|
|
481
497
|
platformTargetGlobs,
|
|
498
|
+
releasePlatformTargetGlobs,
|
|
482
499
|
sourceCheckouts,
|
|
483
500
|
cargoCredentials,
|
|
484
501
|
remoteCache: manifest?.smoo?.remoteCache,
|
|
485
|
-
macosPlatformArchitectures:
|
|
502
|
+
macosPlatformArchitectures: MACOS_PLATFORM_TARGET_GLOBS.some((glob) => releasePlatformTargetGlobs.includes(glob))
|
|
503
|
+
? macosPlatformArchitecturesForTest(targetNames)
|
|
504
|
+
: [],
|
|
486
505
|
privateNpm,
|
|
487
506
|
};
|
|
488
507
|
}
|
|
@@ -491,6 +510,21 @@ export function hasExactTargetForTest(targetNames: Iterable<string>, target: str
|
|
|
491
510
|
return [...targetNames].includes(target);
|
|
492
511
|
}
|
|
493
512
|
|
|
513
|
+
/**
|
|
514
|
+
* The platform families the release workflow produces: the graph's families
|
|
515
|
+
* minus the ones the repository excludes. Excluding a family the graph does not
|
|
516
|
+
* have is not an error — a repository states what it does not release, and the
|
|
517
|
+
* statement stays true when the target is added later.
|
|
518
|
+
*/
|
|
519
|
+
export function releasePlatformTargetGlobsFor(
|
|
520
|
+
platformTargetGlobs: readonly string[],
|
|
521
|
+
excluded: readonly string[] | undefined,
|
|
522
|
+
): string[] {
|
|
523
|
+
if (!excluded || excluded.length === 0) return [...platformTargetGlobs];
|
|
524
|
+
const drop = new Set(excluded);
|
|
525
|
+
return platformTargetGlobs.filter((glob) => !drop.has(glob));
|
|
526
|
+
}
|
|
527
|
+
|
|
494
528
|
export function platformTargetGlobsForTest(targetNames: Iterable<string>): string[] {
|
|
495
529
|
const names = [...targetNames];
|
|
496
530
|
return PLATFORM_TARGET_GLOBS.filter((glob) => {
|
|
@@ -404,8 +404,17 @@ export function renderPublishWorkflowYaml(options: PublishWorkflowDefinitionOpti
|
|
|
404
404
|
if (options.release === false) {
|
|
405
405
|
const steps = definePublishWorkflow(options).steps;
|
|
406
406
|
workflow = `${renderPublishWorkflowHeader(options)}${renderPublishWorkflowSteps(steps, options)}`;
|
|
407
|
-
} else if (hasMacosPlatformTargets(options)) {
|
|
407
|
+
} else if (hasMacosPlatformTargets(options) && !skipsPublishHandoff(options)) {
|
|
408
408
|
workflow = renderPlatformPublishWorkflowYaml(options);
|
|
409
|
+
} else if (hasMacosPlatformTargets(options)) {
|
|
410
|
+
// A linux-cross producer runs the Apple targets on the publisher's own
|
|
411
|
+
// runner. Splitting jobs then buys nothing and costs a real transfer: the
|
|
412
|
+
// outputs get tarred, uploaded, downloaded into a second checkout, and two
|
|
413
|
+
// more devenv setups pay for themselves twice. One job, no artifacts.
|
|
414
|
+
workflow = `${renderPublishWorkflowHeader(options)}${renderSingleJobPublishWorkflowSteps(
|
|
415
|
+
definePublishWorkflow(options).steps,
|
|
416
|
+
options,
|
|
417
|
+
)}`;
|
|
409
418
|
} else if (hasLinuxPlatformTargets(options)) {
|
|
410
419
|
workflow = `${renderPublishWorkflowHeader(options)}${renderSingleJobPublishWorkflowSteps(
|
|
411
420
|
definePublishWorkflow(options).steps,
|
|
@@ -475,7 +484,7 @@ jobs:
|
|
|
475
484
|
publish:
|
|
476
485
|
${options.release === false ? renderRunsOnLine(options.runsOn) : publishJobRunsOnLine(options)}
|
|
477
486
|
env:
|
|
478
|
-
GH_TOKEN: ${githubExpression('github.token')}${remoteCacheJobEnv(options)}${cargoCredentialsJobEnv(options)}${privateNpmInstallJobEnv(options)}
|
|
487
|
+
GH_TOKEN: ${githubExpression('github.token')}${remoteCacheJobEnv(options)}${cargoCredentialsJobEnv(options)}${privateNpmInstallJobEnv(options)}${platformProducerJobEnv(options)}
|
|
479
488
|
steps:
|
|
480
489
|
`;
|
|
481
490
|
}
|
|
@@ -749,11 +758,25 @@ function renderSingleJobPublishWorkflowSteps(
|
|
|
749
758
|
options: PublishWorkflowDefinitionOptions,
|
|
750
759
|
): string {
|
|
751
760
|
const lines: string[] = [];
|
|
761
|
+
const producer = options.platformProducer;
|
|
762
|
+
const crossOnThisRunner = skipsPublishHandoff(options) && hasMacosPlatformTargets(options);
|
|
752
763
|
for (const step of steps) {
|
|
753
764
|
lines.push(...sectionLinesBefore(step));
|
|
754
765
|
lines.push(...commentLinesForStep(step));
|
|
755
766
|
lines.push(...yamlLinesForStep(step, options));
|
|
756
767
|
lines.push('');
|
|
768
|
+
if (crossOnThisRunner && producer && step.kind === PublishWorkflowStepKind.Checkout) {
|
|
769
|
+
// Before any toolchain setup or build, exactly as the dedicated producer
|
|
770
|
+
// job ordered it: the SDK has to be on disk before devenv resolves it.
|
|
771
|
+
lines.push(
|
|
772
|
+
' - name: Check cross-platform toolchain prerequisites',
|
|
773
|
+
' working-directory: .',
|
|
774
|
+
' run: |',
|
|
775
|
+
' set -euo pipefail',
|
|
776
|
+
...producer.preflight.split('\n').map((line) => ` ${line}`),
|
|
777
|
+
'',
|
|
778
|
+
);
|
|
779
|
+
}
|
|
757
780
|
if (step.kind === PublishWorkflowStepKind.Build) {
|
|
758
781
|
lines.push(
|
|
759
782
|
' - name: 🐧 Build supplemental Linux targets',
|
|
@@ -763,6 +786,16 @@ function renderSingleJobPublishWorkflowSteps(
|
|
|
763
786
|
)}"`,
|
|
764
787
|
'',
|
|
765
788
|
);
|
|
789
|
+
if (crossOnThisRunner) {
|
|
790
|
+
lines.push(
|
|
791
|
+
' - name: 🍎 Build selected macOS and iOS release outputs',
|
|
792
|
+
" if: steps.version.outputs.mode != 'none'",
|
|
793
|
+
` run: smoo github-ci nx-run-many --targets "${macosPlatformFamilies(options).join(',')}" --projects "${githubExpression(
|
|
794
|
+
'steps.version.outputs.projects',
|
|
795
|
+
)}"`,
|
|
796
|
+
'',
|
|
797
|
+
);
|
|
798
|
+
}
|
|
766
799
|
}
|
|
767
800
|
}
|
|
768
801
|
return `${lines.join('\n').trimEnd()}\n`;
|
|
@@ -1423,6 +1456,41 @@ function githubExpression(expression: string): string {
|
|
|
1423
1456
|
return ['$', '{{ ', expression, ' }}'].join('');
|
|
1424
1457
|
}
|
|
1425
1458
|
|
|
1459
|
+
/**
|
|
1460
|
+
* Whether the separate publishing job can be dropped, so the release runs as
|
|
1461
|
+
* one job that builds every artifact and publishes them.
|
|
1462
|
+
*
|
|
1463
|
+
* The hand-off exists for ONE reason: npmjs mints provenance from the
|
|
1464
|
+
* publishing job's OIDC token and refuses a self-hosted runner for it, so a
|
|
1465
|
+
* release with public packages must publish from the GitHub-hosted runner and
|
|
1466
|
+
* therefore has to receive its self-hosted artifacts through an artifact.
|
|
1467
|
+
* A release that publishes only to the declared private registry has no such
|
|
1468
|
+
* constraint; when its Apple targets are cross-built on the same runner (or it
|
|
1469
|
+
* has none), one job holds everything.
|
|
1470
|
+
*/
|
|
1471
|
+
function skipsPublishHandoff(options: PublishWorkflowDefinitionOptions): boolean {
|
|
1472
|
+
if (options.actionsProvider !== 'forgejo' || options.privateNpm === undefined) {
|
|
1473
|
+
return false;
|
|
1474
|
+
}
|
|
1475
|
+
return !hasMacosPlatformTargets(options) || options.platformProducer?.kind === 'linux-cross';
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
/**
|
|
1479
|
+
* Toolchain environment for a foreign-platform producer, rendered onto whichever
|
|
1480
|
+
* job runs its builds. In the flattened single-job shape that is the publish job
|
|
1481
|
+
* itself, so the SDK path and cross flags have to reach it the same way the
|
|
1482
|
+
* dedicated producer job received them.
|
|
1483
|
+
*/
|
|
1484
|
+
function platformProducerJobEnv(options: PublishWorkflowDefinitionOptions): string {
|
|
1485
|
+
const producer = options.platformProducer;
|
|
1486
|
+
if (!producer || !skipsPublishHandoff(options)) {
|
|
1487
|
+
return '';
|
|
1488
|
+
}
|
|
1489
|
+
return Object.entries(producer.env ?? {})
|
|
1490
|
+
.map(([name, value]) => `\n ${name}: ${JSON.stringify(value)}`)
|
|
1491
|
+
.join('');
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1426
1494
|
function privateNpmInstallJobEnv(options: PublishWorkflowDefinitionOptions): string {
|
|
1427
1495
|
const tokenEnv = options.privateNpm?.readTokenEnv;
|
|
1428
1496
|
if (!tokenEnv) {
|