@smoothbricks/cli 0.11.11 → 0.11.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +104 -20
  2. package/dist/lib/json.d.ts +51 -0
  3. package/dist/lib/json.d.ts.map +1 -1
  4. package/dist/lib/json.js +111 -67
  5. package/dist/monorepo/cargo-policy.d.ts +25 -1
  6. package/dist/monorepo/cargo-policy.d.ts.map +1 -1
  7. package/dist/monorepo/cargo-policy.js +396 -4
  8. package/dist/monorepo/ci-workflow.d.ts +27 -2
  9. package/dist/monorepo/ci-workflow.d.ts.map +1 -1
  10. package/dist/monorepo/ci-workflow.js +151 -11
  11. package/dist/monorepo/index.d.ts.map +1 -1
  12. package/dist/monorepo/index.js +5 -1
  13. package/dist/monorepo/managed-files.d.ts +3 -1
  14. package/dist/monorepo/managed-files.d.ts.map +1 -1
  15. package/dist/monorepo/managed-files.js +3 -0
  16. package/dist/monorepo/packs/index.d.ts.map +1 -1
  17. package/dist/monorepo/packs/index.js +13 -0
  18. package/dist/monorepo/publish-workflow.d.ts +8 -1
  19. package/dist/monorepo/publish-workflow.d.ts.map +1 -1
  20. package/dist/monorepo/publish-workflow.js +15 -5
  21. package/managed/raw/tooling/direnv/devenv.smoo.nix +28 -2
  22. package/managed/raw/tooling/direnv/secret-references.ts +248 -51
  23. package/managed/templates/github/actions/save-nix-devenv/action.yml +2 -2
  24. package/managed/templates/github/actions/setup-devenv/action.yml +43 -5
  25. package/package.json +2 -2
  26. package/src/lib/json.ts +52 -0
  27. package/src/monorepo/__tests__/ci-workflow.test.ts +183 -0
  28. package/src/monorepo/__tests__/publish-workflow.test.ts +29 -0
  29. package/src/monorepo/cargo-policy.test.ts +206 -3
  30. package/src/monorepo/cargo-policy.ts +341 -2
  31. package/src/monorepo/ci-workflow.ts +180 -11
  32. package/src/monorepo/index.ts +5 -1
  33. package/src/monorepo/managed-files.ts +6 -0
  34. package/src/monorepo/packs/index.test.ts +32 -0
  35. package/src/monorepo/packs/index.ts +13 -0
  36. package/src/monorepo/publish-workflow.ts +24 -4
  37. package/src/monorepo/secret-references.test.ts +175 -2
@@ -2,7 +2,7 @@ import { appendFileSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { printCommandOutput, run, runResult } from '../lib/run.js';
3
3
  import { escapeRegex, getWorkspacePackages, getWorkspacePatterns, listReleasePackages } from '../lib/workspace.js';
4
4
  import { readProjectTargets } from '../nx/index.js';
5
- import { validateCargoCachePolicy } from './cargo-policy.js';
5
+ import { applyCargoFeatureUnification, validateCargoCachePolicy } from './cargo-policy.js';
6
6
  import {
7
7
  formatCommitMessage,
8
8
  stagedDeletedPublicPackages,
@@ -112,6 +112,10 @@ export async function updateManagedFiles(root: string): Promise<void> {
112
112
  // Tool dependency policy (typescript API 6, @typescript/native for ttsc, nx, …)
113
113
  // lives next to managed templates — update must install them, not only rewrite files.
114
114
  await applyToolConfigDefaults(root);
115
+ // Rust's half of the same job: a multi-crate Cargo workspace must resolve
116
+ // features once for every member, or each per-crate cargo invocation
117
+ // recompiles the shared graph for its own selection.
118
+ applyCargoFeatureUnification(root);
115
119
  syncBunLockfileVersions(root, { mode: 'install' });
116
120
  console.log('installing workspace dependencies (bun install)');
117
121
  await run('bun', ['install', '--no-summary'], root);
@@ -10,6 +10,7 @@ import {
10
10
  type PackageCargoCredentialsConfig,
11
11
  type PackageJson,
12
12
  type PackagePrivateNpmConfig,
13
+ type PackageRemoteCacheConfig,
13
14
  type PackageSmooGithub,
14
15
  type PackageSmooGithubEnvironments,
15
16
  type PackageSourceCheckoutConfig,
@@ -176,6 +177,8 @@ export interface ManagedFileContext {
176
177
  sourceCheckouts?: PackageSourceCheckoutConfig[];
177
178
  /** Declared Cargo private-dependency credentials from the root smoo config; absent means none. */
178
179
  cargoCredentials?: PackageCargoCredentialsConfig;
180
+ /** Declared self-hosted Nx remote cache from the root smoo config; absent means local caching only. */
181
+ remoteCache?: PackageRemoteCacheConfig;
179
182
  }
180
183
 
181
184
  interface DeployTargetInfo {
@@ -391,6 +394,7 @@ function getManagedContent(file: ManagedFile, context: ManagedFileContext): stri
391
394
  privateNpm: context.privateNpm,
392
395
  sourceCheckouts: context.sourceCheckouts,
393
396
  cargoCredentials: context.cargoCredentials,
397
+ remoteCache: context.remoteCache,
394
398
  environments: context.ciEnvironments,
395
399
  deploySecrets: context.ciDeploySecrets,
396
400
  e2eSecrets: context.ciE2eSecrets,
@@ -414,6 +418,7 @@ function getManagedContent(file: ManagedFile, context: ManagedFileContext): stri
414
418
  privateNpm: context.privateNpm,
415
419
  sourceCheckouts: context.sourceCheckouts,
416
420
  cargoCredentials: context.cargoCredentials,
421
+ remoteCache: context.remoteCache,
417
422
  deploySecrets: context.ciDeploySecrets,
418
423
  });
419
424
  }
@@ -476,6 +481,7 @@ async function getManagedFileContext(root: string): Promise<ManagedFileContext>
476
481
  platformTargetGlobs,
477
482
  sourceCheckouts,
478
483
  cargoCredentials,
484
+ remoteCache: manifest?.smoo?.remoteCache,
479
485
  macosPlatformArchitectures: macosPlatformArchitecturesForTest(targetNames),
480
486
  privateNpm,
481
487
  };
@@ -221,6 +221,38 @@ describe('monorepo validation pack phases', () => {
221
221
  }
222
222
  });
