@smoothbricks/cli 0.10.2 → 0.10.3

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 (62) hide show
  1. package/README.md +25 -13
  2. package/dist/cli.js +24 -0
  3. package/dist/github-ci/index.d.ts +17 -2
  4. package/dist/github-ci/index.d.ts.map +1 -1
  5. package/dist/github-ci/index.js +141 -11
  6. package/dist/github-ci/outputs.d.ts +22 -0
  7. package/dist/github-ci/outputs.d.ts.map +1 -0
  8. package/dist/github-ci/outputs.js +649 -0
  9. package/dist/monorepo/ci-workflow.js +1 -1
  10. package/dist/monorepo/managed-files.d.ts +1 -0
  11. package/dist/monorepo/managed-files.d.ts.map +1 -1
  12. package/dist/monorepo/managed-files.js +38 -0
  13. package/dist/monorepo/package-policy.d.ts.map +1 -1
  14. package/dist/monorepo/package-policy.js +26 -0
  15. package/dist/monorepo/packs/index.d.ts +3 -0
  16. package/dist/monorepo/packs/index.d.ts.map +1 -1
  17. package/dist/monorepo/packs/index.js +5 -2
  18. package/dist/monorepo/publish-workflow.d.ts +1 -0
  19. package/dist/monorepo/publish-workflow.d.ts.map +1 -1
  20. package/dist/monorepo/publish-workflow.js +239 -2
  21. package/dist/nx/index.d.ts +6 -0
  22. package/dist/nx/index.d.ts.map +1 -1
  23. package/dist/nx/index.js +55 -5
  24. package/dist/release/github-release.d.ts +3 -0
  25. package/dist/release/github-release.d.ts.map +1 -1
  26. package/dist/release/github-release.js +11 -0
  27. package/dist/release/index.d.ts +19 -1
  28. package/dist/release/index.d.ts.map +1 -1
  29. package/dist/release/index.js +140 -31
  30. package/dist/release/orchestration.d.ts +8 -1
  31. package/dist/release/orchestration.d.ts.map +1 -1
  32. package/dist/release/orchestration.js +38 -1
  33. package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +5 -0
  34. package/managed/raw/tooling/direnv/setup-environment.ts +40 -3
  35. package/managed/templates/github/actions/cache-nix-devenv/action.yml +12 -10
  36. package/managed/templates/github/actions/cache-node-modules/action.yml +3 -2
  37. package/managed/templates/github/actions/cache-nx/action.yml +2 -2
  38. package/managed/templates/github/actions/save-nix-devenv/action.yml +11 -3
  39. package/package.json +7 -4
  40. package/src/cli.ts +30 -4
  41. package/src/github-ci/index.test.ts +486 -0
  42. package/src/github-ci/index.ts +172 -13
  43. package/src/github-ci/outputs.ts +506 -0
  44. package/src/monorepo/__tests__/ci-workflow.test.ts +11 -0
  45. package/src/monorepo/__tests__/publish-workflow.test.ts +222 -1
  46. package/src/monorepo/ci-workflow.ts +1 -1
  47. package/src/monorepo/managed-files.test.ts +62 -0
  48. package/src/monorepo/managed-files.ts +41 -0
  49. package/src/monorepo/package-policy.test.ts +50 -1
  50. package/src/monorepo/package-policy.ts +28 -0
  51. package/src/monorepo/packs/index.test.ts +18 -1
  52. package/src/monorepo/packs/index.ts +7 -3
  53. package/src/monorepo/publish-workflow.ts +479 -2
  54. package/src/nx/index.test.ts +39 -0
  55. package/src/nx/index.ts +65 -5
  56. package/src/release/__tests__/fixture-repo.test.ts +18 -16
  57. package/src/release/__tests__/github-release.test.ts +11 -0
  58. package/src/release/__tests__/orchestration.test.ts +57 -2
  59. package/src/release/__tests__/trust-publisher.test.ts +91 -2
  60. package/src/release/github-release.ts +12 -0
  61. package/src/release/index.ts +218 -40
  62. package/src/release/orchestration.ts +55 -3
package/README.md CHANGED
@@ -228,21 +228,20 @@ candidates to downstream dependents because that would duplicate Nx's dependency
228
228
 
229
229
  `smoo` keeps Nx target names predictable and separates tool work from aggregate workflows.
230
230
 
