@smoothbricks/cli 0.11.12 → 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.
@@ -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
+ });