@smoothbricks/cli 0.10.1 → 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 (77) 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 +145 -14
  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/lib/conflict-markers.d.ts.map +1 -1
  10. package/dist/lib/workspace.d.ts +1 -1
  11. package/dist/lib/workspace.d.ts.map +1 -1
  12. package/dist/monorepo/ci-workflow.d.ts.map +1 -1
  13. package/dist/monorepo/ci-workflow.js +2 -1
  14. package/dist/monorepo/managed-files.d.ts +2 -0
  15. package/dist/monorepo/managed-files.d.ts.map +1 -1
  16. package/dist/monorepo/managed-files.js +50 -5
  17. package/dist/monorepo/package-policy.d.ts.map +1 -1
  18. package/dist/monorepo/package-policy.js +26 -0
  19. package/dist/monorepo/packs/index.d.ts +3 -0
  20. package/dist/monorepo/packs/index.d.ts.map +1 -1
  21. package/dist/monorepo/packs/index.js +5 -2
  22. package/dist/monorepo/publish-workflow.d.ts +1 -0
  23. package/dist/monorepo/publish-workflow.d.ts.map +1 -1
  24. package/dist/monorepo/publish-workflow.js +240 -2
  25. package/dist/monorepo/tool-validation.d.ts.map +1 -1
  26. package/dist/monorepo/tool-validation.js +20 -7
  27. package/dist/nx/index.d.ts +6 -0
  28. package/dist/nx/index.d.ts.map +1 -1
  29. package/dist/nx/index.js +55 -5
  30. package/dist/release/github-release.d.ts +5 -2
  31. package/dist/release/github-release.d.ts.map +1 -1
  32. package/dist/release/github-release.js +11 -0
  33. package/dist/release/index.d.ts +19 -1
  34. package/dist/release/index.d.ts.map +1 -1
  35. package/dist/release/index.js +140 -31
  36. package/dist/release/orchestration.d.ts +8 -1
  37. package/dist/release/orchestration.d.ts.map +1 -1
  38. package/dist/release/orchestration.js +38 -1
  39. package/dist/wrangler/prepare-env.d.ts.map +1 -1
  40. package/dist/wrangler/prepare-env.js +1 -1
  41. package/dist/wrangler/scaffold.d.ts.map +1 -1
  42. package/dist/wrangler/scaffold.js +1 -1
  43. package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +5 -0
  44. package/managed/raw/tooling/direnv/setup-environment.ts +32 -1
  45. package/managed/templates/github/actions/cache-nix-devenv/action.yml +12 -10
  46. package/managed/templates/github/actions/cache-node-modules/action.yml +3 -2
  47. package/managed/templates/github/actions/cache-nx/action.yml +2 -2
  48. package/managed/templates/github/actions/save-nix-devenv/action.yml +11 -3
  49. package/package.json +7 -14
  50. package/src/cli.ts +30 -4
  51. package/src/github-ci/index.test.ts +486 -0
  52. package/src/github-ci/index.ts +177 -16
  53. package/src/github-ci/outputs.ts +506 -0
  54. package/src/monorepo/__tests__/ci-workflow.test.ts +13 -0
  55. package/src/monorepo/__tests__/publish-workflow.test.ts +234 -5
  56. package/src/monorepo/ci-workflow.ts +3 -1
  57. package/src/monorepo/managed-files.test.ts +75 -0
  58. package/src/monorepo/managed-files.ts +61 -5
  59. package/src/monorepo/package-policy.test.ts +57 -38
  60. package/src/monorepo/package-policy.ts +28 -0
  61. package/src/monorepo/packs/index.test.ts +18 -1
  62. package/src/monorepo/packs/index.ts +7 -3
  63. package/src/monorepo/publish-workflow.ts +481 -2
  64. package/src/monorepo/tool-validation.test.ts +59 -24
  65. package/src/monorepo/tool-validation.ts +21 -7
  66. package/src/monorepo/wrangler.test.ts +9 -2
  67. package/src/nx/index.test.ts +39 -0
  68. package/src/nx/index.ts +65 -5
  69. package/src/release/__tests__/fixture-repo.test.ts +18 -16
  70. package/src/release/__tests__/github-release.test.ts +11 -0
  71. package/src/release/__tests__/orchestration.test.ts +57 -2
  72. package/src/release/__tests__/trust-publisher.test.ts +91 -2
  73. package/src/release/github-release.ts +12 -0
  74. package/src/release/index.ts +218 -40
  75. package/src/release/orchestration.ts +55 -3
  76. package/src/wrangler/prepare-env.ts +14 -4
  77. package/src/wrangler/scaffold.ts +8 -3
@@ -3,6 +3,8 @@ 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 { type ProjectTargets, readProjectTargets } from '../nx/index.js';
7
+ import type { NxTargetRun } from './outputs.js';
6
8
 