231
- Official Nx plugins own targets they already know how to infer. For example, `@nx/js/typescript` owns TypeScript library
232
- builds, so a package `tsconfig.lib.json` produces the concrete target `tsc-js`.
233
-
234
- `@smoothbricks/nx-plugin` only fills SmoothBricks convention gaps that official plugins do not provide. Today that means
235
- Bun test typechecking, non-TypeScript build-tool steps, and aggregate targets.
231
+ `@smoothbricks/nx-plugin` owns transformer-aware TypeScript targets because this workspace compiles through `ttsc`.
232
+ Configuring `@nx/js/typescript` would run `tsc`/`tsgo` directly and bypass the Typia and LMAO transformers. A package
233
+ `tsconfig.lib.json` therefore produces the concrete `tsc-js` target from the SmoothBricks plugin. The same plugin also
234
+ infers Bun test typechecking, non-TypeScript build-tool steps, and aggregate targets.
236
235
 
237
236
  Concrete targets use `{tool}-{output}` names and describe the tool that runs and the artifact or purpose it produces:
238
237
 
239
- - `tsc-js` comes from the official TypeScript plugin and runs `tsc` for package JavaScript/declaration output.
238
+ - `tsc-js` comes from `@smoothbricks/nx-plugin` and runs `ttsc` for package JavaScript/declaration output.
240
239
  - Packages that run `bun test` must have `tsconfig.test.json`. Bun executes tests without typechecking, so smoo creates
241
240
  a no-emit `typecheck-tests` target from that config and wires it into validation. Other test runners may own their own
242
241
  typecheck path.
243
242
  - Test tsconfigs are validation configs, not TypeScript build-mode projects. They must use `noEmit`, must not set
244
243
  `composite: true`, and package root `tsconfig.json` must not reference `./tsconfig.test.json`. The inferred
245
- `typecheck-tests` target runs `tsc --noEmit -p tsconfig.test.json` after `build` instead.
244
+ `typecheck-tests` target runs `ttsc --noEmit -p tsconfig.test.json` after `build` instead.
246
245
  - Non-TypeScript build steps come from explicit tool configuration. For example, a package `build.zig` must expose named
247
246
  `b.step("name", ...)` entries; each non-reserved step becomes a `zig-name` target such as `zig-wasm`.
248
247
  - `build` is an aggregate. It exists only when there is at least one concrete build target such as `tsc-js`,
@@ -250,6 +249,13 @@ Concrete targets use `{tool}-{output}` names and describe the tool that runs and
250
249
  `*-html`, `*-css`, `*-ios`, `*-android`, `*-native`, `*-napi`, `*-bun`, and `*-wasm` instead of duplicating commands.
251
250
  - `lint` is an aggregate validation target. It is not a formatting target.
252
251
 
252
+ The root `@typescript/native` dependency follows TypeScript's documented side-by-side pattern: it aliases TypeScript 7
253
+ and supplies the native compiler used by `ttsc`. Because `ttsc` resolves only the unscoped package by default, the
254
+ managed devenv shell sets `TTSC_TSGO_BINARY` to `node_modules/@typescript/native/bin/tsc`; the GitHub setup action
255
+ persists that absolute path through `GITHUB_ENV`. Nx and other JavaScript tooling still require the full TypeScript
256
+ compiler API, so workspace `typescript` dependencies stay on TypeScript 6. Both dependencies and the environment binding
257
+ are required: TypeScript 7 under the unscoped name breaks Nx API calls such as `readConfigFile`.
258
+
253
259
  Explicit Nx target names must not contain `:`. Nx already uses colon syntax at the CLI boundary:
254
260
  `project:target:configuration`. Allowing target names like `build:wasm` makes command parsing and package-script aliases
255
261
  look like configurations, and it prevents a clean split between concrete tool-output targets and aggregate targets.
@@ -286,6 +292,9 @@ smoo monorepo diff
286
292
 
287
293
  `check` fails when a managed file is missing or stale. `diff` reports drift without writing files.
288
294
 
295
+ The generated publish workflow is canonical Prettier YAML. Running the repository formatter over
296
+ `.github/workflows/publish.yml` is byte-stable and does not create managed-file drift.
297
+
289
298
  ## Formatting And Git Hooks
290
299
 
291
300
  The root `lint:fix` script runs [`git-format-staged`][git-format-staged] with
@@ -467,12 +476,15 @@ Publishing:
467
476
  for targeted bootstraps.
468
477
  - `smoo release trust-publisher` configures [npm trusted publishing][npm-trusted-publishing] for every owned release
469
478
  package. It uses the root `package.json` `repository.url` as the GitHub `owner/repo`, uses `publish.yml` as the
