@smoothbricks/cli 0.11.18 → 0.11.19
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/README.md +23 -6
- package/dist/github-ci/index.d.ts.map +1 -1
- package/dist/github-ci/index.js +6 -20
- package/dist/lib/deploy-tags.d.ts +17 -3
- package/dist/lib/deploy-tags.d.ts.map +1 -1
- package/dist/lib/deploy-tags.js +23 -4
- package/dist/monorepo/managed-files.d.ts +5 -2
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +35 -21
- package/package.json +2 -2
- package/src/github-ci/index.test.ts +6 -7
- package/src/github-ci/index.ts +11 -19
- package/src/lib/deploy-tags.ts +22 -4
- package/src/monorepo/managed-files.test.ts +69 -37
- package/src/monorepo/managed-files.ts +38 -21
package/README.md
CHANGED
|
@@ -430,11 +430,28 @@ access makes cleanup refuse before it deletes anything rather than half-clean a
|
|
|
430
430
|
A wrong value type anywhere in `smoo.github` fails `smoo monorepo update` and `smoo github-ci nx-deploy` with the
|
|
431
431
|
offending path, rather than silently falling back to the defaults.
|
|
432
432
|
|
|
433
|
+
#### What CI deploys: the deploy tags
|
|
434
|
+
|
|
435
|
+
A `deploy` target says a project CAN be deployed. Four Nx tags say whether CI does, and they are the only thing that
|
|
436
|
+
says so: the generated workflows and `smoo github-ci nx-deploy` read one rule, so a repository cannot render a deploy
|
|
437
|
+
job that deploys nothing, nor deploy a project no job announced.
|
|
438
|
+
|
|
439
|
+
- `stage-deploy-target`: deployed on every stage — pull-request previews, staging, and production.
|
|
440
|
+
- `staging-deploy-target`: deployed on staging only, for infrastructure the pull-request stages share.
|
|
441
|
+
- `permanent-deploy-target`: excluded from every stage deploy; deployed outside the stage flow.
|
|
442
|
+
- `production-push-deploy-target`: also deployed by the generated production-on-push job (below).
|
|
443
|
+
|
|
444
|
+
An untagged `deploy` target is deployable by hand — `nx run <project>:deploy --stage=pr42` — and invisible to CI. A
|
|
445
|
+
repository where no project carries one of these tags gets no deploy step, no `deployments: write` permission, no
|
|
446
|
+
Cloudflare credentials and no `pr-preview-cleanup.yml`, however many wrangler manifests its packages hold: a published
|
|
447
|
+
library ships one to document a Durable Object binding for its consumers, and that manifest looks exactly like a
|
|
448
|
+
deployable worker's.
|
|
449
|
+
|
|
433
450
|
Tag a project `production-push-deploy-target` to have it deployed to production by a generated `deploy-production` job
|
|
434
451
|
that runs after Validate and the e2e job succeed on a push to the staging push branch
|
|
435
|
-
(`smoo github-ci nx-deploy --stage production --select-tag production-push-deploy-target`). The project must also
|
|
436
|
-
|
|
437
|
-
|
|
452
|
+
(`smoo github-ci nx-deploy --stage production --select-tag production-push-deploy-target`). The project must also carry
|
|
453
|
+
`stage-deploy-target`, otherwise `--select-tag` finds nothing and the job logs
|
|
454
|
+
`No run-many deploy projects; skipping production.`
|
|
438
455
|
|
|
439
456
|
#### Ordering one deploy after another
|
|
440
457
|
|
|
@@ -452,9 +469,9 @@ drop.
|
|
|
452
469
|
|
|
453
470
|
What makes the edge safe is that a deploy is never cached and always cheap when there is nothing to do:
|
|
454
471
|
|
|
455
|
-
- `@smoothbricks/nx-plugin` gives
|
|
456
|
-
|
|
457
|
-
|
|
472
|
+
- `@smoothbricks/nx-plugin` gives the `deploy` it infers `cache: false`. An Nx cache hit on a deploy means "we once
|
|
473
|
+
uploaded this hash", which a rollback silently falsifies — the workspace is unchanged, so the hash is unchanged, so a
|
|
474
|
+
cached deploy would report success while the previous version keeps serving.
|
|
458
475
|
- `smoo wrangler deploy-stage` reads live state first. If the version tagged with this task's hash is already the one
|
|
459
476
|
serving 100% of traffic, it returns `remote-cache-hit` after two API calls: no upload, no migration, no traffic shift.
|
|
460
477
|
- The expensive, purely file-derived half belongs in a sibling `deploy-build` target (build the artifact, register it,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/github-ci/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,iBAAiB,EAGvB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,KAAK,cAAc,EAAsB,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAE,KAAK,eAAe,EAA8D,MAAM,sBAAsB,CAAC;AAExH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,MAAM,WAAW,yBAAyB;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE;QACX,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,YAAY,CAAC,EAAE;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE;YACL,IAAI,CAAC,EAAE;gBACL,SAAS,CAAC,EAAE,MAAM,CAAC;aACpB,CAAC;SACH,CAAC;KACH,CAAC;CACH;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/github-ci/index.ts"],"names":[],"mappings":"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"}
|
package/dist/github-ci/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { appendFile } from 'node:fs/promises';
|
|
|
4
4
|
import { PLATFORM_TARGET_GLOBS } from '@smoothbricks/nx-plugin/workspace-config-policy';
|
|
5
5
|
import { $ } from 'bun';
|
|
6
6
|
import typia from 'typia';
|
|
7
|
-
import {
|
|
7
|
+
import { PERMANENT_DEPLOY_TAG, STAGING_DEPLOY_TAG, stageDeploysProject } from '../lib/deploy-tags.js';
|
|
8
8
|
import { ciPushBranches, isNonEmpty, parseStringArrayText, readSmooGithub, } from '../lib/json.js';
|
|
9
9
|
import { decode, printCommandOutput, run, runStatus, runText } from '../lib/run.js';
|
|
10
10
|
import { readProjectTargets } from '../nx/index.js';
|
|
@@ -25,19 +25,11 @@ const isGithubDeployment = (() => {
|
|
|
25
25
|
return input => "object" === typeof input && null !== input && _io0(input);
|
|
26
26
|
})();
|
|
27
27
|
const isNxProjectDeployTarget = (() => {
|
|
28
|
-
const
|
|
29
|
-
const _io0 = input => true && (undefined === input.targets || "object" === typeof input.targets && null !== input.targets && false === Array.isArray(input.targets) && _io1(input.targets));
|
|
30
|
-
const _io1 = input => undefined === input.deploy || "object" === typeof input.deploy && null !== input.deploy && false === Array.isArray(input.deploy) && _io2(input.deploy);
|
|
31
|
-
const _io2 = input => _ip0(input) && (undefined === input.options || "object" === typeof input.options && null !== input.options && false === Array.isArray(input.options) && _io3(input.options));
|
|
32
|
-
const _io3 = input => _ip0(input);
|
|
28
|
+
const _io0 = input => undefined === input.tags || Array.isArray(input.tags) && input.tags.every(elem => "string" === typeof elem);
|
|
33
29
|
return input => "object" === typeof input && null !== input && false === Array.isArray(input) && _io0(input);
|
|
34
30
|
})();
|
|
35
31
|
const parseNxProjectDeployTarget = (() => {
|
|
36
|
-
const
|
|
37
|
-
const _io0 = input => true && (undefined === input.targets || "object" === typeof input.targets && null !== input.targets && false === Array.isArray(input.targets) && _io1(input.targets));
|
|
38
|
-
const _io1 = input => undefined === input.deploy || "object" === typeof input.deploy && null !== input.deploy && false === Array.isArray(input.deploy) && _io2(input.deploy);
|
|
39
|
-
const _io2 = input => _ip0(input) && (undefined === input.options || "object" === typeof input.options && null !== input.options && false === Array.isArray(input.options) && _io3(input.options));
|
|
40
|
-
const _io3 = input => _ip0(input);
|
|
32
|
+
const _io0 = input => undefined === input.tags || Array.isArray(input.tags) && input.tags.every(elem => "string" === typeof elem);
|
|
41
33
|
const __is = input => "object" === typeof input && null !== input && false === Array.isArray(input) && _io0(input);
|
|
42
34
|
return input => { input = JSON.parse(input); return __is(input) ? input : null; };
|
|
43
35
|
})();
|
|
@@ -434,17 +426,11 @@ export async function selectStageDeployProjects(candidates, stage, requireTag, l
|
|
|
434
426
|
const definition = await loadProject(project);
|
|
435
427
|
if (!isNxProjectDeployTarget(definition))
|
|
436
428
|
continue;
|
|
437
|
-
const
|
|
438
|
-
const commandValue = deploy?.options?.command ?? deploy?.command;
|
|
439
|
-
const tags = Array.isArray(definition.tags)
|
|
440
|
-
? definition.tags.filter((tag) => typeof tag === 'string')
|
|
441
|
-
: [];
|
|
442
|
-
const isStageDerived = isStageDerivedDeploy(tags, typeof commandValue === 'string' ? commandValue : undefined);
|
|
443
|
-
const isStagingOnly = tags.includes(STAGING_DEPLOY_TAG);
|
|
429
|
+
const tags = definition.tags;
|
|
444
430
|
// A required tag narrows the stage rules; it never selects a project they exclude.
|
|
445
|
-
if (requireTag && !tags
|
|
431
|
+
if (requireTag && !tags?.includes(requireTag))
|
|
446
432
|
continue;
|
|
447
|
-
if (
|
|
433
|
+
if (stageDeploysProject(tags, stage)) {
|
|
448
434
|
selected.push(project);
|
|
449
435
|
}
|
|
450
436
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DeploymentStage } from '../wrangler/stage.js';
|
|
1
2
|
/** Deployed on staging only: infrastructure the pull-request stages share. */
|
|
2
3
|
export declare const STAGING_DEPLOY_TAG = "staging-deploy-target";
|
|
3
4
|
/** Excluded from every stage deploy; deployed outside the stage flow. */
|
|
@@ -5,8 +6,21 @@ export declare const PERMANENT_DEPLOY_TAG = "permanent-deploy-target";
|
|
|
5
6
|
/** Opts a project into the generated production-on-push deploy job. */
|
|
6
7
|
export declare const PRODUCTION_PUSH_DEPLOY_TAG = "production-push-deploy-target";
|
|
7
8
|
/**
|
|
8
|
-
* Whether
|
|
9
|
-
*
|
|
9
|
+
* Whether the stage flow deploys a project, from its TAGS alone — the one rule
|
|
10
|
+
* CI selection and workflow generation both read, so a generated deploy job and
|
|
11
|
+
* the projects that job deploys cannot disagree.
|
|
12
|
+
*
|
|
13
|
+
* Having a `deploy` target is not that answer. A deploy target says a project
|
|
14
|
+
* CAN be deployed; only a tag says CI does it. A published library ships a
|
|
15
|
+
* wrangler manifest to document a Durable Object binding for its consumers, and
|
|
16
|
+
* that manifest is structurally identical to a deployable worker's — so the
|
|
17
|
+
* plugin infers `deploy` from one, and a repository that deploys nothing from CI
|
|
18
|
+
* would otherwise generate a deploy job, with cloud credentials, for it.
|
|
19
|
+
*
|
|
20
|
+
* The three tags compose as precedence, not as a set: `permanent` excludes a
|
|
21
|
+
* project from every stage, `staging` narrows it to one, `stage` generalizes it
|
|
22
|
+
* to all. `production-push` selects WITHIN this answer (see `--select-tag`) and
|
|
23
|
+
* never widens it.
|
|
10
24
|
*/
|
|
11
|
-
export declare function
|
|
25
|
+
export declare function stageDeploysProject(tags: string[] | undefined, stage: DeploymentStage): boolean;
|
|
12
26
|
//# sourceMappingURL=deploy-tags.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy-tags.d.ts","sourceRoot":"","sources":["../../src/lib/deploy-tags.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deploy-tags.d.ts","sourceRoot":"","sources":["../../src/lib/deploy-tags.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAK5D,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAE1D,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,4BAA4B,CAAC;AAE9D,uEAAuE;AACvE,eAAO,MAAM,0BAA0B,kCAAkC,CAAC;AAE1E;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAK/F"}
|
package/dist/lib/deploy-tags.js
CHANGED
|
@@ -9,9 +9,28 @@ export const PERMANENT_DEPLOY_TAG = 'permanent-deploy-target';
|
|
|
9
9
|
/** Opts a project into the generated production-on-push deploy job. */
|
|
10
10
|
export const PRODUCTION_PUSH_DEPLOY_TAG = 'production-push-deploy-target';
|
|
11
11
|
/**
|
|
12
|
-
* Whether
|
|
13
|
-
*
|
|
12
|
+
* Whether the stage flow deploys a project, from its TAGS alone — the one rule
|
|
13
|
+
* CI selection and workflow generation both read, so a generated deploy job and
|
|
14
|
+
* the projects that job deploys cannot disagree.
|
|
15
|
+
*
|
|
16
|
+
* Having a `deploy` target is not that answer. A deploy target says a project
|
|
17
|
+
* CAN be deployed; only a tag says CI does it. A published library ships a
|
|
18
|
+
* wrangler manifest to document a Durable Object binding for its consumers, and
|
|
19
|
+
* that manifest is structurally identical to a deployable worker's — so the
|
|
20
|
+
* plugin infers `deploy` from one, and a repository that deploys nothing from CI
|
|
21
|
+
* would otherwise generate a deploy job, with cloud credentials, for it.
|
|
22
|
+
*
|
|
23
|
+
* The three tags compose as precedence, not as a set: `permanent` excludes a
|
|
24
|
+
* project from every stage, `staging` narrows it to one, `stage` generalizes it
|
|
25
|
+
* to all. `production-push` selects WITHIN this answer (see `--select-tag`) and
|
|
26
|
+
* never widens it.
|
|
14
27
|
*/
|
|
15
|
-
export function
|
|
16
|
-
|
|
28
|
+
export function stageDeploysProject(tags, stage) {
|
|
29
|
+
if (tags === undefined)
|
|
30
|
+
return false;
|
|
31
|
+
if (tags.includes(PERMANENT_DEPLOY_TAG))
|
|
32
|
+
return false;
|
|
33
|
+
if (tags.includes(STAGING_DEPLOY_TAG))
|
|
34
|
+
return stage === 'staging';
|
|
35
|
+
return tags.includes(STAGE_DEPLOY_TAG);
|
|
17
36
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type NonEmptyArray, type PackageCargoCredentialsConfig, type PackagePrivateNpmConfig, type PackageRemoteCacheConfig, type PackageSmooGithub, type PackageSmooGithubEnvironments, type PackageSourceCheckoutConfig } from '../lib/json.js';
|
|
2
2
|
import { type NxProjects } from '../nx/index.js';
|
|
3
|
+
import type { DeploymentStage } from '../wrangler/stage.js';
|
|
3
4
|
import { type CiCrossTestArchive } from './ci-workflow.js';
|
|
4
5
|
/**
|
|
5
6
|
* Repos may append their own content to a managed file below this marker —
|
|
@@ -97,6 +98,8 @@ export declare const managedFileTargetsForTest: {
|
|
|
97
98
|
target: string;
|
|
98
99
|
executable: boolean | undefined;
|
|
99
100
|
}[];
|
|
101
|
+
/** Test seam: the managed targets a repository of this shape gets, in file order. */
|
|
102
|
+
export declare function managedFileTargetsForContext(context: ManagedFileContext): string[];
|
|
100
103
|
export declare function applyManagedFiles(root: string, mode: 'update' | 'check' | 'diff'): Promise<FileResult[]>;
|
|
101
104
|
/** Renders a generated workflow through its real managed-file descriptor, so the context wiring is covered. */
|
|
102
105
|
export declare function renderManagedWorkflowForTest(source: 'ci-workflow' | 'publish-workflow', context: ManagedFileContext): string;
|
|
@@ -134,8 +137,8 @@ export declare function platformTargetGlobsForTest(targetNames: Iterable<string>
|
|
|
134
137
|
* platform builds serialize no matter how many workers Nx is given.
|
|
135
138
|
*/
|
|
136
139
|
export declare function macosPlatformArchitecturesForTest(targetNames: Iterable<string>): string[];
|
|
137
|
-
/** Test seam:
|
|
138
|
-
export declare function deployTargetInfoFromProjects(projects: NxProjects,
|
|
140
|
+
/** Test seam: what the stage flow deploys, and with which provider, from graph nodes. */
|
|
141
|
+
export declare function deployTargetInfoFromProjects(projects: NxProjects, stage: DeploymentStage): DeployTargetInfo;
|
|
139
142
|
/** Test seam: whether any project in the graph carries the Nx tag. */
|
|
140
143
|
export declare function anyProjectHasTagForTest(projects: NxProjects, tag: string): boolean;
|
|
141
144
|
export declare function printResults(results: FileResult[]): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"managed-files.d.ts","sourceRoot":"","sources":["../../src/monorepo/managed-files.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"managed-files.d.ts","sourceRoot":"","sources":["../../src/monorepo/managed-files.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { cargoCrossTestArchiveFile, cargoCrossTestTripleFromArchiveTarget, } from '@smoothbricks/nx-plugin/cross-check-policy';
|
|
5
5
|
import { MACOS_PLATFORM_TARGET_GLOBS, PLATFORM_TARGET_GLOBS } from '@smoothbricks/nx-plugin/workspace-config-policy';
|
|
6
|
-
import {
|
|
6
|
+
import { PRODUCTION_PUSH_DEPLOY_TAG, stageDeploysProject } from '../lib/deploy-tags.js';
|
|
7
7
|
import { ciPushBranches, readValidatedPackageJson, } from '../lib/json.js';
|
|
8
8
|
import { listReleasePackages, packageInfo } from '../lib/workspace.js';
|
|
9
9
|
import { loadNxProjects, projectRootFromNxProjectJson, targetNamesFromNxProjectJson, targetNamesFromProjects, } from '../nx/index.js';
|
|
@@ -236,15 +236,27 @@ const managedFiles = [
|
|
|
236
236
|
];
|
|
237
237
|
export const managedFileTargetsForTest = managedFiles.map(({ target, executable }) => ({ target, executable }));
|
|
238
238
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
239
|
+
/**
|
|
240
|
+
* Whether a repository of this shape has the file at all. A repository that owns
|
|
241
|
+
* no packages has nothing to publish, and one the stage flow does not deploy to
|
|
242
|
+
* Cloudflare has no preview stage to clean up.
|
|
243
|
+
*/
|
|
244
|
+
function managedFileApplies(file, context) {
|
|
245
|
+
if (file.releasePackagesOnly === true && !context.hasReleasePackages && !context.hasProductionDeployTargets) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
return file.cloudflareDeployOnly !== true || context.stagingDeployProvider === 'cloudflare';
|
|
249
|
+
}
|
|
250
|
+
/** Test seam: the managed targets a repository of this shape gets, in file order. */
|
|
251
|
+
export function managedFileTargetsForContext(context) {
|
|
252
|
+
return managedFiles.filter((file) => managedFileApplies(file, context)).map((file) => file.target);
|
|
253
|
+
}
|
|
239
254
|
export async function applyManagedFiles(root, mode) {
|
|
240
255
|
const context = await getManagedFileContext(root);
|
|
241
256
|
return managedFiles.map((file) => applyManagedFile(root, file, mode, context));
|
|
242
257
|
}
|
|
243
258
|
function applyManagedFile(root, file, mode, context) {
|
|
244
|
-
if (file
|
|
245
|
-
return { target: file.target, action: 'skipped' };
|
|
246
|
-
}
|
|
247
|
-
if (file.cloudflareDeployOnly === true && context.stagingDeployProvider !== 'cloudflare') {
|
|
259
|
+
if (!managedFileApplies(file, context)) {
|
|
248
260
|
return { target: file.target, action: 'skipped' };
|
|
249
261
|
}
|
|
250
262
|
const target = resolve(root, file.target);
|
|
@@ -483,12 +495,12 @@ export function macosPlatformArchitecturesForTest(targetNames) {
|
|
|
483
495
|
}
|
|
484
496
|
return [...architectures].sort((left, right) => left.localeCompare(right));
|
|
485
497
|
}
|
|
486
|
-
/** Test seam:
|
|
487
|
-
export function deployTargetInfoFromProjects(projects,
|
|
498
|
+
/** Test seam: what the stage flow deploys, and with which provider, from graph nodes. */
|
|
499
|
+
export function deployTargetInfoFromProjects(projects, stage) {
|
|
488
500
|
let exists = false;
|
|
489
501
|
let provider;
|
|
490
502
|
for (const project of Object.values(projects)) {
|
|
491
|
-
const info = deployTargetInfoFromProject(project,
|
|
503
|
+
const info = deployTargetInfoFromProject(project, stage);
|
|
492
504
|
if (!info.exists) {
|
|
493
505
|
continue;
|
|
494
506
|
}
|
|
@@ -501,22 +513,24 @@ export function deployTargetInfoFromProjects(projects, configuration) {
|
|
|
501
513
|
export function anyProjectHasTagForTest(projects, tag) {
|
|
502
514
|
return Object.values(projects).some((project) => project.tags?.includes(tag));
|
|
503
515
|
}
|
|
504
|
-
|
|
516
|
+
/**
|
|
517
|
+
* A generated deploy job exists for the projects CI would actually deploy, which
|
|
518
|
+
* is `stageDeploysProject` and nothing else — the same rule `smoo github-ci
|
|
519
|
+
* nx-deploy` selects by, so the job and its selection cannot disagree. Reading
|
|
520
|
+
* the target's presence instead would render a deploy job, with cloud
|
|
521
|
+
* credentials, into a repository whose only wrangler manifests document a
|
|
522
|
+
* Durable Object binding for a published library's consumers.
|
|
523
|
+
*
|
|
524
|
+
* The command is read for the PROVIDER only: which cloud CLI runs says which
|
|
525
|
+
* credentials the job needs, and says nothing about whether CI runs it.
|
|
526
|
+
*/
|
|
527
|
+
function deployTargetInfoFromProject(project, stage) {
|
|
505
528
|
const deploy = project.targets?.deploy;
|
|
506
|
-
if (!deploy) {
|
|
507
|
-
return { exists: false };
|
|
508
|
-
}
|
|
509
|
-
const baseCommandValue = deploy.options?.command ?? deploy.command;
|
|
510
|
-
const baseCommand = typeof baseCommandValue === 'string' ? baseCommandValue : '';
|
|
511
|
-
if (isStageDerivedDeploy(project.tags, baseCommand)) {
|
|
512
|
-
return { exists: true, provider: deployProvider(baseCommand) };
|
|
513
|
-
}
|
|
514
|
-
const config = deploy.configurations?.[configuration];
|
|
515
|
-
if (!config) {
|
|
529
|
+
if (!deploy || !stageDeploysProject(project.tags, stage)) {
|
|
516
530
|
return { exists: false };
|
|
517
531
|
}
|
|
518
|
-
const
|
|
519
|
-
return { exists: true, provider: deployProvider(typeof
|
|
532
|
+
const command = deploy.options?.command ?? deploy.command;
|
|
533
|
+
return { exists: true, provider: deployProvider(typeof command === 'string' ? command : '') };
|
|
520
534
|
}
|
|
521
535
|
function deployProvider(command) {
|
|
522
536
|
return command.includes('wrangler ') ? 'cloudflare' : undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smoothbricks/cli",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.19",
|
|
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.13",
|
|
68
68
|
"@smoothbricks/validation": "0.1.8",
|
|
69
69
|
"commander": "^14.0.3",
|
|
70
70
|
"make-synchronized": "^0.8.0",
|
|
@@ -847,8 +847,11 @@ describe('event-aware stage deployment', () => {
|
|
|
847
847
|
).toThrow(/same-repository/);
|
|
848
848
|
});
|
|
849
849
|
|
|
850
|
-
it('selects stage
|
|
850
|
+
it('selects tagged stage projects plus staging-only infrastructure, and never an untagged one', async () => {
|
|
851
851
|
const definitions: Record<string, unknown> = {
|
|
852
|
+
// A private wrangler project the plugin gave a deploy target and nobody tagged: deployable
|
|
853
|
+
// by hand, invisible to CI. Reading the command instead deployed whatever held a wrangler
|
|
854
|
+
// manifest, including a published library that ships one to document a binding.
|
|
852
855
|
app: {
|
|
853
856
|
targets: {
|
|
854
857
|
deploy: { options: { command: 'smoo wrangler deploy-stage --stage {args.stage}' } },
|
|
@@ -875,18 +878,15 @@ describe('event-aware stage deployment', () => {
|
|
|
875
878
|
const loadProject = async (project: string) => definitions[project];
|
|
876
879
|
|
|
877
880
|
await expect(selectStageDeployProjects(candidates, 'staging', undefined, loadProject)).resolves.toEqual([
|
|
878
|
-
'app',
|
|
879
881
|
'app-backend',
|
|
880
882
|
'e2e-mail-capture',
|
|
881
883
|
'website',
|
|
882
884
|
]);
|
|
883
885
|
await expect(selectStageDeployProjects(candidates, 'pr123', undefined, loadProject)).resolves.toEqual([
|
|
884
|
-
'app',
|
|
885
886
|
'app-backend',
|
|
886
887
|
'website',
|
|
887
888
|
]);
|
|
888
889
|
await expect(selectStageDeployProjects(candidates, 'production', undefined, loadProject)).resolves.toEqual([
|
|
889
|
-
'app',
|
|
890
890
|
'app-backend',
|
|
891
891
|
'website',
|
|
892
892
|
]);
|
|
@@ -1303,15 +1303,14 @@ describe('selectStageDeployProjects with a required tag', () => {
|
|
|
1303
1303
|
};
|
|
1304
1304
|
const loadProject = async (project: string) => definitions[project];
|
|
1305
1305
|
|
|
1306
|
-
it('keeps only projects carrying the tag, on top of the stage
|
|
1306
|
+
it('keeps only projects carrying the tag, on top of the stage rules', async () => {
|
|
1307
1307
|
await expect(
|
|
1308
1308
|
selectStageDeployProjects(Object.keys(definitions), 'production', 'production-push-deploy-target', loadProject),
|
|
1309
1309
|
).resolves.toEqual(['website']);
|
|
1310
1310
|
});
|
|
1311
1311
|
|
|
1312
|
-
it('selects the
|
|
1312
|
+
it('selects only the tagged project on every stage, required tag or not', async () => {
|
|
1313
1313
|
await expect(selectStageDeployProjects(Object.keys(definitions), 'pr12', undefined, loadProject)).resolves.toEqual([
|
|
1314
|
-
'app',
|
|
1315
1314
|
'website',
|
|
1316
1315
|
]);
|
|
1317
1316
|
});
|
package/src/github-ci/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { appendFile } from 'node:fs/promises';
|
|
|
4
4
|
import { PLATFORM_TARGET_GLOBS } from '@smoothbricks/nx-plugin/workspace-config-policy';
|
|
5
5
|
import { $ } from 'bun';
|
|
6
6
|
import typia from 'typia';
|
|
7
|
-
import {
|
|
7
|
+
import { PERMANENT_DEPLOY_TAG, STAGING_DEPLOY_TAG, stageDeploysProject } from '../lib/deploy-tags.js';
|
|
8
8
|
import {
|
|
9
9
|
ciPushBranches,
|
|
10
10
|
isNonEmpty,
|
|
@@ -35,16 +35,14 @@ export interface GithubActionsEventPayload {
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* What a deploy candidate is READ for: its tags. `nx show projects --withTarget
|
|
40
|
+
* deploy` already established the target, and the tags decide the rest, so a
|
|
41
|
+
* project whose `tags` is not a string array fails the parse instead of being
|
|
42
|
+
* silently untagged.
|
|
43
|
+
*/
|
|
38
44
|
interface NxProjectDeployTarget {
|
|
39
|
-
tags?:
|
|
40
|
-
targets?: {
|
|
41
|
-
deploy?: {
|
|
42
|
-
command?: unknown;
|
|
43
|
-
options?: {
|
|
44
|
-
command?: unknown;
|
|
45
|
-
};
|
|
46
|
-
};
|
|
47
|
-
};
|
|
45
|
+
tags?: string[];
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
const parseGithubActionsEvent = typia.json.createIsParse<GithubActionsEventPayload>();
|
|
@@ -570,16 +568,10 @@ export async function selectStageDeployProjects(
|
|
|
570
568
|
for (const project of candidates) {
|
|
571
569
|
const definition = await loadProject(project);
|
|
572
570
|
if (!isNxProjectDeployTarget(definition)) continue;
|
|
573
|
-
const
|
|
574
|
-
const commandValue = deploy?.options?.command ?? deploy?.command;
|
|
575
|
-
const tags = Array.isArray(definition.tags)
|
|
576
|
-
? definition.tags.filter((tag): tag is string => typeof tag === 'string')
|
|
577
|
-
: [];
|
|
578
|
-
const isStageDerived = isStageDerivedDeploy(tags, typeof commandValue === 'string' ? commandValue : undefined);
|
|
579
|
-
const isStagingOnly = tags.includes(STAGING_DEPLOY_TAG);
|
|
571
|
+
const tags = definition.tags;
|
|
580
572
|
// A required tag narrows the stage rules; it never selects a project they exclude.
|
|
581
|
-
if (requireTag && !tags
|
|
582
|
-
if (
|
|
573
|
+
if (requireTag && !tags?.includes(requireTag)) continue;
|
|
574
|
+
if (stageDeploysProject(tags, stage)) {
|
|
583
575
|
selected.push(project);
|
|
584
576
|
}
|
|
585
577
|
}
|
package/src/lib/deploy-tags.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// The Nx tags that drive stage deploys. CI selection (github-ci), workflow generation (monorepo)
|
|
2
2
|
// and the graph readers must agree on these, so they live here rather than with any one of them.
|
|
3
3
|
|
|
4
|
+
import type { DeploymentStage } from '../wrangler/stage.js';
|
|
5
|
+
|
|
4
6
|
/** Deployed on every stage: one command that deploys whichever stage it is given. */
|
|
5
7
|
const STAGE_DEPLOY_TAG = 'stage-deploy-target';
|
|
6
8
|
|
|
@@ -14,9 +16,25 @@ export const PERMANENT_DEPLOY_TAG = 'permanent-deploy-target';
|
|
|
14
16
|
export const PRODUCTION_PUSH_DEPLOY_TAG = 'production-push-deploy-target';
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
|
-
* Whether
|
|
18
|
-
*
|
|
19
|
+
* Whether the stage flow deploys a project, from its TAGS alone — the one rule
|
|
20
|
+
* CI selection and workflow generation both read, so a generated deploy job and
|
|
21
|
+
* the projects that job deploys cannot disagree.
|
|
22
|
+
*
|
|
23
|
+
* Having a `deploy` target is not that answer. A deploy target says a project
|
|
24
|
+
* CAN be deployed; only a tag says CI does it. A published library ships a
|
|
25
|
+
* wrangler manifest to document a Durable Object binding for its consumers, and
|
|
26
|
+
* that manifest is structurally identical to a deployable worker's — so the
|
|
27
|
+
* plugin infers `deploy` from one, and a repository that deploys nothing from CI
|
|
28
|
+
* would otherwise generate a deploy job, with cloud credentials, for it.
|
|
29
|
+
*
|
|
30
|
+
* The three tags compose as precedence, not as a set: `permanent` excludes a
|
|
31
|
+
* project from every stage, `staging` narrows it to one, `stage` generalizes it
|
|
32
|
+
* to all. `production-push` selects WITHIN this answer (see `--select-tag`) and
|
|
33
|
+
* never widens it.
|
|
19
34
|
*/
|
|
20
|
-
export function
|
|
21
|
-
|
|
35
|
+
export function stageDeploysProject(tags: string[] | undefined, stage: DeploymentStage): boolean {
|
|
36
|
+
if (tags === undefined) return false;
|
|
37
|
+
if (tags.includes(PERMANENT_DEPLOY_TAG)) return false;
|
|
38
|
+
if (tags.includes(STAGING_DEPLOY_TAG)) return stage === 'staging';
|
|
39
|
+
return tags.includes(STAGE_DEPLOY_TAG);
|
|
22
40
|
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
LOCAL_SECTION_MARKER,
|
|
19
19
|
type ManagedFileContext,
|
|
20
20
|
macosPlatformArchitecturesForTest,
|
|
21
|
+
managedFileTargetsForContext,
|
|
21
22
|
managedFileTargetsForTest,
|
|
22
23
|
platformTargetGlobsForTest,
|
|
23
24
|
reinsertInlineLocalBlocksForTest,
|
|
@@ -263,14 +264,10 @@ describe('nx graph project helpers', () => {
|
|
|
263
264
|
},
|
|
264
265
|
},
|
|
265
266
|
app: {
|
|
267
|
+
// What the plugin infers for a private wrangler project, and what a published library
|
|
268
|
+
// holding a wrangler manifest used to get: a deploy target no tag enrols in CI.
|
|
266
269
|
targets: {
|
|
267
|
-
deploy: {
|
|
268
|
-
options: { command: 'echo no' },
|
|
269
|
-
configurations: {
|
|
270
|
-
staging: { command: 'wrangler deploy --env staging' },
|
|
271
|
-
production: { options: { command: 'wrangler deploy --env production' } },
|
|
272
|
-
},
|
|
273
|
-
},
|
|
270
|
+
deploy: { options: { command: 'smoo wrangler deploy-stage --stage {args.stage}' } },
|
|
274
271
|
'package-macos': {},
|
|
275
272
|
},
|
|
276
273
|
},
|
|
@@ -296,43 +293,40 @@ describe('nx graph project helpers', () => {
|
|
|
296
293
|
expect(hasExactTargetForTest(nearMisses, 'e2e-deployment')).toBe(false);
|
|
297
294
|
});
|
|
298
295
|
|
|
299
|
-
it('
|
|
300
|
-
expect(deployTargetInfoFromProjects(sampleProjects, 'staging')).toEqual({
|
|
301
|
-
|
|
302
|
-
provider: 'cloudflare',
|
|
303
|
-
});
|
|
304
|
-
expect(deployTargetInfoFromProjects(sampleProjects, 'production')).toEqual({
|
|
305
|
-
exists: true,
|
|
306
|
-
provider: 'cloudflare',
|
|
307
|
-
});
|
|
308
|
-
expect(deployTargetInfoFromProjects(sampleProjects, 'preview')).toEqual({ exists: false });
|
|
296
|
+
it('deploys nothing for a wrangler deploy target no tag names', () => {
|
|
297
|
+
expect(deployTargetInfoFromProjects(sampleProjects, 'staging')).toEqual({ exists: false });
|
|
298
|
+
expect(deployTargetInfoFromProjects(sampleProjects, 'production')).toEqual({ exists: false });
|
|
309
299
|
});
|
|
310
300
|
|
|
311
|
-
it('
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
},
|
|
318
|
-
},
|
|
319
|
-
},
|
|
320
|
-
};
|
|
301
|
+
it('reads the tags as precedence: permanent excludes, staging narrows, stage generalizes', () => {
|
|
302
|
+
const deploy = { options: { command: 'smoo wrangler deploy-stage --stage {args.stage}' } };
|
|
303
|
+
const stageFor = (tags: string[]) => ({
|
|
304
|
+
staging: deployTargetInfoFromProjects({ site: { tags, targets: { deploy } } }, 'staging').exists,
|
|
305
|
+
production: deployTargetInfoFromProjects({ site: { tags, targets: { deploy } } }, 'production').exists,
|
|
306
|
+
});
|
|
321
307
|
|
|
322
|
-
expect(
|
|
323
|
-
expect(
|
|
308
|
+
expect(stageFor(['stage-deploy-target'])).toEqual({ staging: true, production: true });
|
|
309
|
+
expect(stageFor(['staging-deploy-target'])).toEqual({ staging: true, production: false });
|
|
310
|
+
expect(stageFor(['permanent-deploy-target'])).toEqual({ staging: false, production: false });
|
|
311
|
+
// Both spellings on one project is a contradiction; the exclusion wins, which is what
|
|
312
|
+
// `nx show projects --exclude=tag:permanent-deploy-target` does to it in CI too.
|
|
313
|
+
expect(stageFor(['permanent-deploy-target', 'stage-deploy-target'])).toEqual({
|
|
314
|
+
staging: false,
|
|
315
|
+
production: false,
|
|
316
|
+
});
|
|
317
|
+
expect(stageFor(['npm:private'])).toEqual({ staging: false, production: false });
|
|
324
318
|
});
|
|
325
319
|
|
|
326
|
-
it('
|
|
327
|
-
const
|
|
328
|
-
site: {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
},
|
|
320
|
+
it('reads the command for the provider only: which credentials the generated job needs', () => {
|
|
321
|
+
const cloudflare: NxProjects = {
|
|
322
|
+
site: { tags: ['stage-deploy-target'], targets: { deploy: { command: 'wrangler deploy --config dist/w.json' } } },
|
|
323
|
+
};
|
|
324
|
+
const elsewhere: NxProjects = {
|
|
325
|
+
site: { tags: ['stage-deploy-target'], targets: { deploy: { command: 'bun tooling/deploy-site.ts' } } },
|
|
332
326
|
};
|
|
333
327
|
|
|
334
|
-
expect(deployTargetInfoFromProjects(
|
|
335
|
-
expect(deployTargetInfoFromProjects(
|
|
328
|
+
expect(deployTargetInfoFromProjects(cloudflare, 'staging')).toEqual({ exists: true, provider: 'cloudflare' });
|
|
329
|
+
expect(deployTargetInfoFromProjects(elsewhere, 'staging')).toEqual({ exists: true, provider: undefined });
|
|
336
330
|
});
|
|
337
331
|
|
|
338
332
|
it('finds a tag carried by any project in the graph', () => {
|
|
@@ -582,6 +576,44 @@ describe('CI workflow rendering by repo shape', () => {
|
|
|
582
576
|
expect(rendered).toContain(' deploy-production:');
|
|
583
577
|
});
|
|
584
578
|
|
|
579
|
+
it('renders no deploy job at all for a repo whose wrangler projects carry no deploy tag', () => {
|
|
580
|
+
// The regression this exists for: published libraries ship a wrangler manifest to document a
|
|
581
|
+
// Durable Object binding for their consumers, the plugin inferred a deploy target from each,
|
|
582
|
+
// and a repository that deploys nothing from CI grew a Deploy Stage step holding Cloudflare
|
|
583
|
+
// credentials, a deployments permission, and a preview-cleanup workflow.
|
|
584
|
+
const deploy = { options: { command: 'smoo wrangler deploy-stage --stage {args.stage}' } };
|
|
585
|
+
const projects: NxProjects = {
|
|
586
|
+
'acme-cloudflare': { tags: ['npm:private'], targets: { deploy } },
|
|
587
|
+
'acme-documents': { tags: ['npm:public'], targets: { deploy } },
|
|
588
|
+
// A private worker, deployed by hand from a developer's shell and never by CI.
|
|
589
|
+
'acme-service': { targets: { deploy } },
|
|
590
|
+
};
|
|
591
|
+
const staging = deployTargetInfoFromProjects(projects, 'staging');
|
|
592
|
+
const production = deployTargetInfoFromProjects(projects, 'production');
|
|
593
|
+
const shape = context({
|
|
594
|
+
hasStagingDeployTargets: staging.exists,
|
|
595
|
+
stagingDeployProvider: staging.provider,
|
|
596
|
+
hasProductionDeployTargets: production.exists,
|
|
597
|
+
productionDeployProvider: production.provider,
|
|
598
|
+
hasE2eDeploymentTargets: true,
|
|
599
|
+
});
|
|
600
|
+
const ci = renderManagedWorkflowForTest('ci-workflow', shape);
|
|
601
|
+
|
|
602
|
+
expect(ci).not.toContain('Deploy Stage');
|
|
603
|
+
expect(ci).not.toContain('deployments: write');
|
|
604
|
+
expect(ci).not.toContain('CLOUDFLARE_API_TOKEN');
|
|
605
|
+
expect(ci).not.toContain('e2e-deployment');
|
|
606
|
+
expect(renderManagedWorkflowForTest('publish-workflow', shape)).not.toContain('Deploy production');
|
|
607
|
+
expect(managedFileTargetsForContext(shape)).not.toContain('.github/workflows/pr-preview-cleanup.yml');
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it('adds the preview cleanup workflow only where a cloudflare stage deploy makes preview stages', () => {
|
|
611
|
+
expect(managedFileTargetsForContext(deploying)).toContain('.github/workflows/pr-preview-cleanup.yml');
|
|
612
|
+
expect(managedFileTargetsForContext({ ...deploying, stagingDeployProvider: undefined })).not.toContain(
|
|
613
|
+
'.github/workflows/pr-preview-cleanup.yml',
|
|
614
|
+
);
|
|
615
|
+
});
|
|
616
|
+
|
|
585
617
|
it('threads environments and secrets from the context into the workflow', () => {
|
|
586
618
|
const rendered = renderManagedWorkflowForTest('ci-workflow', {
|
|
587
619
|
...deploying,
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
cargoCrossTestTripleFromArchiveTarget,
|
|
7
7
|
} from '@smoothbricks/nx-plugin/cross-check-policy';
|
|
8
8
|
import { MACOS_PLATFORM_TARGET_GLOBS, PLATFORM_TARGET_GLOBS } from '@smoothbricks/nx-plugin/workspace-config-policy';
|
|
9
|
-
import {
|
|
9
|
+
import { PRODUCTION_PUSH_DEPLOY_TAG, stageDeploysProject } from '../lib/deploy-tags.js';
|
|
10
10
|
import {
|
|
11
11
|
ciPushBranches,
|
|
12
12
|
type NonEmptyArray,
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
targetNamesFromProjects,
|
|
30
30
|
} from '../nx/index.js';
|
|
31
31
|
import { resolvePrivateNpmWorkflowConfig } from '../release/private-npm.js';
|
|
32
|
+
import type { DeploymentStage } from '../wrangler/stage.js';
|
|
32
33
|
import { type CiCrossTestArchive, privateNpmReadTokenJobEnv, renderCiWorkflowYaml } from './ci-workflow.js';
|
|
33
34
|
import { renderRunsOnLine } from './github-runs-on.js';
|
|
34
35
|
import { renderPrPreviewCleanupWorkflowYaml } from './pr-preview-cleanup-workflow.js';
|
|
@@ -342,6 +343,23 @@ export const managedFileTargetsForTest = managedFiles.map(({ target, executable
|
|
|
342
343
|
|
|
343
344
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
344
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Whether a repository of this shape has the file at all. A repository that owns
|
|
348
|
+
* no packages has nothing to publish, and one the stage flow does not deploy to
|
|
349
|
+
* Cloudflare has no preview stage to clean up.
|
|
350
|
+
*/
|
|
351
|
+
function managedFileApplies(file: ManagedFile, context: ManagedFileContext): boolean {
|
|
352
|
+
if (file.releasePackagesOnly === true && !context.hasReleasePackages && !context.hasProductionDeployTargets) {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
return file.cloudflareDeployOnly !== true || context.stagingDeployProvider === 'cloudflare';
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** Test seam: the managed targets a repository of this shape gets, in file order. */
|
|
359
|
+
export function managedFileTargetsForContext(context: ManagedFileContext): string[] {
|
|
360
|
+
return managedFiles.filter((file) => managedFileApplies(file, context)).map((file) => file.target);
|
|
361
|
+
}
|
|
362
|
+
|
|
345
363
|
export async function applyManagedFiles(root: string, mode: 'update' | 'check' | 'diff'): Promise<FileResult[]> {
|
|
346
364
|
const context = await getManagedFileContext(root);
|
|
347
365
|
return managedFiles.map((file) => applyManagedFile(root, file, mode, context));
|
|
@@ -353,10 +371,7 @@ function applyManagedFile(
|
|
|
353
371
|
mode: 'update' | 'check' | 'diff',
|
|
354
372
|
context: ManagedFileContext,
|
|
355
373
|
): FileResult {
|
|
356
|
-
if (file
|
|
357
|
-
return { target: file.target, action: 'skipped' };
|
|
358
|
-
}
|
|
359
|
-
if (file.cloudflareDeployOnly === true && context.stagingDeployProvider !== 'cloudflare') {
|
|
374
|
+
if (!managedFileApplies(file, context)) {
|
|
360
375
|
return { target: file.target, action: 'skipped' };
|
|
361
376
|
}
|
|
362
377
|
const target = resolve(root, file.target);
|
|
@@ -612,12 +627,12 @@ export function macosPlatformArchitecturesForTest(targetNames: Iterable<string>)
|
|
|
612
627
|
return [...architectures].sort((left, right) => left.localeCompare(right));
|
|
613
628
|
}
|
|
614
629
|
|
|
615
|
-
/** Test seam:
|
|
616
|
-
export function deployTargetInfoFromProjects(projects: NxProjects,
|
|
630
|
+
/** Test seam: what the stage flow deploys, and with which provider, from graph nodes. */
|
|
631
|
+
export function deployTargetInfoFromProjects(projects: NxProjects, stage: DeploymentStage): DeployTargetInfo {
|
|
617
632
|
let exists = false;
|
|
618
633
|
let provider: DeployTargetInfo['provider'];
|
|
619
634
|
for (const project of Object.values(projects)) {
|
|
620
|
-
const info = deployTargetInfoFromProject(project,
|
|
635
|
+
const info = deployTargetInfoFromProject(project, stage);
|
|
621
636
|
if (!info.exists) {
|
|
622
637
|
continue;
|
|
623
638
|
}
|
|
@@ -632,22 +647,24 @@ export function anyProjectHasTagForTest(projects: NxProjects, tag: string): bool
|
|
|
632
647
|
return Object.values(projects).some((project) => project.tags?.includes(tag));
|
|
633
648
|
}
|
|
634
649
|
|
|
635
|
-
|
|
650
|
+
/**
|
|
651
|
+
* A generated deploy job exists for the projects CI would actually deploy, which
|
|
652
|
+
* is `stageDeploysProject` and nothing else — the same rule `smoo github-ci
|
|
653
|
+
* nx-deploy` selects by, so the job and its selection cannot disagree. Reading
|
|
654
|
+
* the target's presence instead would render a deploy job, with cloud
|
|
655
|
+
* credentials, into a repository whose only wrangler manifests document a
|
|
656
|
+
* Durable Object binding for a published library's consumers.
|
|
657
|
+
*
|
|
658
|
+
* The command is read for the PROVIDER only: which cloud CLI runs says which
|
|
659
|
+
* credentials the job needs, and says nothing about whether CI runs it.
|
|
660
|
+
*/
|
|
661
|
+
function deployTargetInfoFromProject(project: NxProjectJson, stage: DeploymentStage): DeployTargetInfo {
|
|
636
662
|
const deploy = project.targets?.deploy;
|
|
637
|
-
if (!deploy) {
|
|
638
|
-
return { exists: false };
|
|
639
|
-
}
|
|
640
|
-
const baseCommandValue = deploy.options?.command ?? deploy.command;
|
|
641
|
-
const baseCommand = typeof baseCommandValue === 'string' ? baseCommandValue : '';
|
|
642
|
-
if (isStageDerivedDeploy(project.tags, baseCommand)) {
|
|
643
|
-
return { exists: true, provider: deployProvider(baseCommand) };
|
|
644
|
-
}
|
|
645
|
-
const config = deploy.configurations?.[configuration];
|
|
646
|
-
if (!config) {
|
|
663
|
+
if (!deploy || !stageDeploysProject(project.tags, stage)) {
|
|
647
664
|
return { exists: false };
|
|
648
665
|
}
|
|
649
|
-
const
|
|
650
|
-
return { exists: true, provider: deployProvider(typeof
|
|
666
|
+
const command = deploy.options?.command ?? deploy.command;
|
|
667
|
+
return { exists: true, provider: deployProvider(typeof command === 'string' ? command : '') };
|
|
651
668
|
}
|
|
652
669
|
|
|
653
670
|
function deployProvider(command: string): DeployTargetInfo['provider'] {
|