@smoothbricks/cli 0.10.8 → 0.10.10

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 (45) hide show
  1. package/dist/cli.js +7 -5
  2. package/dist/github-ci/index.d.ts +9 -7
  3. package/dist/github-ci/index.d.ts.map +1 -1
  4. package/dist/github-ci/index.js +36 -35
  5. package/dist/monorepo/ci-workflow.d.ts +3 -0
  6. package/dist/monorepo/ci-workflow.d.ts.map +1 -1
  7. package/dist/monorepo/ci-workflow.js +59 -15
  8. package/dist/monorepo/managed-files.d.ts +1 -0
  9. package/dist/monorepo/managed-files.d.ts.map +1 -1
  10. package/dist/monorepo/managed-files.js +15 -5
  11. package/dist/monorepo/publish-workflow.d.ts +2 -2
  12. package/dist/monorepo/publish-workflow.d.ts.map +1 -1
  13. package/dist/monorepo/publish-workflow.js +7 -7
  14. package/dist/release/index.d.ts +1 -0
  15. package/dist/release/index.d.ts.map +1 -1
  16. package/dist/release/index.js +14 -6
  17. package/dist/wrangler/cloudflare.d.ts +4 -2
  18. package/dist/wrangler/cloudflare.d.ts.map +1 -1
  19. package/dist/wrangler/cloudflare.js +6 -3
  20. package/dist/wrangler/{deploy-environment.d.ts → deploy-stage.d.ts} +6 -6
  21. package/dist/wrangler/deploy-stage.d.ts.map +1 -0
  22. package/dist/wrangler/{deploy-environment.js → deploy-stage.js} +26 -26
  23. package/dist/wrangler/stage.d.ts +58 -0
  24. package/dist/wrangler/stage.d.ts.map +1 -0
  25. package/dist/wrangler/{environment.js → stage.js} +44 -44
  26. package/package.json +8 -8
  27. package/src/cli.ts +11 -8
  28. package/src/github-ci/index.test.ts +105 -34
  29. package/src/github-ci/index.ts +53 -46
  30. package/src/monorepo/__tests__/ci-workflow.test.ts +87 -49
  31. package/src/monorepo/__tests__/publish-workflow.test.ts +14 -12
  32. package/src/monorepo/ci-workflow.ts +67 -14
  33. package/src/monorepo/managed-files.test.ts +26 -5
  34. package/src/monorepo/managed-files.ts +18 -5
  35. package/src/monorepo/publish-workflow.ts +15 -12
  36. package/src/release/index.ts +12 -4
  37. package/src/wrangler/cloudflare.test.ts +76 -0
  38. package/src/wrangler/cloudflare.ts +6 -3
  39. package/src/wrangler/{deploy-environment.test.ts → deploy-stage.test.ts} +11 -11
  40. package/src/wrangler/{deploy-environment.ts → deploy-stage.ts} +41 -41
  41. package/src/wrangler/{environment.test.ts → stage.test.ts} +15 -15
  42. package/src/wrangler/{environment.ts → stage.ts} +49 -52
  43. package/dist/wrangler/deploy-environment.d.ts.map +0 -1
  44. package/dist/wrangler/environment.d.ts +0 -58
  45. package/dist/wrangler/environment.d.ts.map +0 -1