470
- trusted workflow, and runs `npm trust` through `nix shell nixpkgs#nodejs_latest` because the Lambda-pinned Node 24/npm
471
- toolchain may lag the npm CLI feature. It does not run a separate `npm login` before trust setup; `npm trust list` and
472
- `npm trust github` own authentication so npm can offer the 5-minute trust/publish challenge bypass. Pass
473
- `--package <name...>` to target specific owned packages. Pass `--bootstrap` to create missing npm package names first,
474
- then configure trusted publishing in the same command. With `--bootstrap`, `--skip-login` only skips the placeholder
475
- publish login. Existing matching trusted publishers are skipped via `npm trust list <package> --json`.
479
+ trusted workflow, grants that workflow npm's `--allow-publish` permission, and runs `npm trust` through
480
+ `nix shell nixpkgs#nodejs_latest` because the Lambda-pinned Node 24/npm toolchain may lag the npm CLI feature. It does
481
+ not pre-login: `npm trust list` and `npm trust github` own normal authentication so npm can offer the 5-minute
482
+ trust/publish challenge bypass. If `npm trust list` denies access, smoo reports the active npm identity and package
483
+ owners, opens `npm login --auth-type=web` once so the operator can switch accounts, then retries the lookup. A second
484
+ denial fails instead of looping. Pass `--package <name...>` to target specific owned packages. Pass `--bootstrap` to
485
+ create missing npm package names first, then configure trusted publishing in the same command. With `--bootstrap`,
486
+ `--skip-login` only skips the placeholder publish login. Existing matching trusted publishers are skipped via
487
+ `npm trust list <package> --json`.
476
488
 
477
489
  GitHub Releases:
478
490
 