223
223
 
224
+ it('validates and fixes cargo workspace feature unification through its own pack', async () => {
225
+ const root = await mkdtemp(join(tmpdir(), 'smoo-validate-cargo-'));
226
+ try {
227
+ await mkdir(join(root, 'crates/alpha'), { recursive: true });
228
+ await mkdir(join(root, 'crates/beta'), { recursive: true });
229
+ await mkdir(join(root, 'tooling/direnv'), { recursive: true });
230
+ await writeFile(
231
+ join(root, 'Cargo.toml'),
232
+ '[workspace]\nmembers = ["crates/*"]\n\n[profile.test]\nincremental = false\ndebug = 0\n',
233
+ );
234
+ await writeFile(join(root, 'crates/alpha/Cargo.toml'), '[package]\nname = "alpha"\n');
235
+ await writeFile(join(root, 'crates/beta/Cargo.toml'), '[package]\nname = "beta"\n');
236
+ await writeFile(join(root, 'tooling/direnv/devenv.smoo.nix'), 'languages.rust = {\n channel = "nightly";\n};\n');
237
+ const cargoPack = packsForTest.find((pack) => pack.name === 'cargo');
238
+ if (!cargoPack) {
239
+ throw new Error('cargo validation pack not found');
240
+ }
241
+ const runBuild = () => 0;
242
+
243
+ // A policy nothing calls is not a policy: validate must reach it.
244
+ expect(
245
+ await runValidatePacks({ root, syncRuntime: false }, { failFast: true }, { packs: [cargoPack], runBuild }),
246
+ ).toEqual({ failures: 1, failedChecks: 1 });
247
+
248
+ expect(
249
+ await runValidatePacks({ root, syncRuntime: false }, { fix: true }, { packs: [cargoPack], runBuild }),
250
+ ).toEqual({ failures: 0, failedChecks: 0 });
251
+ } finally {
252
+ await rm(root, { recursive: true, force: true });
253
+ }
254
+ });
255
+
224
256
  it('propagates parsed target dependencies through the production adapter', () => {
225
257
  const targetDependencies = new Map([
226
258
  ['build', ['compile-linux']],
@@ -2,6 +2,7 @@ import { chmodSync, existsSync, statSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { printCommandOutput, runResult, runStatus } from '../../lib/run.js';
4
4
  import { type ProjectTargets, readProjectTargets } from '../../nx/index.js';
5
+ import { applyCargoFeatureUnification, validateCargoCachePolicy } from '../cargo-policy.js';
5
6
  import { validateGoToolchainAgreement } from '../go-toolchain.js';
6
7
  import { syncBunLockfileVersions, validateBunLockfileVersions } from '../lockfile.js';
7
8
  import { validateDevenvModuleImport, warnOnManagedFileDrift } from '../managed-files.js';
@@ -120,6 +121,18 @@ const packs: MonorepoPack[] = [
120
121
  return validateDevenvModuleImport(ctx.root);
121
122
  },
122
123
  },
124
+ {
125
+ // Cargo's build-cache and feature-unification conventions. `--fix` writes
126
+ // the workspace feature unification a multi-crate workspace needs; the rest
127
+ // of the policy is a verdict a human has to act on.
128
+ name: 'cargo',
129
+ fixPreBuild(ctx) {
130
+ applyCargoFeatureUnification(ctx.root);
131
+ },
132
+ validatePreBuild(ctx) {
133
+ return validateCargoCachePolicy(ctx.root);
134
+ },
135
+ },
123
136
  {
124
137
  name: 'publishing',
125
138
  init(ctx) {
@@ -11,6 +11,7 @@ import { isSmoothBricksCodebasePackageName } from '../lib/cli-package.js';
11
11
  import type {
12
12
  PackageCargoCredentialsConfig,
13
13
  PackagePrivateNpmConfig,
14
+ PackageRemoteCacheConfig,
14
15
  PackageSmooGithub,
15
16
  PackageSourceCheckoutConfig,
16
17
  } from '../lib/json.js';
@@ -21,6 +22,7 @@ import {
21
22
  cargoCredentialStepLines,
22
23
  type DeployStepSecretConfig,
23
24
  deployStepSecretEnvLines,
25
+ remoteCacheJobEnvLines,
24
26
  sourceCheckoutsStepLines,
25
27
  } from './ci-workflow.js';
26
28
  import { GITHUB_HOSTED_LINUX_RUNNER, renderRunsOnLine, type WorkflowRunsOn } from './github-runs-on.js';
@@ -125,6 +127,13 @@ export interface PublishWorkflowDefinitionOptions extends DeployStepSecretConfig
125
127
  * become job env because every later cargo fetch resolves them.
126
128
  */
127
129
  cargoCredentials?: PackageCargoCredentialsConfig;
130
+ /**
131
+ * Declared self-hosted Nx remote cache. Every job here runs Nx targets —
132
+ * the release-candidate build, the platform build, the publish gate — so
133
+ * each carries the server and access token and reuses what CI already
134
+ * built for the same hashes.
135
+ */
136
+ remoteCache?: PackageRemoteCacheConfig;
128
137
  }
129
138
 
130
139
  export interface PublishWorkflowInputs {
@@ -466,7 +475,7 @@ jobs:
466
475
  publish:
467
476
  ${options.release === false ? renderRunsOnLine(options.runsOn) : publishJobRunsOnLine(options)}
468
477
  env:
469
- GH_TOKEN: ${githubExpression('github.token')}${cargoCredentialsJobEnv(options)}${privateNpmInstallJobEnv(options)}
478
+ GH_TOKEN: ${githubExpression('github.token')}${remoteCacheJobEnv(options)}${cargoCredentialsJobEnv(options)}${privateNpmInstallJobEnv(options)}
470
479
  steps:
471
480
  `;
472
481
  }
@@ -717,6 +726,17 @@ function cargoCredentialsJobEnv(options: PublishWorkflowDefinitionOptions): stri
717
726
  return rendered === '' ? '' : `\n${rendered}`;
718
727
  }
719
728
 
729
+ /**
730
+ * The remote cache is job env for the same reason: every Nx target in the job
731
+ * reads it, and a release build that missed the cache would rebuild what CI
732
+ * already built for the identical hashes. Only `${{ secrets.NAME }}` is
733
+ * rendered for the token. Malformed declarations throw here, not in CI.
734
+ */
735
+ function remoteCacheJobEnv(options: PublishWorkflowDefinitionOptions): string {
736
+ const rendered = remoteCacheJobEnvLines(options.remoteCache).trimEnd();
737
+ return rendered === '' ? '' : `\n${rendered}`;
738
+ }
739
+
720
740
  function renderSingleJobPublishWorkflowSteps(
721
741
  steps: PublishWorkflowStep[],
722
742
  options: PublishWorkflowDefinitionOptions,
@@ -796,7 +816,7 @@ ${renderRunsOnLine(options.runsOn)}
796
816
  mode: ${githubExpression('steps.version.outputs.mode')}
797
817
  release-sha: ${githubExpression('steps.release-state.outputs.sha')}
798
818
  env:
799
- GH_TOKEN: ${githubExpression('github.token')}${cargoCredentialsJobEnv(options)}${privateNpmInstallJobEnv(options)}
819
+ GH_TOKEN: ${githubExpression('github.token')}${remoteCacheJobEnv(options)}${cargoCredentialsJobEnv(options)}${privateNpmInstallJobEnv(options)}
800
820
  steps:
801
821
  ${renderLinuxReleaseCandidateSteps(steps, options)}
802
822
 
@@ -806,7 +826,7 @@ ${renderMacosJobHeaderLines(options)}
806
826
  contents: read
807
827
  id-token: none
808
828
  env:
809
- GH_TOKEN: ${githubExpression('github.token')}${cargoCredentialsJobEnv(options)}${privateNpmInstallJobEnv(options)}
829
+ GH_TOKEN: ${githubExpression('github.token')}${remoteCacheJobEnv(options)}${cargoCredentialsJobEnv(options)}${privateNpmInstallJobEnv(options)}
810
830
  ${Object.entries(options.platformProducer?.env ?? {})
811
831
  .map(([name, value]) => ` ${name}: ${JSON.stringify(value)}`)
812
832
  .join('\n')}
@@ -821,7 +841,7 @@ ${publishJobRunsOnLine(options)}
821
841
  id-token: write
822
842
  env:
823
843
  TTSC_TSGO_BINARY: ${githubExpression('github.workspace')}/node_modules/@typescript/native/bin/tsc
824
- GH_TOKEN: ${githubExpression('github.token')}${cargoCredentialsJobEnv(options)}${privateNpmInstallJobEnv(options)}
844
+ GH_TOKEN: ${githubExpression('github.token')}${remoteCacheJobEnv(options)}${cargoCredentialsJobEnv(options)}${privateNpmInstallJobEnv(options)}
825
845
  steps:
826
846
  ${renderFinalLinuxPublishSteps(options)}
827
847
  `;
@@ -136,14 +136,18 @@ async function resolveInBootstrap(options: {
136
136
 
137
137
  /** Builds a fixture repository whose package.json and .npmrc are the resolver's real inputs. */
138
138
  async function withFixture(
139
- options: { secrets: Record<string, SecretSpec>; npmrc?: string },
139
+ options: { secrets: Record<string, SecretSpec>; npmrc?: string; remoteCache?: unknown },
140
140
  run: (root: string) => Promise<void>,
141
141
  ): Promise<void> {
142
142
  const root = await mkdtemp(join(tmpdir(), 'smoo-secret-references-'));
143
143
  try {
144
144
  await writeFile(
145
145
  join(root, 'package.json'),
146
- JSON.stringify({ name: 'fixture', version: '0.0.0', smoo: { secrets: options.secrets } }),
146
+ JSON.stringify({
147
+ name: 'fixture',
148
+ version: '0.0.0',
149
+ smoo: { secrets: options.secrets, remoteCache: options.remoteCache },
150
+ }),
147
151
  );
148
152
  if (options.npmrc !== undefined) {
149
153
  await writeFile(join(root, '.npmrc'), options.npmrc);
@@ -249,6 +253,28 @@ describe('resolveSecretEnvironment', () => {
249
253
  });
250
254
  });
251
255
 
256
+ it('an unreachable cache-token provider does not block the install, while other secrets still refuse', async () => {
257
+ await withFixture(
258
+ {
259
+ secrets: {
260
+ NX_REMOTE_CACHE_TOKEN: { command: ['definitely-not-a-real-smoo-binary-xyz'] },
261
+ SMOO_TOKEN: { command: emit('tok-from-provider') },
262
+ },
263
+ remoteCache: { server: 'https://nx-cache.example.net', tokenSecret: 'NX_REMOTE_CACHE_TOKEN' },
264
+ },
265
+ async (root) => {
266
+ // A cache is an optimization: its provider being down installs
267
+ // dependencies anyway. Every other declared secret keeps its refusal.
268
+ expect(await resolveInBootstrap({ root, env: {} })).toEqual({
269
+ resolved: { SMOO_TOKEN: 'tok-from-provider' },
270
+ });
271
+ const withoutCacheDeclaration = await resolveInBootstrap({ root, env: { CI: 'true' } });
272
+ expect(withoutCacheDeclaration.error).toContain('SMOO_TOKEN');
273
+ expect(withoutCacheDeclaration.error).not.toContain('NX_REMOTE_CACHE_TOKEN');
274
+ },
275
+ );
276
+ });
277
+
252
278
  it('a present variable is not reported while another refuses in CI', async () => {
253
279
  await withFixture(
254
280
  {
@@ -361,3 +387,150 @@ describe('resolveSecretEnvironment', () => {
361
387
  }
362
388
  });
363
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
+ });