@smoothbricks/cli 0.10.4 → 0.10.6

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 (56) hide show
  1. package/dist/cli.js +5 -5
  2. package/dist/github-ci/index.d.ts.map +1 -1
  3. package/dist/github-ci/index.js +5 -3
  4. package/dist/lib/json.d.ts +5 -1
  5. package/dist/lib/json.d.ts.map +1 -1
  6. package/dist/lib/json.js +18 -16
  7. package/dist/monorepo/ci-workflow.d.ts +2 -0
  8. package/dist/monorepo/ci-workflow.d.ts.map +1 -1
  9. package/dist/monorepo/ci-workflow.js +10 -4
  10. package/dist/monorepo/github-runs-on.d.ts +14 -0
  11. package/dist/monorepo/github-runs-on.d.ts.map +1 -0
  12. package/dist/monorepo/github-runs-on.js +43 -0
  13. package/dist/monorepo/index.d.ts +3 -3
  14. package/dist/monorepo/index.d.ts.map +1 -1
  15. package/dist/monorepo/index.js +20 -8
  16. package/dist/monorepo/lockfile.d.ts +2 -1
  17. package/dist/monorepo/lockfile.d.ts.map +1 -1
  18. package/dist/monorepo/lockfile.js +1 -1
  19. package/dist/monorepo/managed-files.d.ts +30 -3
  20. package/dist/monorepo/managed-files.d.ts.map +1 -1
  21. package/dist/monorepo/managed-files.js +83 -63
  22. package/dist/monorepo/packs/index.js +2 -2
  23. package/dist/monorepo/publish-workflow.d.ts +3 -0
  24. package/dist/monorepo/publish-workflow.d.ts.map +1 -1
  25. package/dist/monorepo/publish-workflow.js +17 -14
  26. package/dist/monorepo/tool-validation.d.ts +2 -2
  27. package/dist/monorepo/tool-validation.d.ts.map +1 -1
  28. package/dist/monorepo/tool-validation.js +110 -16
  29. package/dist/release/index.d.ts.map +1 -1
  30. package/dist/release/index.js +6 -0
  31. package/managed/raw/envrc +5 -3
  32. package/managed/raw/patches/ttsc@0.19.3.patch +67 -0
  33. package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +24 -7
  34. package/managed/raw/tooling/direnv/setup-environment.ts +23 -3
  35. package/managed/templates/github/actions/cache-nix-devenv/action.yml +13 -11
  36. package/managed/templates/github/actions/save-nix-devenv/action.yml +34 -3
  37. package/managed/templates/github/actions/setup-devenv/action.yml +44 -15
  38. package/managed/templates/github/workflows/managed-files.yml +64 -12
  39. package/package.json +2 -2
  40. package/src/cli.ts +6 -6
  41. package/src/github-ci/index.test.ts +3 -3
  42. package/src/github-ci/index.ts +6 -3
  43. package/src/lib/json.ts +5 -1
  44. package/src/monorepo/__tests__/ci-workflow.test.ts +37 -1
  45. package/src/monorepo/__tests__/publish-workflow.test.ts +28 -1
  46. package/src/monorepo/ci-workflow.ts +14 -4
  47. package/src/monorepo/github-runs-on.ts +45 -0
  48. package/src/monorepo/index.ts +20 -8
  49. package/src/monorepo/lockfile.ts +3 -2
  50. package/src/monorepo/managed-files.test.ts +49 -0
  51. package/src/monorepo/managed-files.ts +105 -64
  52. package/src/monorepo/packs/index.ts +2 -2
  53. package/src/monorepo/publish-workflow.ts +23 -15
  54. package/src/monorepo/tool-validation.test.ts +38 -16
  55. package/src/monorepo/tool-validation.ts +114 -16
  56. package/src/release/index.ts +6 -0
@@ -5,13 +5,19 @@ import { cliPackageVersion, isSmoothBricksCodebasePackageName } from '../lib/cli
5
5
  import { ensureDependencyMap, readJsonObject, setPackageStringField, setStringProperty, writeJsonObject, } from '../lib/json.js';
6
6
  import { readPackageJsonObject } from '../lib/workspace.js';