7
9
  type NxSmartMode = 'auto' | 'affected' | 'run-many';
8
10
 
@@ -113,6 +115,15 @@ async function addReferencesFrom(roots: Set<string>, path: string, cwd: string):
113
115
  }
114
116
  }
115
117
 
118
+ export function nxSmartArgs(target: string, mode: 'affected' | 'run-many', configuration?: string): string[] {
119
+ const args = [mode, '-t', target];
120
+ if (configuration) {
121
+ args.push(`--configuration=${configuration}`);
122
+ }
123
+ args.push(`--exclude=tag:ci:skip:${target}`, '--parallel=5');
124
+ return args;
125
+ }
126
+
116
127
  export async function githubCiNxSmart(
117
128
  root: string,
118
129
  options: { target: string; name?: string; step?: string; mode?: NxSmartMode; configuration?: string },
@@ -121,11 +132,7 @@ export async function githubCiNxSmart(
121
132
  const step = options.step ?? '';
122
133
  await createGithubStatus(name, step);
123
134
  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');
135
+ const nxArgs = nxSmartArgs(options.target, mode, options.configuration);
129
136
  const status = await runStatus('nx', nxArgs, root);
130
137
  await updateGithubStatus(name, status === 0 ? 'success' : 'failure', step);
131
138
  if (status !== 0) {
@@ -133,18 +140,170 @@ export async function githubCiNxSmart(
133
140
  }
134
141
  }
135
142
 
136
- export async function githubCiNxRunMany(
143
+ export interface NxRunManyOptions {
144
+ targets: string;
145
+ projects?: string;
146
+ configuration?: string;
147
+ collectOutputs?: string;
148
+ allowEmptyProjects?: boolean;
149
+ }
150
+
151
+ export interface ExpandedNxTargetRuns {
152
+ runs: NxTargetRun[];
153
+ unmatchedGlobs: string[];
154
+ }
155
+
156
+ export function expandNxTargetRuns(projects: ProjectTargets[], options: NxRunManyOptions): ExpandedNxTargetRuns {
157
+ const selectedProjects = selectProjects(projects, options.projects);
158
+ if (options.projects !== undefined && selectedProjects.length === 0 && options.allowEmptyProjects !== true) {
159
+ throw new Error(`No Nx projects matched --projects ${options.projects}.`);
160
+ }
161
+ const selectedTargetNames = [...new Set(selectedProjects.flatMap((project) => project.targets))].sort((a, b) =>
162
+ a.localeCompare(b),
163
+ );
164
+ const runs: NxTargetRun[] = [];
165
+ const unmatchedGlobs: string[] = [];
166
+ const addedTargets = new Set<string>();
167
+ for (const targetPattern of commaSeparatedValues(options.targets)) {
168
+ const isGlob = isGlobPattern(targetPattern);
169
+ const targets = isGlob
170
+ ? selectedTargetNames.filter((target) => new Bun.Glob(targetPattern).match(target))
171
+ : [targetPattern];
172
+ if (isGlob && targets.length === 0) {
173
+ unmatchedGlobs.push(targetPattern);
174
+ }
175
+ for (const target of targets) {
176
+ if (addedTargets.has(target)) {
177
+ continue;
178
+ }
179
+ const owners = selectedProjects.filter((project) => project.targets.includes(target));
180
+ const runProjects = owners.length > 0 ? owners : selectedProjects;
181
+ if (runProjects.length === 0) {
182
+ continue;
183
+ }
184
+ addedTargets.add(target);
185
+ runs.push({ target, projects: runProjects });
186
+ }
187
+ }
188
+ return { runs, unmatchedGlobs };
189
+ }
190
+
191
+ export function expandNxTargetDependencyRuns(runs: NxTargetRun[]): NxTargetRun[] {
192
+ const expanded: NxTargetRun[] = [];
193
+ const added = new Set<string>();
194
+ const visiting = new Set<string>();
195
+
196
+ const visit = (project: ProjectTargets, target: string): void => {
197
+ const key = `${project.project}:${target}`;
198
+ if (added.has(key)) {
199
+ return;
200
+ }
201
+ if (visiting.has(key)) {
202
+ throw new Error(`Nx target dependency cycle detected at ${key}.`);
203
+ }
204
+ visiting.add(key);
205
+ for (const dependency of project.targetDependencies?.get(target) ?? []) {
206
+ if (dependency.startsWith('^')) {
207
+ continue;
208
+ }
209
+ const dependencyTargets = isGlobPattern(dependency)
210
+ ? project.targets.filter((candidate) => new Bun.Glob(dependency).match(candidate))
211
+ : project.targets.includes(dependency)
212
+ ? [dependency]
213
+ : [];
214
+ for (const dependencyTarget of dependencyTargets.sort((left, right) => left.localeCompare(right))) {
215
+ visit(project, dependencyTarget);
216
+ }
217
+ }
218
+ visiting.delete(key);
219
+ added.add(key);
220
+ if ((project.targetOutputs?.get(target)?.length ?? 0) > 0) {
221
+ expanded.push({ target, projects: [project] });
222
+ }
223
+ };
224
+
225
+ for (const run of runs) {
226
+ for (const project of run.projects) {
227
+ visit(project, run.target);
228
+ }
229
+ }
230
+ return expanded;
231
+ }
232
+
233
+ export function nxRunManyArgs(run: NxTargetRun, configuration?: string): string[] {
234
+ if (run.projects.length === 0) {
235
+ throw new Error(`Nx target ${run.target} has no selected projects.`);
236
+ }
237
+ const nxArgs = [
238
+ 'run-many',
239
+ '-t',
240
+ run.target,
241
+ `--projects=${run.projects.map((project) => project.project).join(',')}`,
242
+ ];
243
+ if (configuration) {
244
+ nxArgs.push(`--configuration=${configuration}`);
245
+ }
246
+ nxArgs.push('--parallel=5');
247
+ return nxArgs;
248
+ }
249
+
250
+ export async function readGitHeadSha(root: string): Promise<string> {
251
+ // invariant throw: GitHub CI commands require a valid repository root.
252
+ return decode((await $`git rev-parse HEAD`.cwd(root).quiet()).stdout).trim();
253
+ }
254
+
255
+ export async function githubCiNxRunMany(root: string, options: NxRunManyOptions): Promise<void> {
256
+ const expanded = expandNxTargetRuns(await readProjectTargets(root), options);
257
+ if (expanded.unmatchedGlobs.length > 0) {
258
+ console.log(`No Nx targets matched target glob(s): ${expanded.unmatchedGlobs.join(', ')}; skipping.`);
259
+ }
260
+ for (const targetRun of expanded.runs) {
261
+ await run('nx', nxRunManyArgs(targetRun, options.configuration), root);
262
+ }
263
+ if (options.collectOutputs) {
264
+ const { collectNxOutputs } = await loadOutputBoundary();
265
+ const sourceSha = await readGitHeadSha(root);
266
+ await collectNxOutputs(root, options.collectOutputs, expandNxTargetDependencyRuns(expanded.runs), sourceSha);
267
+ }
268
+ }
269
+
270
+ export async function githubCiApplyOutputs(
137
271
  root: string,
138
- options: { targets: string; projects?: string; configuration?: string },
272
+ directories: string[],
273
+ expectedSourceSha: string,
139
274
  ): Promise<void> {
140
- const nxArgs = ['run-many', '-t', options.targets, '--parallel=5'];
141
- if (options.projects) {
142
- nxArgs.push(`--projects=${options.projects}`);
275
+ const { applyCollectedOutputs } = await loadOutputBoundary();
276
+ await applyCollectedOutputs(root, directories, expectedSourceSha);
277
+ }
278
+
279
+ async function loadOutputBoundary() {
280
+ if (import.meta.url.endsWith('/src/github-ci/index.ts')) {
281
+ // The source self-hosting shim has no Typia transform; register it before loading manifest validators.
282
+ await import('@smoothbricks/validation/bun/preload');
143
283
  }
144
- if (options.configuration) {
145
- nxArgs.push(`--configuration=${options.configuration}`);
284
+ // This boundary must stay lazy because the transformed dist and source self-hosting paths initialize Typia differently.
285
+ return import('./outputs.js');
286
+ }
287
+
288
+ function isGlobPattern(value: string): boolean {
289
+ return /[*?{[]/.test(value);
290
+ }
291
+
292
+ function selectProjects(projects: ProjectTargets[], selectors: string | undefined): ProjectTargets[] {
293
+ if (selectors === undefined) {
294
+ return projects.slice().sort((left, right) => left.project.localeCompare(right.project));
146
295
  }
147
- await run('nx', nxArgs, root);
296
+ const patterns = commaSeparatedValues(selectors);
297
+ return projects
298
+ .filter((project) => patterns.some((pattern) => new Bun.Glob(pattern).match(project.project)))
299
+ .sort((left, right) => left.project.localeCompare(right.project));
300
+ }
301
+
302
+ function commaSeparatedValues(value: string): string[] {
303
+ return value
304
+ .split(',')
305
+ .map((entry) => entry.trim())
306
+ .filter(Boolean);
148
307
  }
149
308
 
150
309
  export async function githubCiNxDeploy(
@@ -220,9 +379,11 @@ function nxProjectList(output: string): string[] {
220
379
  }
221
380
 
222
381
  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;
382
+ return isRecord(value) ? value : undefined;
383
+ }
384
+
385
+ function isRecord(value: unknown): value is Record<string, unknown> {
386
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
226
387
  }
227
388
 
228
389
  async function createGithubStatus(name: string, step: string): Promise<void> {