@@ -9,6 +9,7 @@ import { type NxProjects, targetNamesFromProjects } from '../nx/index.js';
9
9
  import {
10
10
  deployTargetInfoFromProjects,
11
11
  extractInlineLocalBlocksForTest,
12
+ hasExactTargetForTest,
12
13
  INLINE_LOCAL_BEGIN,
13
14
  INLINE_LOCAL_END,
14
15
  LOCAL_SECTION_MARKER,
@@ -128,7 +129,14 @@ describe('managed-file inline local blocks', () => {
128
129
  it("reinserting refuses when the anchor no longer appears — never silently drops the repo's customization", () => {
129
130
  const fresh = ['a:', ' - one', 'b:'].join('\n'); // ' - two' is gone
130
131
  expect(() => reinsertInlineLocalBlocksForTest(fresh, [{ anchor: ' - two', lines: ' - repo-owned' }])).toThrow(
131
- /no longer matches/,
132
+ /matches no line/,
133
+ );
134
+ });
135
+
136
+ it('reinserting refuses an ambiguous multiply occurring anchor', () => {
137
+ const fresh = ['anchor', 'middle', 'anchor'].join('\n');
138
+ expect(() => reinsertInlineLocalBlocksForTest(fresh, [{ anchor: 'anchor', lines: 'repo-owned' }])).toThrow(
139
+ /matches 2 lines/,
132
140
  );
133
141
  });
134
142
 
@@ -136,8 +144,7 @@ describe('managed-file inline local blocks', () => {
136
144
  // Lines that can serve as anchors: non-empty, not a marker, and each drawn
137
145
  // from a small alphabet so uniqueness is checkable — the round-trip only
138
146
  // holds when an anchor line occurs exactly once in the managed section
139
- // (reinsert finds the FIRST occurrence by design, same as the real file
140
- // shape: comments/patterns are unique in practice).
147
+ // because ambiguous anchors are rejected rather than selecting one.
141
148
  const linePool = fc.constantFrom('alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta');
142
149
  fc.assert(
143
150
  fc.property(
@@ -205,6 +212,20 @@ describe('nx graph project helpers', () => {
205
212
  );
206
213
  });
207
214
 
215
+ it('detects browser and deployment E2E targets by exact name only', () => {
216
+ const exact = targetNamesFromProjects({
217
+ app: { targets: { 'test-browser': {}, 'e2e-deployment': {} } },
218
+ });
219
+ const nearMisses = targetNamesFromProjects({
220
+ app: { targets: { 'test-browser-extra': {}, 'e2e-deployments': {}, 'pre-e2e-deployment': {} } },
221
+ });
222
+
223
+ expect(hasExactTargetForTest(exact, 'test-browser')).toBe(true);
224
+ expect(hasExactTargetForTest(exact, 'e2e-deployment')).toBe(true);
225
+ expect(hasExactTargetForTest(nearMisses, 'test-browser')).toBe(false);
226
+ expect(hasExactTargetForTest(nearMisses, 'e2e-deployment')).toBe(false);
227
+ });
228
+
208
229
  it('detects deploy configurations and cloudflare provider from graph nodes', () => {
209
230
  expect(deployTargetInfoFromProjects(sampleProjects, 'staging')).toEqual({
210
231
  exists: true,
@@ -217,12 +238,12 @@ describe('nx graph project helpers', () => {
217
238
  expect(deployTargetInfoFromProjects(sampleProjects, 'preview')).toEqual({ exists: false });
218
239
  });
219
240
 
220
- it('recognizes convention-driven deploy-environment targets without per-environment configurations', () => {
241
+ it('recognizes convention-driven deploy-stage targets without per-stage configurations', () => {
221
242
  const projects: NxProjects = {
222
243
  app: {
223
244
  targets: {
224
245
  deploy: {
225
- options: { command: 'smoo wrangler deploy-environment --environment {args.environment}' },
246
+ options: { command: 'smoo wrangler deploy-stage --stage {args.stage}' },
226
247
  },
227
248
  },
228
249
  },
@@ -105,13 +105,15 @@ function reinsertInlineLocalBlocks(content: string, blocks: InlineLocalBlock[]):
105
105
  if (blocks.length === 0) return content;
106
106
  const lines = content.split('\n');
107
107
  for (const block of blocks) {
108
- const index = lines.indexOf(block.anchor);
109
- if (index === -1) {
108
+ const matches = lines.flatMap((line, index) => (line === block.anchor ? [index] : []));
109
+ if (matches.length !== 1) {
110
+ const reason = matches.length === 0 ? 'no line' : `${matches.length} lines`;
110
111
  throw new Error(
111
- `${INLINE_LOCAL_BEGIN} block anchored on "${block.anchor}" no longer matches any line in the updated ` +
112
+ `${INLINE_LOCAL_BEGIN} block anchored on "${block.anchor}" matches ${reason} in the updated ` +
112
113
  'template — reconcile the repo-owned block manually',
113
114
  );
114
115
  }
116
+ const index = matches[0] as number;
115
117
  const markerIndent = block.markerIndent ?? '';
116
118
  lines.splice(
117
119
  index + 1,
@@ -136,6 +138,8 @@ interface ManagedFileContext {
136
138
  hasReleasePackages: boolean;
137
139
  hasStagingDeployTargets: boolean;
138
140
  hasProductionDeployTargets: boolean;
141
+ hasBrowserTestTargets: boolean;
142
+ hasE2eDeploymentTargets: boolean;
139
143
  stagingDeployProvider?: 'cloudflare';
140
144
  productionDeployProvider?: 'cloudflare';
141
145
  ciPushBranches: string[];
@@ -320,6 +324,8 @@ function getManagedContent(file: ManagedFile, context: ManagedFileContext): stri
320
324
  return renderCiWorkflowYaml({
321
325
  deploy: context.hasStagingDeployTargets,
322
326
  deployProvider: context.stagingDeployProvider,
327
+ browserTests: context.hasBrowserTestTargets,
328
+ e2eDeployment: context.hasStagingDeployTargets && context.hasE2eDeploymentTargets,
323
329
  pushBranches: context.ciPushBranches,
324
330
  runsOn: context.ciRunsOn,
325
331
  });
@@ -356,7 +362,8 @@ async function getManagedFileContext(root: string): Promise<ManagedFileContext>
356
362
  const nxProjects = await loadNxProjects(root);
357
363
  const stagingDeploy = deployTargetInfoFromProjects(nxProjects, 'staging');
358
364
  const productionDeploy = deployTargetInfoFromProjects(nxProjects, 'production');
359
- const platformTargetGlobs = platformTargetGlobsForTest(targetNamesFromProjects(nxProjects));
365
+ const targetNames = targetNamesFromProjects(nxProjects);
366
+ const platformTargetGlobs = platformTargetGlobsForTest(targetNames);
360
367
  const nodeModulesCacheKey = existsSync(join(root, 'bun.lock'))
361
368
  ? `$${"{{ hashFiles('bun.lock', 'package.json', 'packages/*/package.json') }}"}`
362
369
  : `$${"{{ hashFiles('bun.lockb', 'package.json', 'packages/*/package.json') }}"}`;
@@ -364,6 +371,8 @@ async function getManagedFileContext(root: string): Promise<ManagedFileContext>
364
371
  hasReleasePackages: listReleasePackages(root, packageJson).length > 0,
365
372
  hasStagingDeployTargets: stagingDeploy.exists,
366
373
  hasProductionDeployTargets: productionDeploy.exists,
374
+ hasBrowserTestTargets: hasExactTargetForTest(targetNames, 'test-browser'),
375
+ hasE2eDeploymentTargets: hasExactTargetForTest(targetNames, 'e2e-deployment'),
367
376
  stagingDeployProvider: stagingDeploy.provider,
368
377
  productionDeployProvider: productionDeploy.provider,
369
378
  ciPushBranches,
@@ -374,6 +383,10 @@ async function getManagedFileContext(root: string): Promise<ManagedFileContext>
374
383
  };
375
384
  }
376
385
 
386
+ export function hasExactTargetForTest(targetNames: Iterable<string>, target: string): boolean {
387
+ return [...targetNames].includes(target);
388
+ }
389
+
377
390
  export function platformTargetGlobsForTest(targetNames: Iterable<string>): string[] {
378
391
  const names = [...targetNames];
379
392
  return PLATFORM_TARGET_GLOBS.filter((glob) => {
@@ -404,7 +417,7 @@ function deployTargetInfoFromTargets(targets: Record<string, NxTargetConfig>, co
404
417
  }
405
418
  const baseCommandValue = deploy.options?.command ?? deploy.command;
406
419
  const baseCommand = typeof baseCommandValue === 'string' ? baseCommandValue : '';
407
- if (baseCommand.includes('smoo wrangler deploy-environment')) {
420
+ if (baseCommand.includes('smoo wrangler deploy-stage')) {
408
421
  return { exists: true, provider: 'cloudflare' };
409
422
  }
410
423
  const config = deploy.configurations?.[configuration];
@@ -18,7 +18,7 @@ const PUBLISH_WORKFLOW_FORMAT_OPTIONS = Object.freeze({
18
18
  export type PublishWorkflowBump = 'auto' | 'patch' | 'minor' | 'major' | 'prerelease';
19
19
  export type PublishWorkflowCondition = 'version-mode-not-none' | 'deploy-production' | 'failure' | 'always';
20
20
  export type PublishWorkflowNxTarget = 'build' | 'lint' | 'test';
21
- export type PublishWorkflowDeployEnvironment = 'none' | 'production';
21
+ export type PublishWorkflowDeployStage = 'none' | 'production';
22
22
 
23
23
  export enum PublishWorkflowStepKind {
24
24
  Checkout = 'checkout',
@@ -62,7 +62,7 @@ export interface PublishWorkflowDefinitionOptions {
62
62
 
63
63
  export interface PublishWorkflowInputs {
64
64
  bump: PublishWorkflowBump;
65
- deployEnvironment: PublishWorkflowDeployEnvironment;
65
+ deployStage: PublishWorkflowDeployStage;
66
66
  dryRun: boolean;
67
67
  }
68
68
 
@@ -263,7 +263,7 @@ function shouldRunStep(
263
263
  return version.mode !== 'none';
264
264
  }
265
265
  if (step.condition === 'deploy-production') {
266
- return version.mode !== 'none' && inputs.deployEnvironment === 'production' && !inputs.dryRun;
266
+ return version.mode !== 'none' && inputs.deployStage === 'production' && !inputs.dryRun;
267
267
  }
268
268
  if (step.condition === 'failure') {
269
269
  return failed;
@@ -291,7 +291,7 @@ function renderPublishWorkflowHeader(options: PublishWorkflowDefinitionOptions):
291
291
  const deployInput =
292
292
  options.deploy === true
293
293
  ? `
294
- deploy_environment:
294
+ deploy_stage:
295
295
  type: choice
296
296
  description: Deploy live systems after a successful publish.
297
297
  options: [none, production]
@@ -483,10 +483,10 @@ function deployProductionStep(step: PublishWorkflowStep, options: PublishWorkflo
483
483
  return [
484
484
  ` - name: ${step.name}`,
485
485
  ' if:',
486
- " ${{ steps.version.outputs.mode != 'none' && inputs.deploy_environment == 'production' && inputs.dry_run !=",
486
+ " ${{ steps.version.outputs.mode != 'none' && inputs.deploy_stage == 'production' && inputs.dry_run !=",
487
487
  " 'true' }}",
488
488
  ...deployEnvLines(options),
489
- ' run: smoo github-ci nx-deploy --environment production --mode run-many --verify --name "Deploy Production"',
489
+ ' run: smoo github-ci nx-deploy --stage production --mode run-many --verify --name "Deploy Production"',
490
490
  ];
491
491
  }
492
492
 
@@ -534,7 +534,7 @@ function renderPlatformPublishWorkflowYaml(options: PublishWorkflowDefinitionOpt
534
534
  const deployInput =
535
535
  options.deploy === true
536
536
  ? `
537
- deploy_environment:
537
+ deploy_stage:
538
538
  type: choice
539
539
  description: Deploy live systems after a successful publish.
540
540
  options: [none, production]
@@ -778,16 +778,19 @@ function renderMacosPlatformSteps(options: PublishWorkflowDefinitionOptions): st
778
778
  '',
779
779
  ` # Step ${stepNumber++}`,
780
780
  ' - name: 🍎 Build selected macOS and iOS release outputs',
781
+ ' id: platform-outputs',
781
782
  ' run:',
782
783
  ` smoo release build-platform-outputs --bump "${githubExpression(
783
784
  'inputs.bump',
784
785
  )}" --ref "${githubExpression('github.sha')}" --targets "${MACOS_PLATFORM_TARGET_GLOBS.join(',')}" --output`,
785
- ` "${githubExpression('runner.temp')}/macos-platform-outputs"`,
786
+ ` "${githubExpression('runner.temp')}/macos-platform-outputs" --github-output "$GITHUB_OUTPUT"`,
786
787
  '',
787
788
  ` # Step ${stepNumber++}`,
788
- ' - name: 🧪 Unit test macOS and iOS packages',
789
+ ' - name: 🧪 Unit test selected macOS and iOS packages',
790
+ ` if: steps.platform-outputs.outputs.projects != ''`,
789
791
  ' run:',
790
- ` smoo github-ci nx-run-many --targets test --projects-with-targets "${MACOS_PLATFORM_TARGET_GLOBS.join(',')}"`,
792
+ ' smoo github-ci nx-run-many --targets test --projects',
793
+ ` "${githubExpression('steps.platform-outputs.outputs.projects')}"`,
791
794
  '',
792
795
  ` # Step ${stepNumber++}`,
793
796
  ' - name: 📤 Upload macOS platform outputs',
@@ -937,10 +940,10 @@ function renderFinalLinuxPublishSteps(options: PublishWorkflowDefinitionOptions)
937
940
  ` # Step ${stepNumber++}`,
938
941
  ' - name: 🚀 Deploy production',
939
942
  ' if:',
940
- " ${{ needs.linux-release-candidate.outputs.mode != 'none' && inputs.deploy_environment == 'production' &&",
943
+ " ${{ needs.linux-release-candidate.outputs.mode != 'none' && inputs.deploy_stage == 'production' &&",
941
944
  " inputs.dry_run != 'true' }}",
942
945
  ...deployEnvLines(options),
943
- ' run: smoo github-ci nx-deploy --environment production --mode run-many --verify --name "Deploy Production"',
946
+ ' run: smoo github-ci nx-deploy --stage production --mode run-many --verify --name "Deploy Production"',
944
947
  );
945
948
  }
946
949
  lines.push(
@@ -80,6 +80,7 @@ export interface ReleaseRepairPendingOptions {
80
80
 
81
81
  export interface ReleasePlatformOutputsOptions {
82
82
  bump: string;
83
+ githubOutput?: string;
83
84
  output: string;
84
85
  ref?: string;
85
86
  targets: string;
@@ -221,7 +222,7 @@ export async function releaseCollectPlatformOutputs(
221
222
  : `Release platform outputs: building current outputs for ${packageSummary(currentPackages)}.`,
222
223
  );
223
224
  const outputRoot = resolve(root, options.output);
224
- await githubCiNxRunMany(root, {
225
+ const currentRuns = await githubCiNxRunMany(root, {
225
226
  targets: options.targets,
226
227
  projects: releasePackageProjects(currentPackages),
227
228
  collectOutputs: join(outputRoot, 'current'),
@@ -236,6 +237,12 @@ export async function releaseCollectPlatformOutputs(
236
237
  targets,
237
238
  restoreRef,
238
239
  );
240
+ if (options.githubOutput) {
241
+ const projects = [
242
+ ...new Set(currentRuns.runs.flatMap((run) => run.projects.map((project) => project.project))),
243
+ ].sort((left, right) => left.localeCompare(right));
244
+ await appendFile(options.githubOutput, `projects=${projects.join(',')}\n`);
245
+ }
239
246
  }
240
247
 
241
248
  export async function releaseTrustPublisher(root: string, options: ReleaseTrustPublisherOptions): Promise<void> {
@@ -966,12 +973,13 @@ function releaseRepairOutputsShell(
966
973
  ): ReleaseRepairOutputsShell<ReleasePackage> {
967
974
  return {
968
975
  ...releaseTargetCheckoutShell(root),
969
- collectRepairTargetOutputs: (target) =>
970
- githubCiNxRunMany(root, {
976
+ collectRepairTargetOutputs: async (target) => {
977
+ await githubCiNxRunMany(root, {
971
978
  targets: targetGlobs,
972
979
  projects: releasePackageProjects(target.npmPackages),
973
980
  collectOutputs: join(outputRoot, target.sha),
974
- }),
981
+ });
982
+ },
975
983
  };
976
984
  }
977
985
 
@@ -0,0 +1,76 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+ import { CloudflareApiError, CloudflareRestClient } from './cloudflare.js';
3
+
4
+ type CloudflareFetcher = NonNullable<ConstructorParameters<typeof CloudflareRestClient>[2]>;
5
+
6
+ function jsonFetcher(body: unknown, status = 200): CloudflareFetcher {
7
+ return async () =>
8
+ new Response(JSON.stringify(body), {
9
+ status,
10
+ headers: { 'Content-Type': 'application/json' },
11
+ });
12
+ }
13
+
14
+ describe('CloudflareRestClient', () => {
15
+ it('accepts nullable diagnostics in successful API envelopes', async () => {
16
+ const client = new CloudflareRestClient(
17
+ 'account-id',
18
+ 'api-token',
19
+ jsonFetcher({
20
+ result: [
21
+ {
22
+ id: 'domain-id',
23
+ hostname: 'app.pr45.example.com',
24
+ service: 'app-pr45',
25
+ },
26
+ ],
27
+ success: true,
28
+ errors: null,
29
+ messages: null,
30
+ result_info: {
31
+ page: 1,
32
+ per_page: 1000,
33
+ count: 1,
34
+ total_count: 1,
35
+ },
36
+ }),
37
+ );
38
+
39
+ await expect(client.listWorkerDomains()).resolves.toEqual([
40
+ {
41
+ id: 'domain-id',
42
+ hostname: 'app.pr45.example.com',
43
+ service: 'app-pr45',
44
+ },
45
+ ]);
46
+ });
47
+
48
+ it('preserves structured Cloudflare API failures', async () => {
49
+ const client = new CloudflareRestClient(
50
+ 'account-id',
51
+ 'api-token',
52
+ jsonFetcher(
53
+ {
54
+ result: null,
55
+ success: false,
56
+ errors: [{ code: 10000, message: 'Authentication error' }],
57
+ messages: [],
58
+ },
59
+ 403,
60
+ ),
61
+ );
62
+
63
+ try {
64
+ await client.listWorkerDomains();
65
+ throw new Error('expected listWorkerDomains to fail');
66
+ } catch (error) {
67
+ expect(error).toBeInstanceOf(CloudflareApiError);
68
+ expect(error).toMatchObject({
69
+ message:
70
+ 'Cloudflare API /accounts/account-id/workers/domains?per_page=1000&page=1 failed: Authentication error',
71
+ status: 403,
72
+ codes: [10000],
73
+ });
74
+ }
75
+ });
76
+ });
@@ -1,5 +1,5 @@
1
1
  import typia from 'typia';
2
- import type { LiveKvNamespace } from './environment.js';
2
+ import type { LiveKvNamespace } from './stage.js';
3
3
 
4
4
  export interface R2Bucket {
5
5
  name: string;
@@ -60,7 +60,8 @@ export interface CloudflareClient {
60
60
  interface CloudflareEnvelope {
61
61
  success: boolean;
62
62
  result?: unknown;
63
- errors?: Array<{ code?: number; message?: string }>;
63
+ errors?: Array<{ code?: number; message?: string }> | null;
64
+ messages?: Array<{ code?: number; message?: string }> | null;
64
65
  result_info?: {
65
66
  page?: number;
66
67
  total_pages?: number;
@@ -92,13 +93,15 @@ export class CloudflareApiError extends Error {
92
93
  }
93
94
  }
94
95
 
96
+ type CloudflareFetcher = (input: string, init?: RequestInit) => Promise<Response>;
97
+
95
98
  export class CloudflareRestClient implements CloudflareClient {
96
99
  private readonly accountPath: string;
97
100
 
98
101
  constructor(
99
102
  accountId: string,
100
103
  private readonly apiToken: string,
101
- private readonly fetcher: typeof fetch = fetch,
104
+ private readonly fetcher: CloudflareFetcher = fetch,
102
105
  ) {
103
106
  if (!accountId || !apiToken) {
104
107
  throw new Error('CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN are required.');
@@ -12,8 +12,8 @@ import type {
12
12
  WorkerRoute,
13
13
  WorkerScript,
14
14
  } from './cloudflare.js';
15
- import { cleanupPullRequest, deployEnvironment, type ProcessResult, type ProcessRunner } from './deploy-environment.js';
16
- import type { LiveKvNamespace } from './environment.js';
15
+ import { cleanupPullRequest, deployStage, type ProcessResult, type ProcessRunner } from './deploy-stage.js';
16
+ import type { LiveKvNamespace } from './stage.js';
17
17
 
18
18
  const HASH = '16577780061662788004';
19
19
  const FIXTURE = `[env.staging]
@@ -154,14 +154,14 @@ class FakeCloudflare implements CloudflareClient {
154
154
  }
155
155
  }
156
156
 
157
- describe('deploy-environment remote version fallback', () => {
157
+ describe('deploy-stage remote version fallback', () => {
158
158
  it('returns a remote cache hit for the active tagged 100% version', async () => {
159
159
  const root = await fixtureRoot();
160
160
  const runner = new FakeRunner([{ id: 'version-1', annotations: { 'workers/tag': `nx-${HASH}` } }], {
161
161
  versions: [{ version_id: 'version-1', percentage: 100 }],
162
162
  });
163
163
 
164
- const result = await deployEnvironment(root, 'pr123', dependencies(runner, new FakeCloudflare()));
164
+ const result = await deployStage(root, 'pr123', dependencies(runner, new FakeCloudflare()));
165
165
 
166
166
  expect(result.action).toBe('remote-cache-hit');
167
167
  expect(runner.calls.map((args) => args.slice(0, 2))).toEqual([
@@ -176,7 +176,7 @@ describe('deploy-environment remote version fallback', () => {
176
176
  versions: [{ version_id: 'version-2', percentage: 100 }],
177
177
  });
178
178
 
179
- const result = await deployEnvironment(root, 'pr123', dependencies(runner, new FakeCloudflare()));
179
+ const result = await deployStage(root, 'pr123', dependencies(runner, new FakeCloudflare()));
180
180
 
181
181
  expect(result.action).toBe('activated');
182
182
  expect(runner.calls.at(-1)?.slice(0, 3)).toEqual(['versions', 'deploy', '--version-tag']);
@@ -189,7 +189,7 @@ describe('deploy-environment remote version fallback', () => {
189
189
  await writeFile(join(root, '.dev.vars.example'), 'OAUTH_STATE_SIGNING_KEY=""\nTOKEN_ENCRYPTION_KEY=""\n');
190
190
  const runner = new FakeRunner([], { versions: [{ version_id: 'version-2', percentage: 100 }] });
191
191
 
192
- const result = await deployEnvironment(root, 'pr123', {
192
+ const result = await deployStage(root, 'pr123', {
193
193
  ...dependencies(runner, new FakeCloudflare()),
194
194
  processEnv: {
195
195
  CLOUDFLARE_ACCOUNT_ID: 'account-1',
@@ -214,13 +214,13 @@ describe('deploy-environment remote version fallback', () => {
214
214
  expect(existsSync(requiredTestValue(runner.secretsPathSeen, 'secrets path'))).toBe(false);
215
215
  });
216
216
 
217
- it('passes only present manifest values for fixed environments and preserves absent remote secrets', async () => {
217
+ it('passes only present manifest values for fixed stages and preserves absent remote secrets', async () => {
218
218
  const root = await fixtureRoot();
219
219
  await writeFile(join(root, '.dev.vars.example'), 'OAUTH_STATE_SIGNING_KEY=""\nTOKEN_ENCRYPTION_KEY=""\n');
220
220
  const runner = new FakeRunner([], {});
221
221
  const cloudflare = new FakeCloudflare();
222
222
 
223
- const result = await deployEnvironment(root, 'staging', {
223
+ const result = await deployStage(root, 'staging', {
224
224
  runner,
225
225
  cloudflare,
226
226
  processEnv: {
@@ -244,7 +244,7 @@ describe('deploy-environment remote version fallback', () => {
244
244
  cloudflare.scripts = [];
245
245
 
246
246
  await expect(
247
- deployEnvironment(root, 'pr123', {
247
+ deployStage(root, 'pr123', {
248
248
  ...dependencies(new FakeRunner([], {}), cloudflare),
249
249
  processEnv: {
250
250
  CLOUDFLARE_ACCOUNT_ID: 'account-1',
@@ -268,7 +268,7 @@ describe('deploy-environment remote version fallback', () => {
268
268
  throw new Error('record already exists');
269
269
  };
270
270
 
271
- const result = await deployEnvironment(root, 'pr123', dependencies(new FakeRunner([], {}), cloudflare));
271
+ const result = await deployStage(root, 'pr123', dependencies(new FakeRunner([], {}), cloudflare));
272
272
 
273
273
  expect(result.action).toBe('deployed');
274
274
  expect(createAttempts).toBe(1);
@@ -276,7 +276,7 @@ describe('deploy-environment remote version fallback', () => {
276
276
  });
277
277
  });
278
278
 
279
- describe('cleanup-pr exact environment matching', () => {
279
+ describe('cleanup-pr exact stage matching', () => {
280
280
  it('rejects an invalid PR before touching the client', async () => {
281
281
  const cloudflare = new FakeCloudflare();
282
282
  let calls = 0;