package/dist/cli.js CHANGED
@@ -125,10 +125,25 @@ function buildProgram() {
125
125
  .command('repair-pending')
126
126
  .description('Repair incomplete older release commits before releasing the current HEAD')
127
127
  .option('--dry-run [dryRun]', 'run without pushing, publishing, or writing GitHub Releases')
128
+ .option('--ref <ref>', 'fixed release graph ref to inspect')
129
+ .option('--platform-outputs <path>', 'cross-platform repair outputs grouped by release SHA')
128
130
  .action(async (options) => {
131
+ // The source self-hosting shim has no Typia transform; release commands import transformed output validators.
129
132
  const { releaseRepairPending } = await import('./release/index.js');
130
133
  await releaseRepairPending(await findRepoRoot(), { ...options, dryRun: booleanOption(options.dryRun) });
131
134
  });
135
+ release
136
+ .command('build-platform-outputs')
137
+ .description('Build selected current and pending-release platform outputs')
138
+ .requiredOption('--bump <bump>', 'auto, patch, minor, major, or prerelease')
139
+ .requiredOption('--targets <targets>', 'comma-separated Nx platform target names or globs')
140
+ .requiredOption('--output <path>', 'output directory for current and repair artifacts')
141
+ .option('--ref <ref>', 'fixed release graph ref to inspect')
142
+ .action(async (options) => {
143
+ // The source self-hosting shim has no Typia transform; release commands import transformed output validators.
144
+ const { releaseCollectPlatformOutputs } = await import('./release/index.js');
145
+ await releaseCollectPlatformOutputs(await findRepoRoot(), options);
146
+ });
132
147
  release
133
148
  .command('version')
134
149
  .option('--bump <bump>', 'auto, patch, minor, major, or prerelease', 'auto')
@@ -268,10 +283,19 @@ function buildProgram() {
268
283
  .requiredOption('--targets <targets>')
269
284
  .option('--projects <projects>')
270
285
  .option('--configuration <configuration>')
286
+ .option('--collect-outputs <directory>')
271
287
  .action(async (options) => {
272
288
  const { githubCiNxRunMany } = await import('./github-ci/index.js');
273
289
  await githubCiNxRunMany(await findRepoRoot(), options);
274
290
  });
291
+ githubCi
292
+ .command('apply-outputs <directories...>')
293
+ .requiredOption('--source-sha <sha>', 'expected source commit SHA')
294
+ .action(async (directories, options) => {
295
+ // GitHub CI commands stay lazy so source self-hosting can initialize Typia only at manifest boundaries.
296
+ const { githubCiApplyOutputs } = await import('./github-ci/index.js');
297
+ await githubCiApplyOutputs(await findRepoRoot(), directories, options.sourceSha);
298
+ });
275
299
  githubCi
276
300
  .command('nx-deploy')
277
301
  .requiredOption('--configuration <configuration>')
@@ -1,5 +1,8 @@
1
+ import { type ProjectTargets } from '../nx/index.js';
2
+ import type { NxTargetRun } from './outputs.js';
1
3
  type NxSmartMode = 'auto' | 'affected' | 'run-many';
2
4
  export declare function cleanupGithubCiCache(root: string): Promise<void>;
5
+ export declare function nxSmartArgs(target: string, mode: 'affected' | 'run-many', configuration?: string): string[];
3
6
  export declare function githubCiNxSmart(root: string, options: {
4
7
  target: string;
5
8
  name?: string;
@@ -7,11 +10,23 @@ export declare function githubCiNxSmart(root: string, options: {
7
10
  mode?: NxSmartMode;
8
11
  configuration?: string;
9
12
  }): Promise<void>;
10
- export declare function githubCiNxRunMany(root: string, options: {
13
+ export interface NxRunManyOptions {
11
14
  targets: string;
12
15
  projects?: string;
13
16
  configuration?: string;
14
- }): Promise<void>;
17
+ collectOutputs?: string;
18
+ allowEmptyProjects?: boolean;
19
+ }
20
+ export interface ExpandedNxTargetRuns {
21
+ runs: NxTargetRun[];
22
+ unmatchedGlobs: string[];
23
+ }
24
+ export declare function expandNxTargetRuns(projects: ProjectTargets[], options: NxRunManyOptions): ExpandedNxTargetRuns;
25
+ export declare function expandNxTargetDependencyRuns(runs: NxTargetRun[]): NxTargetRun[];
26
+ export declare function nxRunManyArgs(run: NxTargetRun, configuration?: string): string[];
27
+ export declare function readGitHeadSha(root: string): Promise<string>;
28
+ export declare function githubCiNxRunMany(root: string, options: NxRunManyOptions): Promise<void>;
29
+ export declare function githubCiApplyOutputs(root: string, directories: string[], expectedSourceSha: string): Promise<void>;
15
30
  export declare function githubCiNxDeploy(root: string, options: {
16
31
  configuration: string;
17
32
  mode?: NxSmartMode;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/github-ci/index.ts"],"names":[],"mappings":"AAMA,KAAK,WAAW,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,CAAC;AAEpD,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBtE;AAmFD,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GACpG,OAAO,CAAC,IAAI,CAAC,CAef;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GACtE,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GACrG,OAAO,CAAC,IAAI,CAAC,CA0Bf"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/github-ci/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,cAAc,EAAsB,MAAM,gBAAgB,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,KAAK,WAAW,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,CAAC;AAEpD,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBtE;AAmFD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,UAAU,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAO3G;AAED,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GACpG,OAAO,CAAC,IAAI,CAAC,CAWf;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,cAAc,EAAE,EAAE,OAAO,EAAE,gBAAgB,GAAG,oBAAoB,CAiC9G;AAED,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAwC/E;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,WAAW,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAehF;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGlE;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAa9F;AAED,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EAAE,EACrB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,IAAI,CAAC,CAGf;AAgCD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GACrG,OAAO,CAAC,IAAI,CAAC,CA0Bf"}
@@ -3,6 +3,7 @@ import { appendFile, mkdtemp, realpath, rename, rm } from 'node:fs/promises';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { $ } from 'bun';
5
5
  import { decode, run, runStatus } from '../lib/run.js';
6
+ import { readProjectTargets } from '../nx/index.js';
6
7
  export async function cleanupGithubCiCache(root) {
7
8
  const githubOutput = process.env.GITHUB_OUTPUT;
8
9
  const markCacheReady = async (ready) => {
@@ -100,31 +101,160 @@ async function addReferencesFrom(roots, path, cwd) {
100
101
  }
101
102
  }
102
103
  }
104
+ export function nxSmartArgs(target, mode, configuration) {
105
+ const args = [mode, '-t', target];
106
+ if (configuration) {
107
+ args.push(`--configuration=${configuration}`);
108
+ }
109
+ args.push(`--exclude=tag:ci:skip:${target}`, '--parallel=5');
110
+ return args;
111
+ }
103
112
  export async function githubCiNxSmart(root, options) {
104
113
  const name = options.name ?? options.target;
105
114
  const step = options.step ?? '';
106
115
  await createGithubStatus(name, step);
107
116
  const mode = resolveNxSmartMode(options.mode ?? 'auto');
108
- const nxArgs = mode === 'run-many' ? ['run-many', '-t', options.target] : ['affected', '-t', options.target];
109
- if (options.configuration) {
110
- nxArgs.push(`--configuration=${options.configuration}`);
111
- }
112
- nxArgs.push('--parallel=5');
117
+ const nxArgs = nxSmartArgs(options.target, mode, options.configuration);
113
118
  const status = await runStatus('nx', nxArgs, root);
114
119
  await updateGithubStatus(name, status === 0 ? 'success' : 'failure', step);
115
120
  if (status !== 0) {
116
121
  throw new Error(`nx ${nxArgs.join(' ')} failed with exit code ${status}`);
117
122
  }
118
123
  }
124
+ export function expandNxTargetRuns(projects, options) {
125
+ const selectedProjects = selectProjects(projects, options.projects);
126
+ if (options.projects !== undefined && selectedProjects.length === 0 && options.allowEmptyProjects !== true) {
127
+ throw new Error(`No Nx projects matched --projects ${options.projects}.`);
128
+ }
129
+ const selectedTargetNames = [...new Set(selectedProjects.flatMap((project) => project.targets))].sort((a, b) => a.localeCompare(b));
130
+ const runs = [];
131
+ const unmatchedGlobs = [];
132
+ const addedTargets = new Set();
133
+ for (const targetPattern of commaSeparatedValues(options.targets)) {
134
+ const isGlob = isGlobPattern(targetPattern);
135
+ const targets = isGlob
136
+ ? selectedTargetNames.filter((target) => new Bun.Glob(targetPattern).match(target))
137
+ : [targetPattern];
138
+ if (isGlob && targets.length === 0) {
139
+ unmatchedGlobs.push(targetPattern);
140
+ }
141
+ for (const target of targets) {
142
+ if (addedTargets.has(target)) {
143
+ continue;
144
+ }
145
+ const owners = selectedProjects.filter((project) => project.targets.includes(target));
146
+ const runProjects = owners.length > 0 ? owners : selectedProjects;
147
+ if (runProjects.length === 0) {
148
+ continue;
149
+ }
150
+ addedTargets.add(target);
151
+ runs.push({ target, projects: runProjects });
152
+ }
153
+ }
154
+ return { runs, unmatchedGlobs };
155
+ }
156
+ export function expandNxTargetDependencyRuns(runs) {
157
+ const expanded = [];
158
+ const added = new Set();
159
+ const visiting = new Set();
160
+ const visit = (project, target) => {
161
+ const key = `${project.project}:${target}`;
162
+ if (added.has(key)) {
163
+ return;
164
+ }
165
+ if (visiting.has(key)) {
166
+ throw new Error(`Nx target dependency cycle detected at ${key}.`);
167
+ }
168
+ visiting.add(key);
169
+ for (const dependency of project.targetDependencies?.get(target) ?? []) {
170
+ if (dependency.startsWith('^')) {
171
+ continue;
172
+ }
173
+ const dependencyTargets = isGlobPattern(dependency)
174
+ ? project.targets.filter((candidate) => new Bun.Glob(dependency).match(candidate))
175
+ : project.targets.includes(dependency)
176
+ ? [dependency]
177
+ : [];
178
+ for (const dependencyTarget of dependencyTargets.sort((left, right) => left.localeCompare(right))) {
179
+ visit(project, dependencyTarget);
180
+ }
181
+ }
182
+ visiting.delete(key);
183
+ added.add(key);
184
+ if ((project.targetOutputs?.get(target)?.length ?? 0) > 0) {
185
+ expanded.push({ target, projects: [project] });
186
+ }
187
+ };
188
+ for (const run of runs) {
189
+ for (const project of run.projects) {
190
+ visit(project, run.target);
191
+ }
192
+ }
193
+ return expanded;
194
+ }
195
+ export function nxRunManyArgs(run, configuration) {
196
+ if (run.projects.length === 0) {
197
+ throw new Error(`Nx target ${run.target} has no selected projects.`);
198
+ }
199
+ const nxArgs = [
200
+ 'run-many',
201
+ '-t',
202
+ run.target,
203
+ `--projects=${run.projects.map((project) => project.project).join(',')}`,
204
+ ];
205
+ if (configuration) {
206
+ nxArgs.push(`--configuration=${configuration}`);
207
+ }
208
+ nxArgs.push('--parallel=5');
209
+ return nxArgs;
210
+ }
211
+ export async function readGitHeadSha(root) {
212
+ // invariant throw: GitHub CI commands require a valid repository root.
213
+ return decode((await $ `git rev-parse HEAD`.cwd(root).quiet()).stdout).trim();
214
+ }
119
215
  export async function githubCiNxRunMany(root, options) {
120
- const nxArgs = ['run-many', '-t', options.targets, '--parallel=5'];
121
- if (options.projects) {
122
- nxArgs.push(`--projects=${options.projects}`);
216
+ const expanded = expandNxTargetRuns(await readProjectTargets(root), options);
217
+ if (expanded.unmatchedGlobs.length > 0) {
218
+ console.log(`No Nx targets matched target glob(s): ${expanded.unmatchedGlobs.join(', ')}; skipping.`);
219
+ }
220
+ for (const targetRun of expanded.runs) {
221
+ await run('nx', nxRunManyArgs(targetRun, options.configuration), root);
123
222
  }
124
- if (options.configuration) {
125
- nxArgs.push(`--configuration=${options.configuration}`);
223
+ if (options.collectOutputs) {
224
+ const { collectNxOutputs } = await loadOutputBoundary();
225
+ const sourceSha = await readGitHeadSha(root);
226
+ await collectNxOutputs(root, options.collectOutputs, expandNxTargetDependencyRuns(expanded.runs), sourceSha);
126
227
  }
127
- await run('nx', nxArgs, root);
228
+ }
229
+ export async function githubCiApplyOutputs(root, directories, expectedSourceSha) {
230
+ const { applyCollectedOutputs } = await loadOutputBoundary();
231
+ await applyCollectedOutputs(root, directories, expectedSourceSha);
232
+ }
233
+ async function loadOutputBoundary() {
234
+ if (import.meta.url.endsWith('/src/github-ci/index.ts')) {
235
+ // The source self-hosting shim has no Typia transform; register it before loading manifest validators.
236
+ await import('@smoothbricks/validation/bun/preload');
237
+ }
238
+ // This boundary must stay lazy because the transformed dist and source self-hosting paths initialize Typia differently.
239
+ return import('./outputs.js');
240
+ }
241
+ function isGlobPattern(value) {
242
+ return /[*?{[]/.test(value);
243
+ }
244
+ function selectProjects(projects, selectors) {
245
+ if (selectors === undefined) {
246
+ return projects.slice().sort((left, right) => left.project.localeCompare(right.project));
247
+ }
248
+ const patterns = commaSeparatedValues(selectors);
249
+ return projects
250
+ .filter((project) => patterns.some((pattern) => new Bun.Glob(pattern).match(project.project)))
251
+ .sort((left, right) => left.project.localeCompare(right.project));
252
+ }
253
+ function commaSeparatedValues(value) {
254
+ return value
255
+ .split(',')
256
+ .map((entry) => entry.trim())
257
+ .filter(Boolean);
128
258
  }
129
259
  export async function githubCiNxDeploy(root, options) {
130
260
  const name = options.name ?? `Deploy ${options.configuration}`;
@@ -0,0 +1,22 @@
1
+ import { type ProjectTargets } from '../nx/index.js';
2
+ export interface CollectedOutputFile {
3
+ project: string;
4
+ target: string;
5
+ output: string;
6
+ path: string;
7
+ size: number;
8
+ sha256: string;
9
+ }
10
+ export interface CollectedOutputsManifest {
11
+ version: 1;
12
+ sourceSha: string;
13
+ files: CollectedOutputFile[];
14
+ }
15
+ export interface NxTargetRun {
16
+ target: string;
17
+ projects: ProjectTargets[];
18
+ }
19
+ export declare function collectNxOutputs(root: string, destination: string, runs: NxTargetRun[], sourceSha: string): Promise<CollectedOutputsManifest>;
20
+ export declare function applyCollectedOutputs(root: string, directories: string[], expectedSourceSha: string, projects?: ProjectTargets[]): Promise<void>;
21
+ export declare function resolveDeclaredOutput(declaredOutput: string, project: ProjectTargets): string;
22
+ //# sourceMappingURL=outputs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outputs.d.ts","sourceRoot":"","sources":["../../src/github-ci/outputs.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,cAAc,EAAsB,MAAM,gBAAgB,CAAC;AAEzE,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,CAAC,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,mBAAmB,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAWD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,WAAW,EAAE,EACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,wBAAwB,CAAC,CAgEnC;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EAAE,EACrB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,CAAC,EAAE,cAAc,EAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CAqGf;AAED,wBAAgB,qBAAqB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,CAa7F"}