7
7
  const isRegistryPackument = (() => {
8
- const _io0 = input => "object" === typeof input.versions && null !== input.versions && false === Array.isArray(input.versions) && _io1(input.versions);
8
+ const _io0 = input => "object" === typeof input.versions && null !== input.versions && false === Array.isArray(input.versions) && _io1(input.versions) && (undefined === input["dist-tags"] || "object" === typeof input["dist-tags"] && null !== input["dist-tags"] && false === Array.isArray(input["dist-tags"]) && _io2(input["dist-tags"]));
9
9
  const _io1 = input => Object.keys(input).every(key => {
10
10
  const value = input[key];
11
11
  if (undefined === value)
12
12
  return true;
13
13
  return true;
14
14
  });
15
+ const _io2 = input => Object.keys(input).every(key => {
16
+ const value = input[key];
17
+ if (undefined === value)
18
+ return true;
19
+ return "string" === typeof value;
20
+ });
15
21
  return input => "object" === typeof input && null !== input && _io0(input);
16
22
  })();
17
23
  const rootDevDependencies = [
@@ -34,10 +40,14 @@ const rootDevDependencies = [
34
40
  },
35
41
  { name: 'nx', fallbackVersion: '23.1.0', minimumVersion: '23.1.0' },
36
42
  { name: 'prettier', fallbackVersion: '^3.6.1', minimumVersion: '3.6.0', prefix: '^' },
37
- { name: 'ttsc', fallbackVersion: '^0.18.4', minimumVersion: '0.18.4', prefix: '^' },
43
+ // Exact pin: Bun patchedDependencies keys are version-locked (see patches/ttsc@0.19.3.patch).
44
+ { name: 'ttsc', fallbackVersion: '0.19.3' },
38
45
  // Nx and typescript-eslint still load the TypeScript JS API (6.x).
39
46
  // Compilation is exclusively delegated to ttsc by the Nx plugin targets.
40
- { name: 'typescript', fallbackVersion: '^5.9.3', minimumVersion: '5.9.0', prefix: '^' },
47
+ { name: 'typescript', fallbackVersion: '^6.0.3', minimumVersion: '6.0.0', prefix: '^' },
48
+ // TS7 native compiler for ttsc (TTSC_TSGO_BINARY). Not the unscoped API package.
49
+ // npm alias form required — Bun cannot install @typescript/typescript6 cleanly (bun#33834).
50
+ { name: '@typescript/native', fallbackVersion: 'npm:typescript@^7.0.2' },
41
51
  ];
42
52
  const cliPackageName = '@smoothbricks/cli';
43
53
  const requiredDevenvPackages = ['bun', 'git', 'git-format-staged', 'jq', 'alejandra', 'coreutils', 'gnutar'];
