@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.
Files changed (42) hide show
  1. package/dist/cli.js +14 -0
  2. package/dist/github-ci/index.d.ts +12 -0
  3. package/dist/github-ci/index.d.ts.map +1 -1
  4. package/dist/github-ci/index.js +72 -4
  5. package/dist/lib/run.d.ts +1 -0
  6. package/dist/lib/run.d.ts.map +1 -1
  7. package/dist/lib/run.js +11 -0
  8. package/dist/monorepo/ci-workflow.d.ts +26 -0
  9. package/dist/monorepo/ci-workflow.d.ts.map +1 -0
  10. package/dist/monorepo/ci-workflow.js +201 -0
  11. package/dist/monorepo/managed-files.d.ts.map +1 -1
  12. package/dist/monorepo/managed-files.js +97 -5
  13. package/dist/monorepo/nx-sync.d.ts.map +1 -1
  14. package/dist/monorepo/nx-sync.js +18 -4
  15. package/dist/monorepo/package-hygiene.d.ts +5 -0
  16. package/dist/monorepo/package-hygiene.d.ts.map +1 -1
  17. package/dist/monorepo/package-hygiene.js +18 -4
  18. package/dist/monorepo/package-policy.d.ts.map +1 -1
  19. package/dist/monorepo/package-policy.js +4 -2
  20. package/dist/monorepo/packed-package.js +5 -4
  21. package/dist/monorepo/packs/index.js +6 -2
  22. package/dist/monorepo/publish-workflow.d.ts +7 -1
  23. package/dist/monorepo/publish-workflow.d.ts.map +1 -1
  24. package/dist/monorepo/publish-workflow.js +59 -15
  25. package/managed/raw/git-format-staged.yml +4 -0
  26. package/package.json +2 -2
  27. package/src/cli.ts +35 -5
  28. package/src/github-ci/index.ts +94 -8
  29. package/src/lib/run.ts +13 -0
  30. package/src/monorepo/__tests__/ci-workflow.test.ts +34 -0
  31. package/src/monorepo/__tests__/publish-workflow.test.ts +65 -2
  32. package/src/monorepo/ci-workflow.ts +229 -0
  33. package/src/monorepo/managed-files.ts +115 -5
  34. package/src/monorepo/nx-sync.ts +18 -4
  35. package/src/monorepo/package-hygiene.test.ts +54 -1
  36. package/src/monorepo/package-hygiene.ts +34 -4
  37. package/src/monorepo/package-policy.test.ts +16 -6
  38. package/src/monorepo/package-policy.ts +4 -2
  39. package/src/monorepo/packed-package.ts +5 -5
  40. package/src/monorepo/packs/index.ts +6 -2
  41. package/src/monorepo/publish-workflow.ts +73 -16
  42. 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 status = await shell.runStatus('sherif', ['-f', '--select', 'highest'], root, !verbose);
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 status = await shell.runStatus('sherif', ['--fail-on-warnings'], root, !verbose);
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;AAUhE,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"}
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 { runResult, runStatus } from '../lib/run.js';
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 status = await runStatus('bun', ['pm', 'pack', '--filename', tarballName, '--ignore-scripts', '--quiet'], packageDir, true);
120
- if (status !== 0) {
121
- throw new Error(`bun pm pack failed with exit code ${status}`);
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 status = await runStatus('nx', ['run-many', '-t', 'build'], ctx.root, options.verbose !== true);
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":"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"}
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 conditionalRunStep(step, run) {
353
+ function deployProductionStep(step, options) {
327
354
  return [
328
355
  ` - name: ${step.name}`,
329
- ` if: ${githubExpression("steps.version.outputs.mode != 'none'")}`,
330
- ` run: ${run}`,
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.4",
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.3",
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
- .action(async (options: { target: string; name?: string; step?: string }) => {
302
- const { githubCiNxSmart } = await import('./github-ci/index.js');
303
- await githubCiNxSmart(await findRepoRoot(), options);
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
- .action(async (options: { targets: string; projects?: string }) => {
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
  }
@@ -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
- process.env.GITHUB_EVENT_NAME === 'push' && process.env.GITHUB_REF_NAME === 'main' ? 'run-many' : 'affected';
123
- const nxArgs =
124
- mode === 'run-many'
125
- ? ['run-many', '-t', options.target, '--parallel=5']
126
- : ['affected', '-t', options.target, '--parallel=5'];
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(root: string, options: { targets: string; projects?: string }): Promise<void> {
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
+ });