@smoothbricks/cli 0.3.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +14 -0
- package/dist/github-ci/index.d.ts +12 -0
- package/dist/github-ci/index.d.ts.map +1 -1
- package/dist/github-ci/index.js +72 -4
- package/dist/lib/run.d.ts +1 -0
- package/dist/lib/run.d.ts.map +1 -1
- package/dist/lib/run.js +11 -0
- package/dist/monorepo/ci-workflow.d.ts +26 -0
- package/dist/monorepo/ci-workflow.d.ts.map +1 -0
- package/dist/monorepo/ci-workflow.js +201 -0
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +97 -5
- package/dist/monorepo/nx-sync.d.ts.map +1 -1
- package/dist/monorepo/nx-sync.js +18 -4
- package/dist/monorepo/package-hygiene.d.ts +5 -0
- package/dist/monorepo/package-hygiene.d.ts.map +1 -1
- package/dist/monorepo/package-hygiene.js +18 -4
- package/dist/monorepo/package-policy.d.ts.map +1 -1
- package/dist/monorepo/package-policy.js +4 -2
- package/dist/monorepo/packed-package.js +5 -4
- package/dist/monorepo/packs/index.js +6 -2
- package/dist/monorepo/publish-workflow.d.ts +7 -1
- package/dist/monorepo/publish-workflow.d.ts.map +1 -1
- package/dist/monorepo/publish-workflow.js +59 -15
- package/managed/raw/git-format-staged.yml +4 -0
- package/package.json +2 -2
- package/src/cli.ts +35 -5
- package/src/github-ci/index.ts +94 -8
- package/src/lib/run.ts +13 -0
- package/src/monorepo/__tests__/ci-workflow.test.ts +34 -0
- package/src/monorepo/__tests__/publish-workflow.test.ts +65 -2
- package/src/monorepo/ci-workflow.ts +229 -0
- package/src/monorepo/managed-files.ts +115 -5
- package/src/monorepo/nx-sync.ts +18 -4
- package/src/monorepo/package-hygiene.test.ts +54 -1
- package/src/monorepo/package-hygiene.ts +34 -4
- package/src/monorepo/package-policy.test.ts +16 -6
- package/src/monorepo/package-policy.ts +4 -2
- package/src/monorepo/packed-package.ts +5 -5
- package/src/monorepo/packs/index.ts +6 -2
- package/src/monorepo/publish-workflow.ts +73 -16
- package/managed/templates/github/workflows/ci.yml +0 -102
|
@@ -1,20 +1,34 @@
|
|
|
1
|
-
import { run, runStatus } from '../lib/run.js';
|
|
2
|
-
const defaultShell = { run, runStatus };
|
|
1
|
+
import { printCommandOutput, run, runResult, runStatus } from '../lib/run.js';
|
|
2
|
+
const defaultShell = { run, runResult, runStatus };
|
|
3
3
|
export async function fixPackageHygiene(root, verboseOrShell = false, maybeShell = defaultShell) {
|
|
4
4
|
const verbose = typeof verboseOrShell === 'boolean' ? verboseOrShell : false;
|
|
5
5
|
const shell = typeof verboseOrShell === 'boolean' ? maybeShell : verboseOrShell;
|
|
6
|
-
const
|
|
6
|
+
const result = await runPackageHygieneCommand(shell, 'sherif', ['-f', '--select', 'highest'], root, verbose);
|
|
7
|
+
const status = result.exitCode;
|
|
7
8
|
if (status !== 0) {
|
|
9
|
+
if (!verbose) {
|
|
10
|
+
printCommandOutput(result.stdout, result.stderr);
|
|
11
|
+
}
|
|
8
12
|
throw new Error(`sherif -f --select highest failed with exit code ${status}`);
|
|
9
13
|
}
|
|
10
14
|
}
|
|
11
15
|
export async function validatePackageHygiene(root, verboseOrShell = false, maybeShell = defaultShell) {
|
|
12
16
|
const verbose = typeof verboseOrShell === 'boolean' ? verboseOrShell : false;
|
|
13
17
|
const shell = typeof verboseOrShell === 'boolean' ? maybeShell : verboseOrShell;
|
|
14
|
-
const
|
|
18
|
+
const result = await runPackageHygieneCommand(shell, 'sherif', ['--fail-on-warnings'], root, verbose);
|
|
19
|
+
const status = result.exitCode;
|
|
15
20
|
if (status !== 0) {
|
|
21
|
+
if (!verbose) {
|
|
22
|
+
printCommandOutput(result.stdout, result.stderr);
|
|
23
|
+
}
|
|
16
24
|
console.error('sherif package hygiene validation failed');
|
|
17
25
|
return 1;
|
|
18
26
|
}
|
|
19
27
|
return 0;
|
|
20
28
|
}
|
|
29
|
+
async function runPackageHygieneCommand(shell, command, args, root, verbose) {
|
|
30
|
+
if (!verbose && shell.runResult) {
|
|
31
|
+
return shell.runResult(command, args, root);
|
|
32
|
+
}
|
|
33
|
+
return { exitCode: await shell.runStatus(command, args, root, !verbose), stdout: '', stderr: '' };
|
|
34
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package-policy.d.ts","sourceRoot":"","sources":["../../src/monorepo/package-policy.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,sBAAsB,EAC5B,MAAM,+CAA+C,CAAC;AACvD,OAAO,EAGL,2BAA2B,EAC3B,uBAAuB,EACxB,MAAM,+CAA+C,CAAC;AA6BvD,YAAY,EAAE,0BAA0B,IAAI,iCAAiC,EAAE,sBAAsB,EAAE,CAAC;AACxG,OAAO,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"package-policy.d.ts","sourceRoot":"","sources":["../../src/monorepo/package-policy.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,sBAAsB,EAC5B,MAAM,+CAA+C,CAAC;AACvD,OAAO,EAGL,2BAA2B,EAC3B,uBAAuB,EACxB,MAAM,+CAA+C,CAAC;AA6BvD,YAAY,EAAE,0BAA0B,IAAI,iCAAiC,EAAE,sBAAsB,EAAE,CAAC;AACxG,OAAO,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,CAAC;AAYhE,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAI/D;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAuB1D;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAMxD;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAuC7D;AAED,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,0BAA+B,GAAG,IAAI,CAwB7G;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAMzD;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAoB7D;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAEvE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAezD;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsC9D;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAW5D;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAyB3D;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAcvD;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA0DlE;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,0BAA+B,GAAG,MAAM,CAiC5G"}
|
|
@@ -9,10 +9,12 @@ import { getWorkspacePackageManifests, getWorkspacePackages, listPackageJsonReco
|
|
|
9
9
|
export { SMOO_NX_RELEASE_TAG_PATTERN, SMOO_NX_VERSION_ACTIONS };
|
|
10
10
|
const extraCommitScopes = ['release'];
|
|
11
11
|
const rootScriptPolicy = {
|
|
12
|
+
clean: 'nx run-many -t clean; nx reset',
|
|
13
|
+
'clean:node_modules': 'rm -rf node_modules && find e* t* p* -type d -name node_modules -print0 | xargs -0 rm -rvf',
|
|
14
|
+
'format:changed': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml --also-unstaged',
|
|
15
|
+
'format:staged': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml',
|
|
12
16
|
lint: 'nx run-many -t lint',
|
|
13
17
|
'lint:fix': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml --unstaged',
|
|
14
|
-
'format:staged': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml',
|
|
15
|
-
'format:changed': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml --also-unstaged',
|
|
16
18
|
};
|
|
17
19
|
export function applyFixableMonorepoDefaults(root) {
|
|
18
20
|
applyRootScriptDefaults(root);
|
|
@@ -3,7 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { publint } from 'publint';
|
|
4
4
|
import { formatMessage } from 'publint/utils';
|
|
5
5
|
import { isRecord } from '../lib/json.js';
|
|
6
|
-
import {
|
|
6
|
+
import { printCommandOutput, runResult } from '../lib/run.js';
|
|
7
7
|
import { listPublicPackages } from '../lib/workspace.js';
|
|
8
8
|
import { readPackedPackageJson, validatePackedWorkspaceDependencies } from './packed-manifest.js';
|
|
9
9
|
export async function validatePackedPublicPackages(root) {
|
|
@@ -116,9 +116,10 @@ async function packPackage(root, pkg) {
|
|
|
116
116
|
const tarballName = `.smoo-${process.pid}-${Date.now()}.tgz`;
|
|
117
117
|
const tarballPath = join(root, tarballName);
|
|
118
118
|
try {
|
|
119
|
-
const
|
|
120
|
-
if (
|
|
121
|
-
|
|
119
|
+
const result = await runResult('bun', ['pm', 'pack', '--filename', tarballName, '--ignore-scripts', '--quiet'], packageDir);
|
|
120
|
+
if (result.exitCode !== 0) {
|
|
121
|
+
printCommandOutput(result.stdout, result.stderr);
|
|
122
|
+
throw new Error(`bun pm pack failed with exit code ${result.exitCode}`);
|
|
122
123
|
}
|
|
123
124
|
const bytes = new Uint8Array(readFileSync(tarballPath));
|
|
124
125
|
return { path: tarballPath, arrayBuffer: bytes.slice().buffer };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { chmodSync, existsSync, statSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { runStatus } from '../../lib/run.js';
|
|
3
|
+
import { printCommandOutput, runResult, runStatus } from '../../lib/run.js';
|
|
4
4
|
import { readProjectTargets } from '../../nx/index.js';
|
|
5
5
|
import { syncBunLockfileVersions, validateBunLockfileVersions } from '../lockfile.js';
|
|
6
6
|
import { validateManagedFiles } from '../managed-files.js';
|
|
@@ -221,10 +221,14 @@ async function runBuild(ctx, options = {}) {
|
|
|
221
221
|
if (options.verbose) {
|
|
222
222
|
printCheckHeading('build', true);
|
|
223
223
|
}
|
|
224
|
-
const
|
|
224
|
+
const result = options.verbose
|
|
225
|
+
? { exitCode: await runStatus('nx', ['run-many', '-t', 'build'], ctx.root, false), stdout: '', stderr: '' }
|
|
226
|
+
: await runResult('nx', ['run-many', '-t', 'build'], ctx.root);
|
|
227
|
+
const status = result.exitCode;
|
|
225
228
|
if (status !== 0) {
|
|
226
229
|
if (!options.verbose) {
|
|
227
230
|
printCheckHeading('build', true);
|
|
231
|
+
printCommandOutput(result.stdout, result.stderr);
|
|
228
232
|
}
|
|
229
233
|
console.error('nx run-many -t build failed');
|
|
230
234
|
printCheckStatus('build', 1);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type PublishWorkflowBump = 'auto' | 'patch' | 'minor' | 'major' | 'prerelease';
|
|
2
|
-
export type PublishWorkflowCondition = 'version-mode-not-none' | 'failure' | 'always';
|
|
2
|
+
export type PublishWorkflowCondition = 'version-mode-not-none' | 'deploy-production' | 'failure' | 'always';
|
|
3
3
|
export type PublishWorkflowNxTarget = 'build' | 'lint' | 'test';
|
|
4
|
+
export type PublishWorkflowDeployEnvironment = 'none' | 'production';
|
|
4
5
|
export declare enum PublishWorkflowStepKind {
|
|
5
6
|
Checkout = "checkout",
|
|
6
7
|
SetupDevenv = "setup-devenv",
|
|
@@ -15,6 +16,7 @@ export declare enum PublishWorkflowStepKind {
|
|
|
15
16
|
UploadTraceDbs = "upload-trace-dbs",
|
|
16
17
|
ValidateMonorepoConfig = "validate-monorepo-config",
|
|
17
18
|
PublishRelease = "publish-release",
|
|
19
|
+
DeployProduction = "deploy-production",
|
|
18
20
|
SaveNixDevenv = "save-nix-devenv"
|
|
19
21
|
}
|
|
20
22
|
export interface PublishWorkflowStep {
|
|
@@ -29,10 +31,13 @@ export interface PublishWorkflowDefinition {
|
|
|
29
31
|
steps: PublishWorkflowStep[];
|
|
30
32
|
}
|
|
31
33
|
export interface PublishWorkflowDefinitionOptions {
|
|
34
|
+
deploy?: boolean;
|
|
35
|
+
deployProvider?: 'cloudflare';
|
|
32
36
|
repoName?: string;
|
|
33
37
|
}
|
|
34
38
|
export interface PublishWorkflowInputs {
|
|
35
39
|
bump: PublishWorkflowBump;
|
|
40
|
+
deployEnvironment: PublishWorkflowDeployEnvironment;
|
|
36
41
|
dryRun: boolean;
|
|
37
42
|
}
|
|
38
43
|
export interface PublishWorkflowVersionOutputs {
|
|
@@ -66,6 +71,7 @@ export interface PublishWorkflowCallbacks {
|
|
|
66
71
|
bump: PublishWorkflowBump;
|
|
67
72
|
dryRun: boolean;
|
|
68
73
|
}): Promise<void>;
|
|
74
|
+
deployProduction(): Promise<void>;
|
|
69
75
|
saveNixDevenv(input: PublishWorkflowSetupOutputs): Promise<void>;
|
|
70
76
|
}
|
|
71
77
|
export interface PublishWorkflowRunContext {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publish-workflow.d.ts","sourceRoot":"","sources":["../../src/monorepo/publish-workflow.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,YAAY,CAAC;AACtF,MAAM,MAAM,wBAAwB,GAAG,uBAAuB,GAAG,SAAS,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"publish-workflow.d.ts","sourceRoot":"","sources":["../../src/monorepo/publish-workflow.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@ export var PublishWorkflowStepKind;
|
|
|
14
14
|
PublishWorkflowStepKind["UploadTraceDbs"] = "upload-trace-dbs";
|
|
15
15
|
PublishWorkflowStepKind["ValidateMonorepoConfig"] = "validate-monorepo-config";
|
|
16
16
|
PublishWorkflowStepKind["PublishRelease"] = "publish-release";
|
|
17
|
+
PublishWorkflowStepKind["DeployProduction"] = "deploy-production";
|
|
17
18
|
PublishWorkflowStepKind["SaveNixDevenv"] = "save-nix-devenv";
|
|
18
19
|
})(PublishWorkflowStepKind || (PublishWorkflowStepKind = {}));
|
|
19
20
|
export function definePublishWorkflow(options = {}) {
|
|
@@ -26,6 +27,19 @@ export function definePublishWorkflow(options = {}) {
|
|
|
26
27
|
if (isSmoothBricksCodebasePackageName(options.repoName)) {
|
|
27
28
|
setupSteps.push({ kind: PublishWorkflowStepKind.BuildSelfHostedCli, name: '🏗️ Build self-hosted smoo' });
|
|
28
29
|
}
|
|
30
|
+
const releaseSteps = [
|
|
31
|
+
{
|
|
32
|
+
kind: PublishWorkflowStepKind.PublishRelease,
|
|
33
|
+
name: `📦 Publish release (${versionMode})`,
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
if (options.deploy === true) {
|
|
37
|
+
releaseSteps.push({
|
|
38
|
+
kind: PublishWorkflowStepKind.DeployProduction,
|
|
39
|
+
name: '🚀 Deploy production',
|
|
40
|
+
condition: 'deploy-production',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
29
43
|
return {
|
|
30
44
|
steps: numberWorkflowSteps([
|
|
31
45
|
...setupSteps,
|
|
@@ -63,10 +77,7 @@ export function definePublishWorkflow(options = {}) {
|
|
|
63
77
|
name: `✅ Validate monorepo config (${versionMode})`,
|
|
64
78
|
condition: 'version-mode-not-none',
|
|
65
79
|
},
|
|
66
|
-
|
|
67
|
-
kind: PublishWorkflowStepKind.PublishRelease,
|
|
68
|
-
name: `📦 Publish release (${versionMode})`,
|
|
69
|
-
},
|
|
80
|
+
...releaseSteps,
|
|
70
81
|
{
|
|
71
82
|
kind: PublishWorkflowStepKind.SaveNixDevenv,
|
|
72
83
|
name: '🧹 Cleanup and cache Nix/devenv',
|
|
@@ -84,7 +95,7 @@ export async function runPublishWorkflow(workflow, context) {
|
|
|
84
95
|
let failed = false;
|
|
85
96
|
let failure;
|
|
86
97
|
for (const step of workflow.steps) {
|
|
87
|
-
if (!shouldRunStep(step, version, failed)) {
|
|
98
|
+
if (!shouldRunStep(step, version, failed, context.inputs)) {
|
|
88
99
|
continue;
|
|
89
100
|
}
|
|
90
101
|
try {
|
|
@@ -132,6 +143,9 @@ export async function runPublishWorkflow(workflow, context) {
|
|
|
132
143
|
dryRun: context.inputs.dryRun,
|
|
133
144
|
});
|
|
134
145
|
break;
|
|
146
|
+
case PublishWorkflowStepKind.DeployProduction:
|
|
147
|
+
await context.callbacks.deployProduction();
|
|
148
|
+
break;
|
|
135
149
|
case PublishWorkflowStepKind.SaveNixDevenv:
|
|
136
150
|
await context.callbacks.saveNixDevenv(setupOutputs);
|
|
137
151
|
break;
|
|
@@ -147,10 +161,13 @@ export async function runPublishWorkflow(workflow, context) {
|
|
|
147
161
|
}
|
|
148
162
|
return { version, failed };
|
|
149
163
|
}
|
|
150
|
-
function shouldRunStep(step, version, failed) {
|
|
164
|
+
function shouldRunStep(step, version, failed, inputs) {
|
|
151
165
|
if (step.condition === 'version-mode-not-none') {
|
|
152
166
|
return version.mode !== 'none';
|
|
153
167
|
}
|
|
168
|
+
if (step.condition === 'deploy-production') {
|
|
169
|
+
return version.mode !== 'none' && inputs.deployEnvironment === 'production' && !inputs.dryRun;
|
|
170
|
+
}
|
|
154
171
|
if (step.condition === 'failure') {
|
|
155
172
|
return failed;
|
|
156
173
|
}
|
|
@@ -158,9 +175,17 @@ function shouldRunStep(step, version, failed) {
|
|
|
158
175
|
}
|
|
159
176
|
export function renderPublishWorkflowYaml(options = {}) {
|
|
160
177
|
const steps = definePublishWorkflow(options).steps;
|
|
161
|
-
return `${renderPublishWorkflowHeader()}${renderPublishWorkflowSteps(steps)}`;
|
|
178
|
+
return `${renderPublishWorkflowHeader(options)}${renderPublishWorkflowSteps(steps, options)}`;
|
|
162
179
|
}
|
|
163
|
-
function renderPublishWorkflowHeader() {
|
|
180
|
+
function renderPublishWorkflowHeader(options) {
|
|
181
|
+
const deployInput = options.deploy === true
|
|
182
|
+
? `
|
|
183
|
+
deploy_environment:
|
|
184
|
+
type: choice
|
|
185
|
+
description: Deploy live systems after a successful publish.
|
|
186
|
+
options: [none, production]
|
|
187
|
+
default: none`
|
|
188
|
+
: '';
|
|
164
189
|
return `name: Publish
|
|
165
190
|
|
|
166
191
|
on:
|
|
@@ -176,7 +201,7 @@ on:
|
|
|
176
201
|
dry_run:
|
|
177
202
|
type: boolean
|
|
178
203
|
description: Run release commands without writing versions, tags, publishes, or GitHub Releases.
|
|
179
|
-
default: false
|
|
204
|
+
default: false${deployInput}
|
|
180
205
|
|
|
181
206
|
permissions:
|
|
182
207
|
contents: write
|
|
@@ -199,12 +224,12 @@ jobs:
|
|
|
199
224
|
steps:
|
|
200
225
|
`;
|
|
201
226
|
}
|
|
202
|
-
function renderPublishWorkflowSteps(steps) {
|
|
227
|
+
function renderPublishWorkflowSteps(steps, options) {
|
|
203
228
|
const lines = [];
|
|
204
229
|
for (const step of steps) {
|
|
205
230
|
lines.push(...sectionLinesBefore(step));
|
|
206
231
|
lines.push(...commentLinesForStep(step));
|
|
207
|
-
lines.push(...yamlLinesForStep(step));
|
|
232
|
+
lines.push(...yamlLinesForStep(step, options));
|
|
208
233
|
lines.push('');
|
|
209
234
|
}
|
|
210
235
|
return `${lines.join('\n').trimEnd()}\n`;
|
|
@@ -244,7 +269,7 @@ function commentLinesForStep(step) {
|
|
|
244
269
|
}
|
|
245
270
|
return [` # Step ${step.number}`];
|
|
246
271
|
}
|
|
247
|
-
function yamlLinesForStep(step) {
|
|
272
|
+
function yamlLinesForStep(step, options) {
|
|
248
273
|
switch (step.kind) {
|
|
249
274
|
case PublishWorkflowStepKind.Checkout:
|
|
250
275
|
return [
|
|
@@ -312,6 +337,8 @@ function yamlLinesForStep(step) {
|
|
|
312
337
|
' # Missing package names are bootstrapped locally before trust setup.',
|
|
313
338
|
` run: smoo release publish --bump "${githubExpression('inputs.bump')}" --dry-run "${githubExpression('inputs.dry_run')}"`,
|
|
314
339
|
];
|
|
340
|
+
case PublishWorkflowStepKind.DeployProduction:
|
|
341
|
+
return deployProductionStep(step, options);
|
|
315
342
|
case PublishWorkflowStepKind.SaveNixDevenv:
|
|
316
343
|
return [
|
|
317
344
|
` - name: ${step.name}`,
|
|
@@ -323,13 +350,30 @@ function yamlLinesForStep(step) {
|
|
|
323
350
|
];
|
|
324
351
|
}
|
|
325
352
|
}
|
|
326
|
-
function
|
|
353
|
+
function deployProductionStep(step, options) {
|
|
327
354
|
return [
|
|
328
355
|
` - name: ${step.name}`,
|
|
329
|
-
|
|
330
|
-
|
|
356
|
+
' if:',
|
|
357
|
+
" ${{ steps.version.outputs.mode != 'none' && inputs.deploy_environment == 'production' && inputs.dry_run !=",
|
|
358
|
+
" 'true' }}",
|
|
359
|
+
...deployEnvLines(options),
|
|
360
|
+
' run: smoo github-ci nx-deploy --configuration production --mode run-many --verify --name "Deploy Production"',
|
|
331
361
|
];
|
|
332
362
|
}
|
|
363
|
+
function deployEnvLines(options) {
|
|
364
|
+
if (options.deployProvider !== 'cloudflare') {
|
|
365
|
+
return [];
|
|
366
|
+
}
|
|
367
|
+
return [
|
|
368
|
+
' env:',
|
|
369
|
+
' CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}',
|
|
370
|
+
' CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}',
|
|
371
|
+
];
|
|
372
|
+
}
|
|
373
|
+
function conditionalRunStep(step, run) {
|
|
374
|
+
const condition = "steps.version.outputs.mode != 'none'";
|
|
375
|
+
return [` - name: ${step.name}`, ` if: ${githubExpression(condition)}`, ` run: ${run}`];
|
|
376
|
+
}
|
|
333
377
|
function githubExpression(expression) {
|
|
334
378
|
return ['$', '{{ ', expression, ' }}'].join('');
|
|
335
379
|
}
|
|
@@ -42,6 +42,10 @@ formatters:
|
|
|
42
42
|
- '!*.nix'
|
|
43
43
|
# Bun lockfiles are generated and should not be rewritten by Prettier
|
|
44
44
|
- '!bun.lock'
|
|
45
|
+
# Binary artifacts: Prettier reads them as UTF-8 and can corrupt them
|
|
46
|
+
# by replacing invalid byte sequences.
|
|
47
|
+
- '!*.wasm'
|
|
48
|
+
- '!*.wasm.sha256'
|
|
45
49
|
|
|
46
50
|
# Alejandra for Nix files
|
|
47
51
|
alejandra:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smoothbricks/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "SmoothBricks monorepo automation CLI",
|
|
6
6
|
"bin": {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
48
|
-
"@smoothbricks/nx-plugin": "0.2.
|
|
48
|
+
"@smoothbricks/nx-plugin": "0.2.4",
|
|
49
49
|
"@smoothbricks/validation": "0.1.1",
|
|
50
50
|
"commander": "^14.0.3",
|
|
51
51
|
"publint": "^0.3.18",
|
package/src/cli.ts
CHANGED
|
@@ -298,18 +298,48 @@ function buildProgram(): Command {
|
|
|
298
298
|
.requiredOption('--target <target>')
|
|
299
299
|
.option('--name <name>')
|
|
300
300
|
.option('--step <step>')
|
|
301
|
-
.
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
301
|
+
.option('--mode <mode>', 'auto, affected, or run-many', 'auto')
|
|
302
|
+
.option('--configuration <configuration>')
|
|
303
|
+
.action(
|
|
304
|
+
async (options: {
|
|
305
|
+
target: string;
|
|
306
|
+
name?: string;
|
|
307
|
+
step?: string;
|
|
308
|
+
mode?: 'auto' | 'affected' | 'run-many';
|
|
309
|
+
configuration?: string;
|
|
310
|
+
}) => {
|
|
311
|
+
const { githubCiNxSmart } = await import('./github-ci/index.js');
|
|
312
|
+
await githubCiNxSmart(await findRepoRoot(), options);
|
|
313
|
+
},
|
|
314
|
+
);
|
|
305
315
|
githubCi
|
|
306
316
|
.command('nx-run-many')
|
|
307
317
|
.requiredOption('--targets <targets>')
|
|
308
318
|
.option('--projects <projects>')
|
|
309
|
-
.
|
|
319
|
+
.option('--configuration <configuration>')
|
|
320
|
+
.action(async (options: { targets: string; projects?: string; configuration?: string }) => {
|
|
310
321
|
const { githubCiNxRunMany } = await import('./github-ci/index.js');
|
|
311
322
|
await githubCiNxRunMany(await findRepoRoot(), options);
|
|
312
323
|
});
|
|
324
|
+
githubCi
|
|
325
|
+
.command('nx-deploy')
|
|
326
|
+
.requiredOption('--configuration <configuration>')
|
|
327
|
+
.option('--mode <mode>', 'auto, affected, or run-many', 'run-many')
|
|
328
|
+
.option('--name <name>')
|
|
329
|
+
.option('--step <step>')
|
|
330
|
+
.option('--verify', 'run build, lint, and test before deploy')
|
|
331
|
+
.action(
|
|
332
|
+
async (options: {
|
|
333
|
+
configuration: string;
|
|
334
|
+
mode?: 'auto' | 'affected' | 'run-many';
|
|
335
|
+
name?: string;
|
|
336
|
+
step?: string;
|
|
337
|
+
verify?: boolean;
|
|
338
|
+
}) => {
|
|
339
|
+
const { githubCiNxDeploy } = await import('./github-ci/index.js');
|
|
340
|
+
await githubCiNxDeploy(await findRepoRoot(), options);
|
|
341
|
+
},
|
|
342
|
+
);
|
|
313
343
|
|
|
314
344
|
return program;
|
|
315
345
|
}
|
package/src/github-ci/index.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { dirname, join } from 'node:path';
|
|
|
4
4
|
import { $ } from 'bun';
|
|
5
5
|
import { decode, run, runStatus } from '../lib/run.js';
|
|
6
6
|
|
|
7
|
+
type NxSmartMode = 'auto' | 'affected' | 'run-many';
|
|
8
|
+
|
|
7
9
|
export async function cleanupGithubCiCache(root: string): Promise<void> {
|
|
8
10
|
const githubOutput = process.env.GITHUB_OUTPUT;
|
|
9
11
|
const markCacheReady = async (ready: boolean): Promise<void> => {
|
|
@@ -113,17 +115,17 @@ async function addReferencesFrom(roots: Set<string>, path: string, cwd: string):
|
|
|
113
115
|
|
|
114
116
|
export async function githubCiNxSmart(
|
|
115
117
|
root: string,
|
|
116
|
-
options: { target: string; name?: string; step?: string },
|
|
118
|
+
options: { target: string; name?: string; step?: string; mode?: NxSmartMode; configuration?: string },
|
|
117
119
|
): Promise<void> {
|
|
118
120
|
const name = options.name ?? options.target;
|
|
119
121
|
const step = options.step ?? '';
|
|
120
122
|
await createGithubStatus(name, step);
|
|
121
|
-
const mode =
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
const mode = resolveNxSmartMode(options.mode ?? 'auto');
|
|
124
|
+
const nxArgs = mode === 'run-many' ? ['run-many', '-t', options.target] : ['affected', '-t', options.target];
|
|
125
|
+
if (options.configuration) {
|
|
126
|
+
nxArgs.push(`--configuration=${options.configuration}`);
|
|
127
|
+
}
|
|
128
|
+
nxArgs.push('--parallel=5');
|
|
127
129
|
const status = await runStatus('nx', nxArgs, root);
|
|
128
130
|
await updateGithubStatus(name, status === 0 ? 'success' : 'failure', step);
|
|
129
131
|
if (status !== 0) {
|
|
@@ -131,14 +133,98 @@ export async function githubCiNxSmart(
|
|
|
131
133
|
}
|
|
132
134
|
}
|
|
133
135
|
|
|
134
|
-
export async function githubCiNxRunMany(
|
|
136
|
+
export async function githubCiNxRunMany(
|
|
137
|
+
root: string,
|
|
138
|
+
options: { targets: string; projects?: string; configuration?: string },
|
|
139
|
+
): Promise<void> {
|
|
135
140
|
const nxArgs = ['run-many', '-t', options.targets, '--parallel=5'];
|
|
136
141
|
if (options.projects) {
|
|
137
142
|
nxArgs.push(`--projects=${options.projects}`);
|
|
138
143
|
}
|
|
144
|
+
if (options.configuration) {
|
|
145
|
+
nxArgs.push(`--configuration=${options.configuration}`);
|
|
146
|
+
}
|
|
139
147
|
await run('nx', nxArgs, root);
|
|
140
148
|
}
|
|
141
149
|
|
|
150
|
+
export async function githubCiNxDeploy(
|
|
151
|
+
root: string,
|
|
152
|
+
options: { configuration: string; mode?: NxSmartMode; name?: string; step?: string; verify?: boolean },
|
|
153
|
+
): Promise<void> {
|
|
154
|
+
const name = options.name ?? `Deploy ${options.configuration}`;
|
|
155
|
+
const step = options.step ?? '';
|
|
156
|
+
await createGithubStatus(name, step);
|
|
157
|
+
const mode = resolveNxSmartMode(options.mode ?? 'run-many');
|
|
158
|
+
const projects = await deployProjectsWithConfiguration(root, options.configuration, mode);
|
|
159
|
+
if (projects.length === 0) {
|
|
160
|
+
console.log(`No ${mode} deploy projects with configuration ${options.configuration}; skipping.`);
|
|
161
|
+
await updateGithubStatus(name, 'success', step);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const projectList = projects.join(',');
|
|
166
|
+
const targets = options.verify === true ? ['build', 'lint', 'test', 'deploy'] : ['deploy'];
|
|
167
|
+
for (const target of targets) {
|
|
168
|
+
const nxArgs = ['run-many', '-t', target, `--projects=${projectList}`, '--parallel=5'];
|
|
169
|
+
if (target === 'deploy') {
|
|
170
|
+
nxArgs.push(`--configuration=${options.configuration}`);
|
|
171
|
+
}
|
|
172
|
+
const status = await runStatus('nx', nxArgs, root);
|
|
173
|
+
if (status !== 0) {
|
|
174
|
+
await updateGithubStatus(name, 'failure', step);
|
|
175
|
+
throw new Error(`nx ${nxArgs.join(' ')} failed with exit code ${status}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
await updateGithubStatus(name, 'success', step);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function resolveNxSmartMode(mode: NxSmartMode): 'affected' | 'run-many' {
|
|
182
|
+
if (mode === 'affected' || mode === 'run-many') {
|
|
183
|
+
return mode;
|
|
184
|
+
}
|
|
185
|
+
return process.env.GITHUB_EVENT_NAME === 'push' && process.env.GITHUB_REF_NAME === 'main' ? 'run-many' : 'affected';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function deployProjectsWithConfiguration(
|
|
189
|
+
root: string,
|
|
190
|
+
configuration: string,
|
|
191
|
+
mode: 'affected' | 'run-many',
|
|
192
|
+
): Promise<string[]> {
|
|
193
|
+
const listArgs =
|
|
194
|
+
mode === 'affected'
|
|
195
|
+
? ['show', 'projects', '--affected', '--withTarget', 'deploy', '--json']
|
|
196
|
+
: ['show', 'projects', '--withTarget', 'deploy', '--json'];
|
|
197
|
+
const result = await $`nx ${listArgs}`.cwd(root).quiet();
|
|
198
|
+
const candidates = nxProjectList(decode(result.stdout));
|
|
199
|
+
const projects: string[] = [];
|
|
200
|
+
for (const project of candidates) {
|
|
201
|
+
if (await deployTargetHasConfiguration(root, project, configuration)) {
|
|
202
|
+
projects.push(project);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return projects.sort((a, b) => a.localeCompare(b));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function deployTargetHasConfiguration(root: string, project: string, configuration: string): Promise<boolean> {
|
|
209
|
+
const result = await $`nx show project ${project} --json`.cwd(root).quiet();
|
|
210
|
+
const parsed: unknown = JSON.parse(decode(result.stdout));
|
|
211
|
+
const targets = recordValue(parsed)?.targets;
|
|
212
|
+
const deploy = recordValue(targets)?.deploy;
|
|
213
|
+
const configurations = recordValue(deploy)?.configurations;
|
|
214
|
+
return recordValue(configurations)?.[configuration] !== undefined;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function nxProjectList(output: string): string[] {
|
|
218
|
+
const parsed: unknown = JSON.parse(output);
|
|
219
|
+
return Array.isArray(parsed) ? parsed.filter((project): project is string => typeof project === 'string') : [];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function recordValue(value: unknown): Record<string, unknown> | undefined {
|
|
223
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
224
|
+
? (value as Record<string, unknown>)
|
|
225
|
+
: undefined;
|
|
226
|
+
}
|
|
227
|
+
|
|
142
228
|
async function createGithubStatus(name: string, step: string): Promise<void> {
|
|
143
229
|
await postGithubStatus(name, 'pending', `Running ${name}...`, step);
|
|
144
230
|
}
|
package/src/lib/run.ts
CHANGED
|
@@ -70,6 +70,15 @@ export async function runResult(
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
export function printCommandOutput(stdout: string, stderr: string): void {
|
|
74
|
+
if (stdout.length > 0) {
|
|
75
|
+
console.log(trimTrailingNewline(stdout));
|
|
76
|
+
}
|
|
77
|
+
if (stderr.length > 0) {
|
|
78
|
+
console.error(trimTrailingNewline(stderr));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
73
82
|
function mergeEnv(env: Record<string, string>): Record<string, string> {
|
|
74
83
|
const merged: Record<string, string> = {};
|
|
75
84
|
for (const [key, value] of Object.entries(process.env)) {
|
|
@@ -118,3 +127,7 @@ export async function findRepoRoot(): Promise<string> {
|
|
|
118
127
|
export function decode(bytes: Uint8Array): string {
|
|
119
128
|
return new TextDecoder().decode(bytes);
|
|
120
129
|
}
|
|
130
|
+
|
|
131
|
+
function trimTrailingNewline(value: string): string {
|
|
132
|
+
return value.endsWith('\n') ? value.slice(0, -1) : value;
|
|
133
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { CiWorkflowStepKind, defineCiWorkflow, renderCiWorkflowYaml } from '../ci-workflow.js';
|
|
5
|
+
|
|
6
|
+
describe('CI workflow definition', () => {
|
|
7
|
+
it('renders the checked-in local CI workflow copy', async () => {
|
|
8
|
+
const rendered = renderCiWorkflowYaml({ deploy: false, pushBranches: ['main'] });
|
|
9
|
+
const packageRoot = join(import.meta.dir, '..', '..', '..');
|
|
10
|
+
|
|
11
|
+
await expect(readFile(join(packageRoot, '..', '..', '.github/workflows/ci.yml'), 'utf8')).resolves.toBe(rendered);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('inserts deploy after tests and renumbers following deeplink steps', () => {
|
|
15
|
+
const steps = defineCiWorkflow({ deploy: true, pushBranches: ['main'] });
|
|
16
|
+
const rendered = renderCiWorkflowYaml({ deploy: true, pushBranches: ['main'] });
|
|
17
|
+
|
|
18
|
+
expect(steps.map((step) => [step.kind, step.number])).toContainEqual([CiWorkflowStepKind.Deploy, 9]);
|
|
19
|
+
expect(rendered).toContain('- name: 🚀 Deploy Staging');
|
|
20
|
+
expect(rendered).not.toContain('CLOUDFLARE_API_TOKEN');
|
|
21
|
+
expect(rendered).not.toContain('CLOUDFLARE_ACCOUNT_ID');
|
|
22
|
+
expect(rendered).toContain(
|
|
23
|
+
'smoo github-ci nx-deploy --configuration staging --mode affected --name "Deploy Staging" --step 9',
|
|
24
|
+
);
|
|
25
|
+
expect(rendered).toContain("# Step 10\n # Nx's database cache needs artifact files");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('adds Cloudflare credentials for Wrangler-backed deploys', () => {
|
|
29
|
+
const rendered = renderCiWorkflowYaml({ deploy: true, deployProvider: 'cloudflare', pushBranches: ['main'] });
|
|
30
|
+
|
|
31
|
+
expect(rendered).toContain('CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}');
|
|
32
|
+
expect(rendered).toContain('CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}');
|
|
33
|
+
});
|
|
34
|
+
});
|