@smoothbricks/cli 0.10.5 โ†’ 0.10.7

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 (65) hide show
  1. package/dist/cli.js +3 -2
  2. package/dist/github-ci/index.d.ts +1 -0
  3. package/dist/github-ci/index.d.ts.map +1 -1
  4. package/dist/github-ci/index.js +28 -5
  5. package/dist/lib/json.d.ts +3 -1
  6. package/dist/lib/json.d.ts.map +1 -1
  7. package/dist/lib/json.js +5 -20
  8. package/dist/lib/workspace.d.ts.map +1 -1
  9. package/dist/lib/workspace.js +1 -0
  10. package/dist/monorepo/ci-workflow.d.ts +2 -0
  11. package/dist/monorepo/ci-workflow.d.ts.map +1 -1
  12. package/dist/monorepo/ci-workflow.js +10 -4
  13. package/dist/monorepo/github-runs-on.d.ts +14 -0
  14. package/dist/monorepo/github-runs-on.d.ts.map +1 -0
  15. package/dist/monorepo/github-runs-on.js +43 -0
  16. package/dist/monorepo/index.d.ts +2 -2
  17. package/dist/monorepo/index.d.ts.map +1 -1
  18. package/dist/monorepo/index.js +7 -7
  19. package/dist/monorepo/managed-files.d.ts +14 -3
  20. package/dist/monorepo/managed-files.d.ts.map +1 -1
  21. package/dist/monorepo/managed-files.js +54 -74
  22. package/dist/monorepo/package-policy.js +2 -2
  23. package/dist/monorepo/packs/index.d.ts.map +1 -1
  24. package/dist/monorepo/packs/index.js +3 -1
  25. package/dist/monorepo/publish-workflow.d.ts +3 -0
  26. package/dist/monorepo/publish-workflow.d.ts.map +1 -1
  27. package/dist/monorepo/publish-workflow.js +17 -14
  28. package/dist/monorepo/runtime.d.ts +2 -2
  29. package/dist/monorepo/runtime.d.ts.map +1 -1
  30. package/dist/monorepo/runtime.js +11 -16
  31. package/dist/monorepo/tool-validation.d.ts.map +1 -1
  32. package/dist/monorepo/tool-validation.js +20 -23
  33. package/dist/nx/index.d.ts +7 -4
  34. package/dist/nx/index.d.ts.map +1 -1
  35. package/dist/nx/index.js +117 -56
  36. package/managed/raw/tooling/devenv +71 -0
  37. package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +24 -7
  38. package/managed/templates/github/actions/cache-nix-devenv/action.yml +13 -11
  39. package/managed/templates/github/actions/save-nix-devenv/action.yml +34 -3
  40. package/managed/templates/github/actions/setup-devenv/action.yml +44 -15
  41. package/managed/templates/github/workflows/managed-files.yml +64 -12
  42. package/package.json +4 -4
  43. package/src/cli.ts +10 -3
  44. package/src/github-ci/index.test.ts +53 -4
  45. package/src/github-ci/index.ts +37 -5
  46. package/src/lib/json.ts +3 -1
  47. package/src/lib/workspace.ts +1 -0
  48. package/src/monorepo/__tests__/ci-workflow.test.ts +37 -1
  49. package/src/monorepo/__tests__/publish-workflow.test.ts +39 -4
  50. package/src/monorepo/ci-workflow.ts +14 -4
  51. package/src/monorepo/github-runs-on.ts +45 -0
  52. package/src/monorepo/index.ts +7 -7
  53. package/src/monorepo/managed-files.test.ts +58 -0
  54. package/src/monorepo/managed-files.ts +59 -76
  55. package/src/monorepo/package-policy.test.ts +31 -0
  56. package/src/monorepo/package-policy.ts +2 -2
  57. package/src/monorepo/packs/index.ts +3 -1
  58. package/src/monorepo/publish-workflow.ts +28 -15
  59. package/src/monorepo/runtime.test.ts +23 -17
  60. package/src/monorepo/runtime.ts +12 -17
  61. package/src/monorepo/tool-validation.test.ts +15 -8
  62. package/src/monorepo/tool-validation.ts +20 -25
  63. package/src/nx/index.test.ts +45 -14
  64. package/src/nx/index.ts +133 -64
  65. package/managed/raw/patches/ttsc@0.19.3.patch +0 -67
@@ -40,8 +40,7 @@ const rootDevDependencies = [
40
40
  },
41
41
  { name: 'nx', fallbackVersion: '23.1.0', minimumVersion: '23.1.0' },
42
42
  { name: 'prettier', fallbackVersion: '^3.6.1', minimumVersion: '3.6.0', prefix: '^' },