@@ -46,15 +56,18 @@ const requiredDevenvPackages = ['bun', 'git', 'git-format-staged', 'jq', 'alejan
46
56
  // with the runtime is validated against the live PATH (validateRootRuntimeVersions),
47
57
  // never against a version template here.
48
58
  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';
49
61
  export async function applyToolConfigDefaults(root) {
50
- const context = readToolContext(root);
62
+ const context = await readToolContext(root);
51
63
  await applyRootPackageToolDefaults(root, context);
52
64
  applyToolingPackageDefaults(root, context.policy);
53
65
  applyDevenvPackageDefaults(root);
54
66
  }
55
- export function validateToolConfig(root) {
56
- const context = readToolContext(root);
67
+ export async function validateToolConfig(root) {
68
+ const context = await readToolContext(root);
57
69
  return (validateRootDevDependencies(context.policy, context.rootPackage) +
70
+ validateTtscPatchedDependency(context.rootPackage) +
58
71
  validateToolingPackage(root, context.policy) +
59
72
  validateToolingWorkspace(context.rootPackage) +
60
73
  validateDevenvPackages(root));
@@ -76,6 +89,7 @@ export async function applyRootDevDependencyDefaults(root, context) {
76
89
  if (delete devDependencies['@smoothbricks/cli']) {
77
90
  changed = true;
78
91
  }
92
+ changed = ensureTtscPatchedDependency(pkg) || changed;
79
93
  if (changed) {
80
94
  writeJsonObject(join(root, 'package.json'), pkg);
81
95
  console.log('updated package.json workspace tool dependencies');
@@ -91,6 +105,7 @@ async function applyRootPackageToolDefaults(root, context) {
91
105
  }
92
106
  let dependencyChanged = false;
93
107
  let workspaceChanged = false;
108
+ let patchChanged = false;
94
109
  const devDependencies = ensureDependencyMap(pkg, 'devDependencies');
95
110
  for (const dependency of rootDevDependencies) {
96
111
  const current = devDependencies[dependency.name];
@@ -103,7 +118,8 @@ async function applyRootPackageToolDefaults(root, context) {
103
118
  dependencyChanged = true;
104
119
  }
105
120
  workspaceChanged = addWorkspacePattern(pkg, 'tooling');
106
- if (dependencyChanged || workspaceChanged) {
121
+ patchChanged = ensureTtscPatchedDependency(pkg);
122
+ if (dependencyChanged || workspaceChanged || patchChanged) {
107
123
  writeJsonObject(join(root, 'package.json'), pkg);
108
124
  }
109
125
  console.log(dependencyChanged
@@ -112,6 +128,9 @@ async function applyRootPackageToolDefaults(root, context) {
112
128
  console.log(workspaceChanged
113
129
  ? 'updated package.json tooling workspace'
114
130
  : 'unchanged package.json tooling workspace');
131
+ console.log(patchChanged
132
+ ? 'updated package.json patchedDependencies for ttsc'
133
+ : 'unchanged package.json patchedDependencies for ttsc');
115
134
  }
116
135
  export function applyToolingPackageDefaults(root, policy) {
117
136
  const path = join(root, 'tooling', 'package.json');
@@ -200,6 +219,22 @@ export function validateRootDevDependencies(policy, rootPackage) {
200
219
  }
201
220
  return failures;
202
221
  }
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;
229
+ }
230
+ const actual = rootPackage.patchedDependencies?.[TTSC_PATCHED_DEPENDENCY_KEY];
231
+ if (actual === TTSC_PATCH_PATH) {
232
+ return 0;
233
+ }
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)'));
236
+ return 1;
237
+ }
203
238
  export function validateToolingPackage(root, policy) {
204
239
  const path = join(root, 'tooling', 'package.json');
205
240
  const pkg = readJsonObject(path);
@@ -287,6 +322,10 @@ function satisfiesDependencyPolicy(policy, version, dependency) {
287
322
  if (workspaceDependencyExpected(policy, dependency)) {
288
323
  return version === 'workspace:*';
289
324
  }
325
+ // Dual-package native compiler: must stay an npm:typescript@7 alias, never unscoped "typescript".
326
+ if (dependency.name === '@typescript/native') {
327
+ return isTypeScriptNativeAlias(version);
328
+ }
290
329
  if (dependency.minimumVersion === undefined) {
291
330
  return version === dependency.fallbackVersion;
292
331
  }
@@ -297,34 +336,38 @@ function satisfiesDependencyPolicy(policy, version, dependency) {
297
336
  }
298
337
  return compareVersions(parsed, minimum) >= 0;
299
338
  }
339
+ function isTypeScriptNativeAlias(version) {
340
+ // Accept npm:typescript@7, npm:typescript@^7.0.2, npm:typescript@~7.0.2, etc.
341
+ return /^npm:typescript@(?:\^|~)?7(?:\.|$)/.test(version.trim());
342
+ }
300
343
  function formatExpectedDependency(policy, dependency) {
301
344
  if (workspaceDependencyExpected(policy, dependency)) {
302
345
  return 'workspace:*';
303
346
  }
347
+ if (dependency.name === '@typescript/native') {
348
+ return 'npm:typescript@^7 (TypeScript 7 native compiler alias for ttsc)';
349
+ }
304
350
  return dependency.minimumVersion ? `>= ${dependency.minimumVersion}` : dependency.fallbackVersion;
305
351
  }
306
- export function readToolContext(root) {
352
+ export async function readToolContext(root) {
307
353
  const rootPackage = readPackageJsonObject(join(root, 'package.json'));
308
354
  const toolingPackage = readJsonObject(join(root, 'tooling', 'package.json'));
309
355
  const configuredCliRange = toolingPackage?.dependencies?.[cliPackageName];
310
356
  return {
311
357
  rootPackage,
312
- policy: toolPolicy(rootPackage, typeof configuredCliRange === 'string' ? configuredCliRange : null),
358
+ policy: await toolPolicy(rootPackage, typeof configuredCliRange === 'string' ? configuredCliRange : null),
313
359
  };
314
360
  }
315
- function toolPolicy(rootPackage, configuredCliRange) {
361
+ async function toolPolicy(rootPackage, configuredCliRange) {
316
362
  const name = rootPackage?.name ?? null;
317
363
  const isCodebase = isSmoothBricksCodebasePackageName(name ?? undefined);
318
364
  const toolingName = toolingPackageName(name);
319
- // A locally linked prerelease can be newer than every published CLI. Keep an existing
320
- // installable range instead of rewriting the consumer manifest to an unpublished version.
321
- const publishedCliRange = cliPackageVersion.includes('-') && configuredCliRange && parseVersion(configuredCliRange)
322
- ? configuredCliRange
323
- : `^${cliPackageVersion}`;
324
365
  return {
325
366
  isSmoothBricksCodebase: isCodebase,
326
367
  toolingPackageName: toolingName,
327
- cliDependencyRange: isCodebase ? 'workspace:*' : publishedCliRange,
368
+ // Consumers pin the latest *published* CLI. Running a linked prerelease must not
369
+ // freeze tooling/package.json on an older range or rewrite it to an unpublished -next.
370
+ cliDependencyRange: isCodebase ? 'workspace:*' : await resolvePublishedCliDependencyRange(configuredCliRange),
328
371
  };
329
372
  }
330
373
  function toolingPackageName(rootName) {
@@ -345,6 +388,57 @@ async function resolveDependencyVersion(policy, dependency) {
345
388
  const latest = await fetchLatestPatchVersion(dependency.name, dependency.minimumVersion);
346
389
  return `${dependency.prefix ?? ''}${latest ?? stripRangePrefix(dependency.fallbackVersion)}`;
347
390
  }
391
+ /**
392
+ * Consumer monorepos pin `@smoothbricks/cli` to the latest stable release.
393
+ * Prefer npm `dist-tags.latest`. Never write a running `-next` package version into
394
+ * consumer manifests; never leave an older pin just because the running CLI is prerelease.
395
+ */
396
+ async function resolvePublishedCliDependencyRange(configuredCliRange) {
397
+ const latest = await fetchLatestStableVersion(cliPackageName);
398
+ if (latest) {
399
+ return `^${latest}`;
400
+ }
401
+ // Registry unavailable: if the running CLI is a stable publish, use it.
402
+ if (!cliPackageVersion.includes('-')) {
403
+ return `^${cliPackageVersion}`;
404
+ }
405
+ // Linked prerelease + offline down: keep an existing installable stable pin.
406
+ if (configuredCliRange && parseVersion(configuredCliRange)) {
407
+ return configuredCliRange;
408
+ }
409
+ // Last resort: caret of the prerelease base (0.10.5-next.0 → ^0.10.5) so manifests stay valid.
410
+ const base = parseVersion(cliPackageVersion);
411
+ return base ? `^${base.major}.${base.minor}.${base.patch}` : `^${cliPackageVersion}`;
412
+ }
413
+ async function fetchLatestStableVersion(packageName) {
414
+ const url = `https://registry.npmjs.org/${encodeURIComponent(packageName).replace('%40', '@')}`;
415
+ const response = await fetch(url, { headers: { accept: 'application/vnd.npm.install-v1+json' } });
416
+ if (!response.ok) {
417
+ return null;
418
+ }
419
+ const body = await response.json();
420
+ if (!isRegistryPackument(body)) {
421
+ return null;
422
+ }
423
+ const tagged = body['dist-tags']?.latest;
424
+ if (typeof tagged === 'string' && parseVersion(tagged) && !tagged.includes('-')) {
425
+ return tagged;
426
+ }
427
+ let latest = null;
428
+ for (const raw of Object.keys(body.versions)) {
429
+ if (raw.includes('-')) {
430
+ continue;
431
+ }
432
+ const version = parseVersion(raw);
433
+ if (!version) {
434
+ continue;
435
+ }
436
+ if (!latest || compareVersions(version, latest) > 0) {
437
+ latest = version;
438
+ }
439
+ }
440
+ return latest ? `${latest.major}.${latest.minor}.${latest.patch}` : null;
441
+ }
348
442
  async function fetchLatestPatchVersion(packageName, minimumVersion) {
349
443
  const minorRange = sameMajorMinorRange(minimumVersion);
350
444
  const url = `https://registry.npmjs.org/${encodeURIComponent(packageName).replace('%40', '@')}`;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/release/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/release/index.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,7 @@ import { withDevenvEnv } from '../lib/devenv.js';
12
12
  import { parseNxJsonText, parseNxProjectJsonText, parsePackageJsonText, } from '../lib/json.js';
13
13
  import { decode, run, runInteractiveStatus, runResult } from '../lib/run.js';
14
14
  import { listReleasePackages, readPackageJson, repositoryInfo } from '../lib/workspace.js';
15
+ import { syncBunLockfileVersions } from '../monorepo/lockfile.js';
15
16
  import { readPackedPackageJson, validatePackedWorkspaceDependencies } from '../monorepo/packed-manifest.js';
16
17
  import { bootstrapNpmPackages, NPM_BOOTSTRAP_DIST_TAG, NPM_BOOTSTRAP_VERSION, } from './bootstrap-npm-packages.js';
17
18
  import { autoReleaseCandidatePackages } from './candidates.js';
@@ -301,6 +302,10 @@ async function listUnpublishedPackages(root, packages) {
301
302
  async function publishPackedPackage(root, pkg, tag, dryRun) {
302
303
  const tempDir = await mkdtemp(join(tmpdir(), 'smoo-publish-'));
303
304
  const tarball = join(tempDir, `${safeTarballPrefix(pkg.name)}-${pkg.version}.tgz`);
305
+ // bun pm pack resolves workspace:* from bun.lock. Day-to-day lock keeps -next
306
+ // (install/CI). For publish, temporarily rewrite unpublished -next entries to
307
+ // the last stable tag so the tarball embeds installable versions, then restore.
308
+ syncBunLockfileVersions(root, { mode: 'publish', log: true });
304
309
  try {
305
310
  console.log(`${pkg.name}@${pkg.version}: packing with bun pm pack`);
306
311
  await run('bun', ['pm', 'pack', '--filename', tarball, '--ignore-scripts', '--quiet'], join(root, pkg.path));
@@ -329,6 +334,7 @@ async function publishPackedPackage(root, pkg, tag, dryRun) {
329
334
  });
330
335
  }
331
336
  finally {
337
+ syncBunLockfileVersions(root, { mode: 'install', log: true });
332
338
  await rm(tempDir, { recursive: true, force: true });
333
339
  }
334
340
  }
package/managed/raw/envrc CHANGED
@@ -11,8 +11,10 @@ esac
11
11
 
12
12
  watch_file tooling/direnv/envrc.sh
13
13
 
14
+ # Load local extensions and overrides from the project root (the gitignored
15
+ # .envrc-local); capture it before we cd into tooling/direnv, since envrc.sh
16
+ # leaves us there and a bare relative path would resolve under tooling/direnv.
17
+ local_override="$PWD/.envrc-local"
14
18
  cd tooling/direnv
15
19
  . envrc.sh
16
-
17
- # Load local extensions and overrides
18
- source_env_if_exists .envrc-local
20
+ source_env_if_exists "$local_override"
@@ -0,0 +1,67 @@
1
+ --- a/driver/emit_plugin.go
2
+ +++ b/driver/emit_plugin.go
3
+ @@ -163,6 +163,32 @@
4
+ }
5
+ host := &pluginEmitHost{program: p.TSProgram, emitResolver: p.Checker.GetEmitResolver()}
6
+ options := p.TSProgram.Options()
7
+ + // Declaration emit must observe the original program before plugin transforms
8
+ + // can mutate source-file nodes while assembling JavaScript output.
9
+ + if options.GetEmitDeclarations() {
10
+ + var wfMu sync.Mutex
11
+ + result := p.TSProgram.Emit(context.Background(), shimcompiler.EmitOptions{
12
+ + EmitOnly: shimcompiler.EmitOnlyDts,
13
+ + WriteFile: func(fileName string, text string, data *shimcompiler.WriteFileData) error {
14
+ + wfMu.Lock()
15
+ + defer wfMu.Unlock()
16
+ + if p.outputEscapesOutDir(fileName) {
17
+ + if data != nil {
18
+ + data.SkippedDtsWrite = true
19
+ + }
20
+ + return nil
21
+ + }
22
+ + if writeFile != nil {
23
+ + return writeFile(fileName, text, data)
24
+ + }
25
+ + return DefaultWriteFile(fileName, text)
26
+ + },
27
+ + })
28
+ + if result != nil && len(result.Diagnostics) != 0 {
29
+ + return convertDiagnostics(result.Diagnostics), nil
30
+ + }
31
+ + }
32
+ +
33
+ for _, sf := range shimcompiler.GetSourceFilesToEmit(host, nil, false) {
34
+ paths := shimcompiler.GetOutputPathsFor(sf, options, host, false)
35
+ if paths.JsFilePath() != "" && !p.outputEscapesOutDir(paths.JsFilePath()) {
36
+ @@ -212,31 +238,6 @@
37
+ }
38
+ }
39
+ }
40
+ - if !options.GetEmitDeclarations() {
41
+ - return nil, nil
42
+ - }
43
+ -
44
+ - var wfMu sync.Mutex
45
+ - result := p.TSProgram.Emit(context.Background(), shimcompiler.EmitOptions{
46
+ - EmitOnly: shimcompiler.EmitOnlyDts,
47
+ - WriteFile: func(fileName string, text string, data *shimcompiler.WriteFileData) error {
48
+ - wfMu.Lock()
49
+ - defer wfMu.Unlock()
50
+ - if p.outputEscapesOutDir(fileName) {
51
+ - if data != nil {
52
+ - data.SkippedDtsWrite = true
53
+ - }
54
+ - return nil
55
+ - }
56
+ - if writeFile != nil {
57
+ - return writeFile(fileName, text, data)
58
+ - }
59
+ - return DefaultWriteFile(fileName, text)
60
+ - },
61
+ - })
62
+ - if result != nil && len(result.Diagnostics) != 0 {
63
+ - return convertDiagnostics(result.Diagnostics), nil
64
+ - }
65
+ return nil, nil
66
+ }
67
+
@@ -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,5 @@
1
1
  #!/usr/bin/env bun
2
- import { existsSync, mkdirSync, readdirSync, readlinkSync, rmSync, symlinkSync } from 'node:fs';
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, readlinkSync, rmSync, symlinkSync } from 'node:fs';
3
3
  import { mkdir, rmdir, stat } from 'node:fs/promises';
4
4
  import { createRequire } from 'node:module';
5
5
  import path from 'node:path';
@@ -98,8 +98,28 @@ function ensureTypeScriptApiPackage(root: string): void {
98
98
  assertTypescriptApiAt(path.join(root, 'node_modules', '.bun', 'node_modules', 'typescript'), apiPackageRoot);
99
99
 
100
100
  const nativeBin = path.join(root, 'node_modules', '@typescript', 'native', 'bin', 'tsc');
101
- if (!existsSync(nativeBin)) {
102
- throw new Error(`Missing ${nativeBin}. @typescript/native must remain the TypeScript 7 native compiler for ttsc.`);
101
+ const rootPackageJson = path.join(root, 'package.json');
102
+ const declaresNative =
103
+ existsSync(rootPackageJson) &&
104
+ (() => {
105
+ try {
106
+ const pkg = JSON.parse(readFileSync(rootPackageJson, 'utf8')) as {
107
+ devDependencies?: Record<string, string>;
108
+ dependencies?: Record<string, string>;
109
+ };
110
+ return (
111
+ typeof pkg.devDependencies?.['@typescript/native'] === 'string' ||
112
+ typeof pkg.dependencies?.['@typescript/native'] === 'string'
113
+ );
114
+ } catch {
115
+ return false;
116
+ }
117
+ })();
118
+ if (declaresNative && !existsSync(nativeBin)) {
119
+ throw new Error(
120
+ `Missing ${nativeBin}. package.json declares @typescript/native but it is not installed. ` +
121
+ 'Run bun install (or direnv reload). @typescript/native is the TypeScript 7 native compiler for ttsc.',
122
+ );
103
123
  }
104
124
  }
105
125
 
@@ -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