43
- // Exact pin: Bun patchedDependencies keys are version-locked (see patches/ttsc@0.19.3.patch).
44
- { name: 'ttsc', fallbackVersion: '0.19.3' },
43
+ { name: 'ttsc', fallbackVersion: '0.25.0' },
45
44
  // Nx and typescript-eslint still load the TypeScript JS API (6.x).
46
45
  // Compilation is exclusively delegated to ttsc by the Nx plugin targets.
47
46
  { name: 'typescript', fallbackVersion: '^6.0.3', minimumVersion: '6.0.0', prefix: '^' },
@@ -56,8 +55,7 @@ const requiredDevenvPackages = ['bun', 'git', 'git-format-staged', 'jq', 'alejan
56
55
  // with the runtime is validated against the live PATH (validateRootRuntimeVersions),
57
56
  // never against a version template here.
58
57
  const nodePackagePattern = /(^|\s)nodejs(_\d+|_latest)?(\s|#|$)/m;
59
- const TTSC_PATCHED_DEPENDENCY_KEY = 'ttsc@0.19.3';
60
- const TTSC_PATCH_PATH = 'patches/ttsc@0.19.3.patch';
58
+ const obsoleteTtscPatchedDependencyKey = 'ttsc@0.19.3';
61
59
  export async function applyToolConfigDefaults(root) {
62
60
  const context = await readToolContext(root);
63
61
  await applyRootPackageToolDefaults(root, context);
@@ -67,7 +65,7 @@ export async function applyToolConfigDefaults(root) {
67
65
  export async function validateToolConfig(root) {
68
66
  const context = await readToolContext(root);
69
67
  return (validateRootDevDependencies(context.policy, context.rootPackage) +
70
- validateTtscPatchedDependency(context.rootPackage) +
68
+ validateObsoleteTtscPatchRemoved(context.rootPackage) +
71
69
  validateToolingPackage(root, context.policy) +
72
70
  validateToolingWorkspace(context.rootPackage) +
73
71
  validateDevenvPackages(root));
@@ -89,7 +87,7 @@ export async function applyRootDevDependencyDefaults(root, context) {
89
87
  if (delete devDependencies['@smoothbricks/cli']) {
90
88
  changed = true;
91
89
  }
92
- changed = ensureTtscPatchedDependency(pkg) || changed;
90
+ changed = removeObsoleteTtscPatch(pkg) || changed;
93
91
  if (changed) {
94
92
  writeJsonObject(join(root, 'package.json'), pkg);
95
93
  console.log('updated package.json workspace tool dependencies');
@@ -105,7 +103,6 @@ async function applyRootPackageToolDefaults(root, context) {
105
103
  }
106
104
  let dependencyChanged = false;
107
105
  let workspaceChanged = false;
108
- let patchChanged = false;
109
106
  const devDependencies = ensureDependencyMap(pkg, 'devDependencies');
110
107
  for (const dependency of rootDevDependencies) {
111
108
  const current = devDependencies[dependency.name];
@@ -118,8 +115,8 @@ async function applyRootPackageToolDefaults(root, context) {
118
115
  dependencyChanged = true;
119
116
  }
120
117
  workspaceChanged = addWorkspacePattern(pkg, 'tooling');
121
- patchChanged = ensureTtscPatchedDependency(pkg);
122
- if (dependencyChanged || workspaceChanged || patchChanged) {
118
+ dependencyChanged = removeObsoleteTtscPatch(pkg) || dependencyChanged;
119
+ if (dependencyChanged || workspaceChanged) {
123
120
  writeJsonObject(join(root, 'package.json'), pkg);
124
121
  }
125
122
  console.log(dependencyChanged
@@ -128,9 +125,6 @@ async function applyRootPackageToolDefaults(root, context) {
128
125
  console.log(workspaceChanged
129
126
  ? 'updated package.json tooling workspace'
130
127
  : 'unchanged package.json tooling workspace');
131
- console.log(patchChanged
132
- ? 'updated package.json patchedDependencies for ttsc'
133
- : 'unchanged package.json patchedDependencies for ttsc');
134
128
  }
135
129
  export function applyToolingPackageDefaults(root, policy) {
136
130
  const path = join(root, 'tooling', 'package.json');
@@ -219,20 +213,23 @@ export function validateRootDevDependencies(policy, rootPackage) {
219
213
  }
220
214
  return failures;
221
215
  }
222
- function ensureTtscPatchedDependency(pkg) {
223
- const patched = ensureDependencyMap(pkg, 'patchedDependencies');
224
- return setStringProperty(patched, TTSC_PATCHED_DEPENDENCY_KEY, TTSC_PATCH_PATH);
225
- }
226
- function validateTtscPatchedDependency(rootPackage) {
227
- if (!rootPackage) {
228
- return 0;
216
+ function removeObsoleteTtscPatch(pkg) {
217
+ const patchedDependencies = pkg.patchedDependencies;
218
+ if (!patchedDependencies || !(obsoleteTtscPatchedDependencyKey in patchedDependencies)) {
219
+ return false;
229
220
  }
230
- const actual = rootPackage.patchedDependencies?.[TTSC_PATCHED_DEPENDENCY_KEY];
231
- if (actual === TTSC_PATCH_PATH) {
221
+ delete patchedDependencies[obsoleteTtscPatchedDependencyKey];
222
+ if (Object.keys(patchedDependencies).length === 0) {
223
+ delete pkg.patchedDependencies;
224
+ }
225
+ return true;
226
+ }
227
+ function validateObsoleteTtscPatchRemoved(rootPackage) {
228
+ const patchedDependencies = rootPackage?.patchedDependencies;
229
+ if (!patchedDependencies || !(obsoleteTtscPatchedDependencyKey in patchedDependencies)) {
232
230
  return 0;
233
231
  }
234
- console.error(`package.json patchedDependencies.${TTSC_PATCHED_DEPENDENCY_KEY} must be ${TTSC_PATCH_PATH}` +
235
- (typeof actual === 'string' ? `; found ${actual}` : ' (required for declaration-emit fix)'));
232
+ console.error(`package.json patchedDependencies.${obsoleteTtscPatchedDependencyKey} must be removed; ttsc 0.25.0 emits typia declarations without the obsolete patch`);
236
233
  return 1;
237
234
  }
238
235
  export function validateToolingPackage(root, policy) {
@@ -1,4 +1,4 @@
1
- import { type NxProjectJson, type NxTargetConfig } from '../lib/json.js';
1
+ import type { NxProjectJson, NxTargetConfig, NxTargetOptions } from '../lib/json.js';
2
2
  export interface ProjectTargets {
3
3
  project: string;
4
4
  root?: string;
@@ -6,6 +6,7 @@ export interface ProjectTargets {
6
6
  buildDependsOn?: string[];
7
7
  targetDependencies?: Map<string, string[]>;
8
8
  targetExecutors?: Map<string, string>;
9
+ targetOptions?: Map<string, NxTargetOptions>;
9
10
  targetOutputs?: Map<string, string[]>;
10
11
  targetScripts?: Map<string, string>;
11
12
  }
@@ -14,24 +15,26 @@ export interface CommandInvocation {
14
15
  args: string[];
15
16
  }
16
17
  export declare function nxResetCommand(): CommandInvocation;
17
- export declare function nxShowProjectCommand(project: string): CommandInvocation;
18
18
  export declare function nxCacheDirectories(root: string): string[];
19
19
  export declare function targetNamesFromNxProjectJson(value: NxProjectJson | null | undefined): string[];
20
20
  export declare function projectRootFromNxProjectJson(value: NxProjectJson | null | undefined): string | undefined;
21
- export declare function buildDependsOnFromNxProjectJson(value: NxProjectJson | null | undefined): string[] | undefined;
22
21
  export declare function targetDependenciesFromNxProjectJson(value: NxProjectJson | null | undefined): Map<string, string[]>;
23
22
  export declare function targetExecutorsFromNxProjectJson(value: NxProjectJson | null | undefined): Map<string, string>;
23
+ export declare function targetOptionsFromNxProjectJson(value: NxProjectJson | null | undefined): Map<string, NxTargetOptions>;
24
24
  export declare function targetOutputsFromNxProjectJson(value: NxProjectJson | null | undefined): Map<string, string[]>;
25
25
  export declare function targetScriptsFromNxProjectJson(value: NxProjectJson | null | undefined): Map<string, string>;
26
26
  export declare function formatProjectTargetLines(projects: ProjectTargets[]): string;
27
27
  export declare function projectNamesWithTarget(projects: ProjectTargets[], target: string): string[];
28
- export declare function projectNamesFromNxShowProjectsOutput(output: string): string[];
29
28
  export declare function listTargets(root: string): Promise<void>;
30
29
  export declare function listProjects(root: string, options: {
31
30
  withTarget?: string;
32
31
  }): Promise<void>;
33
32
  export declare function resetCache(root: string): Promise<void>;
34
33
  export declare function cleanCache(root: string): Promise<void>;
34
+ export type NxProjects = Readonly<Record<string, NxProjectJson>>;
35
+ export declare function loadNxProjects(root: string): Promise<NxProjects>;
36
+ export declare function targetNamesFromProjects(projects: NxProjects): string[];
37
+ export declare function projectTargetsFromNxProjects(projects: NxProjects): ProjectTargets[];
35
38
  export declare function readProjectTargets(root: string): Promise<ProjectTargets[]>;
36
39
  export type { NxProjectJson, NxTargetConfig };
37
40
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/nx/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,cAAc,EAGpB,MAAM,gBAAgB,CAAC;AAGxB,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3C,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACtC,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,wBAAgB,cAAc,IAAI,iBAAiB,CAElD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAEvE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEzD;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAG9F;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAExG;AAED,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,GAAG,SAAS,CAE7G;AAED,wBAAgB,mCAAmC,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAwBlH;AAsBD,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAY7G;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAE7G;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAY3G;AA0BD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,MAAM,CAK3E;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,cAAc,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAK3F;AAED,wBAAgB,oCAAoC,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAsB7E;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAK7D;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhG;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5D;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAa5D;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAGhF;AA2BD,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/nx/index.ts"],"names":[],"mappings":"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"}
package/dist/nx/index.js CHANGED
@@ -1,15 +1,13 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { lstat, rm } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
- import { $ } from 'bun';
5
- import { parseNxProjectJsonText, parseStringArrayText, } from '../lib/json.js';
6
- import { decode, run } from '../lib/run.js';
4
+ // Nx package exports are CLI-oriented; this is the module the CLI uses for graph + daemon IPC.
5
+ import { createProjectGraphAsync } from 'nx/src/project-graph/project-graph.js';
6
+ import { workspaceRoot as primaryWorkspaceRoot } from 'nx/src/utils/workspace-root.js';
7
+ import { run, runResult } from '../lib/run.js';
7
8
  export function nxResetCommand() {
8
9
  return { command: 'nx', args: ['reset'] };
9
10
  }
10
- export function nxShowProjectCommand(project) {
11
- return { command: 'nx', args: ['show', 'project', project, '--json'] };
12
- }
13
11
  export function nxCacheDirectories(root) {
14
12
  return [join(root, '.nx/cache'), join(root, '.nx/workspace-data'), join(root, 'node_modules/.cache/nx')];
15
13
  }
@@ -20,9 +18,6 @@ export function targetNamesFromNxProjectJson(value) {
20
18
  export function projectRootFromNxProjectJson(value) {
21
19
  return typeof value?.root === 'string' ? value.root : undefined;
22
20
  }
23
- export function buildDependsOnFromNxProjectJson(value) {
24
- return targetDependenciesFromNxProjectJson(value).get('build');
25
- }
26
21
  export function targetDependenciesFromNxProjectJson(value) {
27
22
  const targets = value?.targets;
28
23
  const dependencies = new Map();
@@ -79,6 +74,19 @@ export function targetExecutorsFromNxProjectJson(value) {
79
74
  }
80
75
  return executors;
81
76
  }
77
+ export function targetOptionsFromNxProjectJson(value) {
78
+ const targets = value?.targets;
79
+ const options = new Map();
80
+ if (!targets) {
81
+ return options;
82
+ }
83
+ for (const [targetName, target] of Object.entries(targets)) {
84
+ if (target.options) {
85
+ options.set(targetName, target.options);
86
+ }
87
+ }
88
+ return options;
89
+ }
82
90
  export function targetOutputsFromNxProjectJson(value) {
83
91
  return targetStringArraysFromNxProjectJson(value, 'outputs');
84
92
  }
@@ -127,28 +135,6 @@ export function projectNamesWithTarget(projects, target) {
127
135
  .map((project) => project.project)
128
136
  .sort((a, b) => a.localeCompare(b));
129
137
  }
130
- export function projectNamesFromNxShowProjectsOutput(output) {
131
- const trimmed = output.trim();
132
- if (!trimmed) {
133
- return [];
134
- }
135
- if (trimmed.startsWith('[')) {
136
- try {
137
- const parsed = parseStringArrayText(trimmed);
138
- if (parsed) {
139
- return parsed.sort((a, b) => a.localeCompare(b));
140
- }
141
- }
142
- catch {
143
- // Fall through to legacy newline parsing for older Nx output shapes.
144
- }
145
- }
146
- return trimmed
147
- .split('\n')
148
- .map((line) => line.trim())
149
- .filter(Boolean)
150
- .sort((a, b) => a.localeCompare(b));
151
- }
152
138
  export async function listTargets(root) {
153
139
  const output = formatProjectTargetLines(await readProjectTargets(root));
154
140
  if (output) {
@@ -182,30 +168,105 @@ export async function cleanCache(root) {
182
168
  console.log(`Removed ${path}`);
183
169
  }
184
170
  }
171
+ /**
172
+ * Load the resolved project graph through Nx's API. The current workspace uses the
173
+ * daemon in-process; foreign roots use an isolated process because Nx snapshots its
174
+ * workspace cache paths when the module loads.
175
+ */
176
+ let graphLoadTail = Promise.resolve();
177
+ export function loadNxProjects(root) {
178
+ const result = graphLoadTail.then(() => loadNxProjectsNow(root));
179
+ graphLoadTail = result.then(() => undefined, () => undefined);
180
+ return result;
181
+ }
182
+ async function loadNxProjectsNow(root) {
183
+ if (root !== primaryWorkspaceRoot) {
184
+ return loadForeignNxProjects(root);
185
+ }
186
+ const graph = await createProjectGraphAsync({ exitOnError: false });
187
+ return nxProjectsFromNodes(graph.nodes);
188
+ }
189
+ const FOREIGN_GRAPH_SCRIPT = `
190
+ import('nx/src/project-graph/project-graph.js')
191
+ .then(async ({ createProjectGraphAsync }) => {
192
+ const graph = await createProjectGraphAsync({ exitOnError: false });
193
+ const projects = Object.fromEntries(
194
+ Object.entries(graph.nodes).map(([name, node]) => [
195
+ name,
196
+ { ...node.data, name: node.data?.name ?? name, targets: node.data?.targets ?? {} },
197
+ ]),
198
+ );
199
+ process.stdout.write(JSON.stringify(projects));
200
+ })
201
+ .catch((error) => {
202
+ console.error(error);
203
+ process.exitCode = 1;
204
+ });
205
+ `;
206
+ async function loadForeignNxProjects(root) {
207
+ const result = await runResult(process.execPath, ['--eval', FOREIGN_GRAPH_SCRIPT], root, {
208
+ NX_DAEMON: 'false',
209
+ NX_ISOLATE_PLUGINS: 'false',
210
+ NX_WORKSPACE_ROOT_PATH: root,
211
+ });
212
+ if (result.exitCode !== 0) {
213
+ throw new Error(`Failed to load Nx project graph for ${root}: ${result.stderr.trim()}`);
214
+ }
215
+ const parsed = JSON.parse(result.stdout);
216
+ if (!isNxProjects(parsed)) {
217
+ throw new Error(`Nx returned an invalid project graph for ${root}`);
218
+ }
219
+ return parsed;
220
+ }
221
+ function nxProjectsFromNodes(nodes) {
222
+ const projects = {};
223
+ for (const [name, node] of Object.entries(nodes)) {
224
+ projects[name] = {
225
+ ...node.data,
226
+ name: node.data.name ?? name,
227
+ targets: node.data.targets ?? {},
228
+ };
229
+ }
230
+ return projects;
231
+ }
232
+ function isNxProjects(value) {
233
+ if (!isRecord(value))
234
+ return false;
235
+ return Object.values(value).every((project) => isRecord(project) &&
236
+ (project.name === undefined || typeof project.name === 'string') &&
237
+ (project.root === undefined || typeof project.root === 'string') &&
238
+ (project.targets === undefined || (isRecord(project.targets) && Object.values(project.targets).every(isRecord))));
239
+ }
240
+ function isRecord(value) {
241
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
242
+ }
243
+ export function targetNamesFromProjects(projects) {
244
+ const names = new Set();
245
+ for (const project of Object.values(projects)) {
246
+ for (const targetName of targetNamesFromNxProjectJson(project)) {
247
+ names.add(targetName);
248
+ }
249
+ }
250
+ return [...names];
251
+ }
252
+ export function projectTargetsFromNxProjects(projects) {
253
+ return Object.entries(projects)
254
+ .sort(([left], [right]) => left.localeCompare(right))
255
+ .map(([project, metadata]) => {
256
+ const targetDependencies = targetDependenciesFromNxProjectJson(metadata);
257
+ return {
258
+ project,
259
+ root: projectRootFromNxProjectJson(metadata),
260
+ targets: targetNamesFromNxProjectJson(metadata),
261
+ buildDependsOn: targetDependencies.get('build'),
262
+ targetDependencies,
263
+ targetExecutors: targetExecutorsFromNxProjectJson(metadata),
264
+ targetOptions: targetOptionsFromNxProjectJson(metadata),
265
+ targetOutputs: targetOutputsFromNxProjectJson(metadata),
266
+ targetScripts: targetScriptsFromNxProjectJson(metadata),
267
+ };
268
+ });
269
+ }
185
270
  export async function readProjectTargets(root) {
186
- const projects = await readNxProjectNames(root);
187
- return Promise.all(projects.map((project) => readProjectTarget(root, project)));
188
- }
189
- async function readNxProjectNames(root) {
190
- const result = await $ `nx show projects`.cwd(root).quiet();
191
- return projectNamesFromNxShowProjectsOutput(decode(result.stdout));
192
- }
193
- async function readProjectTarget(root, project) {
194
- const command = nxShowProjectCommand(project);
195
- const result = await $ `${command.command} ${command.args}`.cwd(root).quiet();
196
- const parsed = parseNxProjectJsonText(decode(result.stdout));
197
- if (!parsed) {
198
- throw new Error(`Unable to inspect Nx project ${project}.`);
199
- }
200
- const targetDependencies = targetDependenciesFromNxProjectJson(parsed);
201
- return {
202
- project,
203
- root: projectRootFromNxProjectJson(parsed),
204
- targets: targetNamesFromNxProjectJson(parsed),
205
- buildDependsOn: targetDependencies.get('build'),
206
- targetDependencies,
207
- targetExecutors: targetExecutorsFromNxProjectJson(parsed),
208
- targetOutputs: targetOutputsFromNxProjectJson(parsed),
209
- targetScripts: targetScriptsFromNxProjectJson(parsed),
210
- };
271
+ return projectTargetsFromNxProjects(await loadNxProjects(root));
211
272
  }
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env bash
2
+ # Wrapper script to run devenv from anywhere in the monorepo
3
+ # This allows us to keep all devenv files in tooling/direnv
4
+
5
+ # Save the current working directory
6
+ ORIGINAL_PWD="$(pwd)"
7
+ SELF_PATH="$(realpath "$0")"
8
+
9
+ # Find the root of the git repository
10
+ TOOLING_DIR="$(dirname "$SELF_PATH")"
11
+ DEVENV_DIR="$TOOLING_DIR/direnv"
12
+ if [ ! -d "$DEVENV_DIR" ]; then
13
+ echo "Error: Could not find devenv directory at $DEVENV_DIR" >&2
14
+ exit 1
15
+ fi
16
+
17
+ # Find the actual Nix-built devenv binary, skipping every wrapper script.
18
+ # WHY: PATH routinely holds more than one of these wrappers at once โ€” this
19
+ # repo's tooling/devenv, sibling repos' wrappers, and direnv-stacked parent
20
+ # environments. A plain "not equal to SELF_PATH" check happily selects a
21
+ # *different* wrapper and exec's it, so `devenv` bounces into the wrong repo
22
+ # (or self-execs forever on cheap checks like `devenv version`). The real devenv
23
+ # is always the Nix-managed executable under /nix/store, so match that and ignore
24
+ # every wrapper regardless of PATH ordering. (Matching the store path is also more
25
+ # robust than a "is it a binary" test: Nix sometimes ships tools as makeWrapper
26
+ # shell scripts, which are still the correct entry point.)
27
+ DEVENV_BIN=""
28
+ while IFS= read -r candidate; do
29
+ candidate_path="$(realpath "$candidate")"
30
+ case "$candidate_path" in
31
+ /nix/store/*)
32
+ if [ -f "$candidate_path" ] && [ -x "$candidate_path" ]; then
33
+ DEVENV_BIN="$candidate_path"
34
+ break
35
+ fi
36
+ ;;
37
+ esac
38
+ done < <(which -a devenv 2>/dev/null)
39
+
40
+ if [ -z "$DEVENV_BIN" ]; then
41
+ echo "Error: Could not find the Nix-built devenv binary (under /nix/store) in PATH" >&2
42
+ exit 1
43
+ fi
44
+
45
+ # If we're in a project, just run devenv
46
+ if [ -f devenv.nix ]; then
47
+ exec "$DEVENV_BIN" "$@"
48
+ fi
49
+
50
+ # Change to the tooling/direnv directory where devenv files are located
51
+ cd "$DEVENV_DIR" || exit 1
52
+
53
+ # Export the original directory so enterShell can use it
54
+ export DEVENV_SHELL_PWD="$ORIGINAL_PWD"
55
+
56
+ # Run the actual devenv command with all arguments passed through.
57
+ # For updates, refresh nvfetcher-managed overlay sources first so packages like
58
+ # jmunch-mcp and jcodemunch-mcp are current before devenv.lock is updated.
59
+ if [ "${1:-}" = "update" ]; then
60
+ if [ -f "$DEVENV_DIR/nixpkgs-overlay/nvfetcher.toml" ]; then
61
+ echo "Running nvfetcher for nixpkgs-overlay/..."
62
+ (cd "$DEVENV_DIR/nixpkgs-overlay" && echo "
63
+ [keys]
64
+ github = \"$(gh auth token)\"
65
+ " > .gh-auth && (nvfetcher -k .gh-auth -o _sources || true) && rm .gh-auth)
66
+ fi
67
+
68
+ exec "$DEVENV_BIN" "$@"
69
+ fi
70
+
71
+ exec "$DEVENV_BIN" "$@"
@@ -1,8 +1,13 @@
1
- #!/bin/bash
1
+ #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
4
  NIX_STORE_NAR="${NIX_STORE_NAR:-/tmp/nix-store.nar}"
5
5
  nix_store_cmd="/nix/var/nix/profiles/default/bin/nix-store"
6
+ DEVENV_FLAKE="${DEVENV_FLAKE:-github:cachix/devenv}"
7
+ # Host CI cache (GARM bind-mount). Prefer env from runner profile.
8
+ # Path comes from the runner (GARM profile). Ephemeral runners leave unset;
9
+ # devenv enterShell defaults to $PWD/.cache/ttsc.
10
+ TTSC_CACHE_DIR="${TTSC_CACHE_DIR:-}"
6
11
  # Resolve from this script's location, not the caller's cwd. GitHub Actions
7
12
  # runs this from tooling/direnv today, but direct cwd-changing helpers are easy
8
13
  # to misuse and break on repeated calls.
@@ -36,12 +41,21 @@ restore_nix_store() {
36
41
  }
37
42
 
38
43
  install_devenv() {
39
- sudo mkdir -p /nix/var/nix/gcroots/ci
40
- if ! command -v devenv >/dev/null 2>&1; then
41
- nix profile add --accept-flake-config nixpkgs#devenv
44
+ # Shared host /nix/store is the package cache. Image may already provide
45
+ # devenv from github:cachix/devenv; otherwise profile-add the same flake
46
+ # (links store paths; re-fetch only when missing).
47
+ if command -v devenv >/dev/null 2>&1; then
48
+ echo "using existing devenv: $(command -v devenv) ($(devenv version 2>/dev/null || true))"
49
+ else
50
+ echo "nix profile add ${DEVENV_FLAKE}"
51
+ nix profile add --accept-flake-config "$DEVENV_FLAKE"
52
+ fi
53
+ if [ -d "$HOME/.nix-profile/bin" ]; then
54
+ echo "$HOME/.nix-profile/bin" >> "${GITHUB_PATH:-/dev/null}"
55
+ fi
56
+ if [ -n "$TTSC_CACHE_DIR" ]; then
57
+ mkdir -p "$TTSC_CACHE_DIR"
42
58
  fi
43
- echo "$HOME/.nix-profile/bin" >> "$GITHUB_PATH"
44
- sudo ln -sf "$HOME/.nix-profile" /nix/var/nix/gcroots/ci/profile
45
59
  }
46
60
 
47
61
  build_devenv_shell() {
@@ -50,7 +64,10 @@ build_devenv_shell() {
50
64
  # independent workflow step shells that run after this composite action.
51
65
  if [ -n "${GITHUB_ENV:-}" ]; then
52
66
  echo "TTSC_TSGO_BINARY=$repo_root/node_modules/@typescript/native/bin/tsc" >> "$GITHUB_ENV"
53
- echo "TTSC_CACHE_DIR=$repo_root/.cache/ttsc" >> "$GITHUB_ENV"
67
+ # Only re-export when the runner (or a prior step) already set the path.
68
+ if [ -n "$TTSC_CACHE_DIR" ]; then
69
+ echo "TTSC_CACHE_DIR=$TTSC_CACHE_DIR" >> "$GITHUB_ENV"
70
+ fi
54
71
  fi
55
72
  # Add repo-local tools only after the shell exists; cleanup steps use an
56
73
  # explicit PATH because failures before this point must still refresh caches.
@@ -1,5 +1,7 @@
1
1
  name: Cache Nix profiles, devenv and direnv
2
- description: Restore cached Nix/devenv state for SmoothBricks GitHub Actions jobs.
2
+ description: >
3
+ Two independent segments: large nix (NAR/profiles, ephemeral runners only) and small devenv (.devenv/.direnv โ€” eval
4
+ SQLite, shell scripts, profile; all runners).
3
5
 
4
6
  inputs:
5
7
  segment:
@@ -19,12 +21,11 @@ outputs:
19
21
  runs:
20
22
  using: composite
21
23
  steps:
22
- # The large cache is the Nix profile plus exported store NAR. Its key tracks
23
- # the expensive shell closure inputs only; tiny direnv/setup script changes
24
- # should not invalidate a 1GB+ store cache.
25
- - name: Restore Nix profiles, devenv and direnv
24
+ # Large segment: Nix profile + exported store NAR. Ephemeral runners only.
25
+ # Key = shell closure inputs; direnv/setup script edits must not bust a multi-GB NAR.
26
+ - name: Restore Nix profiles and store NAR
26
27
  id: restore-nix
27
- if: ${{ inputs.segment == 'nix' && inputs.mode == 'restore' }}
28
+ if: inputs.segment == 'nix' && inputs.mode == 'restore'
28
29
  uses: actions/cache/restore@v5.0.5
29
30
  with:
30
31
  path: |
@@ -39,7 +40,7 @@ runs:
39
40
  ${{ runner.os }}-${{ runner.arch }}-nix-v3-
40
41
 
41
42
  - name: Save Nix profiles and store NAR
42
- if: ${{ inputs.segment == 'nix' && inputs.mode == 'save' }}
43
+ if: inputs.segment == 'nix' && inputs.mode == 'save'
43
44
  uses: actions/cache/save@v5.0.5
44
45
  with:
45
46
  path: |
@@ -51,11 +52,12 @@ runs:
51
52
  ${{ runner.os }}-${{ runner.arch }}-nix-v3-${{ hashFiles('tooling/direnv/devenv.yaml',
52
53
  'tooling/direnv/devenv.nix', 'tooling/direnv/devenv.lock') }}
53
54
 
54
- # .devenv/.direnv are small, but they contain absolute /nix/store pointers.
55
- # Workflows restore this segment only after an exact Nix cache hit.
55
+ # Small segment: devenv eval cache (nix-eval-cache.db), shell-*.sh, profile, .direnv.
56
+ # Own key. Setup restores on every runner; not gated on nix NAR hit. Devenv drops a
57
+ # shell cache row when drv/out are missing (GC) and re-evals/realizes.
56
58
  - name: Restore devenv and direnv state
57
59
  id: restore-devenv
58
- if: ${{ inputs.segment == 'devenv' && inputs.mode == 'restore' }}
60
+ if: inputs.segment == 'devenv' && inputs.mode == 'restore'
59
61
  uses: actions/cache/restore@v5.0.5
60
62
  with:
61
63
  path: |
@@ -69,7 +71,7 @@ runs:
69
71
  ${{ runner.os }}-${{ runner.arch }}-devenv-v3-
70
72
 
71
73
  - name: Save devenv and direnv state
72
- if: ${{ inputs.segment == 'devenv' && inputs.mode == 'save' }}
74
+ if: inputs.segment == 'devenv' && inputs.mode == 'save'
73
75
  uses: actions/cache/save@v5.0.5
74
76
  with:
75
77
  path: |
@@ -1,5 +1,7 @@
1
1
  name: Save Nix/devenv caches
2
- description: Cleanup the Nix store and explicitly save Nix/devenv cache segments.
2
+ description: >
3
+ Save cache segments after the job. Nix store NAR stays ephemeral-only (host uses shared /nix/store). Devenv/direnv
4
+ eval-cache segment saves on every runner when setup missed and build-shell produced nix-eval-cache.db.
3
5
 
4
6
  inputs:
5
7
  nix-cache-hit:
@@ -14,8 +16,19 @@ inputs:
14
16
  runs:
15
17
  using: composite
16
18
  steps:
19
+ - name: ๐Ÿ”Ž Runner kind
20
+ id: runner-kind
21
+ shell: bash
22
+ run: |
23
+ if [ "${NIX_REMOTE:-}" = daemon ] && [ -d /var/cache/ci ]; then
24
+ echo "host=true" >> "$GITHUB_OUTPUT"
25
+ else
26
+ echo "host=false" >> "$GITHUB_OUTPUT"
27
+ fi
28
+
17
29
  - name: ๐Ÿงน Prepare Nix/devenv cache save
18
30
  id: cleanup-cache
31
+ if: steps.runner-kind.outputs.host != 'true'
19
32
  shell: bash
20
33
  working-directory: tooling/direnv
21
34
  env:
@@ -33,14 +46,32 @@ runs:
33
46
  smoo github-ci cleanup-cache
34
47
 
35
48
  - name: ๐ŸงŠ Save Nix profiles and store NAR
36
- if: ${{ steps.cleanup-cache.outputs.cache-ready == 'true' && inputs.nix-cache-hit != 'true' }}
49
+ if:
50
+ ${{ steps.runner-kind.outputs.host != 'true' && steps.cleanup-cache.outputs.cache-ready == 'true' &&
51
+ inputs.nix-cache-hit != 'true' }}
37
52
  uses: ./.github/actions/cache-nix-devenv
38
53
  with:
39
54
  segment: nix
40
55
  mode: save
41
56
 
57
+ # Devenv segment is independent of nix NAR readiness and host vs ephemeral.
58
+ # Skip when setup never finished (no eval DB) so if: always() cannot poison
59
+ # the next restore with an empty/partial key.
60
+ - name: ๐Ÿ”Ž Devenv save ready
61
+ id: devenv-save
62
+ if: inputs.devenv-cache-hit != 'true'
63
+ shell: bash
64
+ run: |
65
+ db="${GITHUB_WORKSPACE}/tooling/direnv/.devenv/nix-eval-cache.db"
66
+ if [[ -f "$db" ]]; then
67
+ echo "ready=true" >> "$GITHUB_OUTPUT"
68
+ else
69
+ echo "ready=false" >> "$GITHUB_OUTPUT"
70
+ echo "skip devenv save: no nix-eval-cache.db"
71
+ fi
72
+
42
73
  - name: ๐ŸงŠ Save devenv and direnv
43
- if: ${{ steps.cleanup-cache.outputs.cache-ready == 'true' && inputs.devenv-cache-hit != 'true' }}
74
+ if: ${{ inputs.devenv-cache-hit != 'true' && steps.devenv-save.outputs.ready == 'true' }}
44
75
  uses: ./.github/actions/cache-nix-devenv
45
76
  with:
46
77
  segment: devenv