@smoothbricks/cli 0.10.3 → 0.10.5

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 (115) hide show
  1. package/README.md +9 -13
  2. package/dist/bun/preload.d.ts +13 -0
  3. package/dist/bun/preload.d.ts.map +1 -0
  4. package/dist/bun/preload.js +12 -0
  5. package/dist/cli.js +13 -5
  6. package/dist/generate/index.d.ts.map +1 -1
  7. package/dist/generate/index.js +0 -6
  8. package/dist/github-ci/index.d.ts.map +1 -1
  9. package/dist/github-ci/index.js +52 -16
  10. package/dist/github-ci/outputs.d.ts.map +1 -1
  11. package/dist/github-ci/outputs.js +1 -2
  12. package/dist/lib/cli-package.js +2 -2
  13. package/dist/lib/json.d.ts +120 -11
  14. package/dist/lib/json.d.ts.map +1 -1
  15. package/dist/lib/json.js +319 -29
  16. package/dist/lib/workspace.d.ts +7 -14
  17. package/dist/lib/workspace.d.ts.map +1 -1
  18. package/dist/lib/workspace.js +76 -82
  19. package/dist/monorepo/ci-workflow.d.ts +2 -0
  20. package/dist/monorepo/ci-workflow.d.ts.map +1 -1
  21. package/dist/monorepo/ci-workflow.js +27 -1
  22. package/dist/monorepo/git-config.js +2 -2
  23. package/dist/monorepo/index.d.ts +4 -2
  24. package/dist/monorepo/index.d.ts.map +1 -1
  25. package/dist/monorepo/index.js +19 -3
  26. package/dist/monorepo/lockfile.d.ts +14 -1
  27. package/dist/monorepo/lockfile.d.ts.map +1 -1
  28. package/dist/monorepo/lockfile.js +33 -21
  29. package/dist/monorepo/managed-files.d.ts +11 -0
  30. package/dist/monorepo/managed-files.d.ts.map +1 -1
  31. package/dist/monorepo/managed-files.js +57 -32
  32. package/dist/monorepo/package-policy.d.ts.map +1 -1
  33. package/dist/monorepo/package-policy.js +52 -45
  34. package/dist/monorepo/packed-manifest.d.ts +5 -2
  35. package/dist/monorepo/packed-manifest.d.ts.map +1 -1
  36. package/dist/monorepo/packed-manifest.js +16 -47
  37. package/dist/monorepo/packed-package.d.ts.map +1 -1
  38. package/dist/monorepo/packed-package.js +28 -16
  39. package/dist/monorepo/packs/index.d.ts.map +1 -1
  40. package/dist/monorepo/packs/index.js +14 -5
  41. package/dist/monorepo/publish-workflow.js +3 -1
  42. package/dist/monorepo/runtime.d.ts +15 -0
  43. package/dist/monorepo/runtime.d.ts.map +1 -1
  44. package/dist/monorepo/runtime.js +66 -12
  45. package/dist/monorepo/tool-validation.d.ts +2 -2
  46. package/dist/monorepo/tool-validation.d.ts.map +1 -1
  47. package/dist/monorepo/tool-validation.js +141 -38
  48. package/dist/monorepo/wrangler.d.ts.map +1 -1
  49. package/dist/monorepo/wrangler.js +13 -13
  50. package/dist/nx/index.d.ts +9 -7
  51. package/dist/nx/index.d.ts.map +1 -1
  52. package/dist/nx/index.js +48 -31
  53. package/dist/pr/index.d.ts.map +1 -1
  54. package/dist/pr/index.js +28 -32
  55. package/dist/release/candidates.d.ts +3 -2
  56. package/dist/release/candidates.d.ts.map +1 -1
  57. package/dist/release/candidates.js +4 -5
  58. package/dist/release/index.d.ts +1 -0
  59. package/dist/release/index.d.ts.map +1 -1
  60. package/dist/release/index.js +88 -59
  61. package/dist/wrangler/prepare-env.d.ts +0 -1
  62. package/dist/wrangler/prepare-env.d.ts.map +1 -1
  63. package/dist/wrangler/prepare-env.js +100 -51
  64. package/dist/wrangler/scaffold.d.ts.map +1 -1
  65. package/dist/wrangler/scaffold.js +12 -12
  66. package/managed/raw/envrc +5 -3
  67. package/managed/raw/git-format-staged.yml +19 -1
  68. package/managed/raw/patches/ttsc@0.19.3.patch +67 -0
  69. package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +2 -1
  70. package/managed/raw/tooling/direnv/merge-newer-pins.sh +3 -3
  71. package/managed/raw/tooling/direnv/setup-environment.ts +170 -19
  72. package/managed/raw/tooling/git-hooks/pre-commit.sh +0 -6
  73. package/managed/templates/github/actions/cache-node-modules/action.yml +5 -2
  74. package/managed/templates/github/actions/cache-ttsc-plugins/action.yml +19 -0
  75. package/managed/templates/github/actions/setup-devenv/action.yml +5 -0
  76. package/managed/templates/github/workflows/managed-files.yml +80 -0
  77. package/package.json +15 -4
  78. package/src/bun/preload.ts +12 -0
  79. package/src/cli.ts +20 -8
  80. package/src/generate/index.ts +0 -6
  81. package/src/github-ci/index.ts +49 -18
  82. package/src/github-ci/outputs.ts +1 -2
  83. package/src/lib/cli-package.ts +2 -2
  84. package/src/lib/json.ts +293 -32
  85. package/src/lib/workspace.ts +88 -100
  86. package/src/monorepo/__tests__/ci-workflow.test.ts +3 -3
  87. package/src/monorepo/ci-workflow.ts +27 -1
  88. package/src/monorepo/git-config.ts +2 -2
  89. package/src/monorepo/index.ts +19 -3
  90. package/src/monorepo/lockfile.test.ts +35 -36
  91. package/src/monorepo/lockfile.ts +49 -24
  92. package/src/monorepo/managed-files.test.ts +1 -0
  93. package/src/monorepo/managed-files.ts +62 -38
  94. package/src/monorepo/package-policy.test.ts +20 -50
  95. package/src/monorepo/package-policy.ts +76 -59
  96. package/src/monorepo/packed-manifest.ts +18 -52
  97. package/src/monorepo/packed-package.ts +43 -22
  98. package/src/monorepo/packs/index.ts +14 -5
  99. package/src/monorepo/publish-workflow.ts +3 -1
  100. package/src/monorepo/runtime.test.ts +42 -3
  101. package/src/monorepo/runtime.ts +90 -12
  102. package/src/monorepo/tool-validation.test.ts +38 -16
  103. package/src/monorepo/tool-validation.ts +149 -41
  104. package/src/monorepo/wrangler.test.ts +11 -15
  105. package/src/monorepo/wrangler.ts +15 -15
  106. package/src/nx/index.test.ts +9 -5
  107. package/src/nx/index.ts +69 -42
  108. package/src/pr/index.ts +42 -36
  109. package/src/release/__tests__/candidates.test.ts +5 -4
  110. package/src/release/__tests__/trust-publisher.test.ts +7 -0
  111. package/src/release/candidates.ts +10 -17
  112. package/src/release/index.ts +110 -73
  113. package/src/wrangler/prepare-env.ts +75 -54
  114. package/src/wrangler/scaffold.test.ts +9 -12
  115. package/src/wrangler/scaffold.ts +13 -13
@@ -1,11 +1,12 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
- import { runtimeTypesRangeForPublishedVersions } from './runtime.js';
2
+ import type { PackageJson } from '../lib/json.js';
3
+ import { runtimeTypesRangeForPublishedVersions, validateRuntimePins } from './runtime.js';
3
4
 
4
5
  describe('runtimeTypesRangeForPublishedVersions', () => {
5
- test('uses the installed Node major when @types/node has published it', () => {
6
+ test('uses the newest published minor within the installed Node major, never the ~major.0.0 floor', () => {
6
7
  expect(
7
8
  runtimeTypesRangeForPublishedVersions('@types/node', '24.12.0', 'major', ['24.0.0', '24.12.4', '25.9.1']),
8
- ).toBe('~24.0.0');
9
+ ).toBe('~24.12.4');
9
10
  });
10
11
 
11
12
  test('falls back to latest published @types/node when the Node major is unpublished', () => {
@@ -28,3 +29,41 @@ describe('runtimeTypesRangeForPublishedVersions', () => {
28
29
  );
29
30
  });
30
31
  });
32
+
33
+ describe('validateRuntimePins', () => {
34
+ const runtime = { node: '24.16.0', bun: '1.3.14' };
35
+ const aligned = () => ({
36
+ engines: { node: '>=24.0.0' },
37
+ packageManager: 'bun@1.3.14',
38
+ devDependencies: { '@types/node': '~24.13.0' },
39
+ });
40
+
41
+ test('passes when every pin agrees with the PATH runtimes', () => {
42
+ expect(validateRuntimePins(aligned(), runtime)).toBe(0);
43
+ });
44
+
45
+ test('fails when @types/node tracks a different major than the PATH node', () => {
46
+ const pkg = aligned();
47
+ pkg.devDependencies['@types/node'] = '~26.1.1';
48
+ expect(validateRuntimePins(pkg, runtime)).toBe(1);
49
+ });
50
+
51
+ test('fails the ~major.0.0 floor pin even when the major matches', () => {
52
+ const pkg = aligned();
53
+ pkg.devDependencies['@types/node'] = '~24.0.0';
54
+ expect(validateRuntimePins(pkg, runtime)).toBe(1);
55
+ });
56
+
57
+ test('fails engines.node and packageManager drift against the PATH runtimes', () => {
58
+ const pkg = aligned();
59
+ pkg.engines.node = '>=22.0.0';
60
+ pkg.packageManager = 'bun@1.2.0';
61
+ expect(validateRuntimePins(pkg, runtime)).toBe(2);
62
+ });
63
+
64
+ test('fails a missing @types/node pin', () => {
65
+ const pkg: PackageJson = aligned();
66
+ pkg.devDependencies = {};
67
+ expect(validateRuntimePins(pkg, runtime)).toBe(1);
68
+ });
69
+ });
@@ -1,7 +1,31 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { $ } from 'bun';
4
- import { getOrCreateRecord, readJsonObject, setStringProperty, writeJsonObject } from '../lib/json.js';
4
+ import {
5
+ ensureDependencyMap,
6
+ ensureEngines,
7
+ type PackageJson,
8
+ parseStringArrayText,
9
+ readJsonObject,
10
+ setPackageStringField,
11
+ setStringProperty,
12
+ writeJsonObject,
13
+ } from '../lib/json.js';
14
+
15
+ export interface RuntimeVersions {
16
+ node: string;
17
+ bun: string;
18
+ }
19
+
20
+ /** Live runtime versions from the shell PATH — the single source sync AND validate derive from. */
21
+ export async function runtimeVersionsFromPath(root: string): Promise<RuntimeVersions> {
22
+ const node = runtimeCommand(root, 'node');
23
+ const bun = runtimeCommand(root, 'bun');
24
+ return {
25
+ node: (await $`${node} --version`.cwd(root).text()).trim().replace(/^v/, ''),
26
+ bun: (await $`${bun} --version`.cwd(root).text()).trim(),
27
+ };
28
+ }
5
29
 
6
30
  export async function syncRootRuntimeVersions(root: string): Promise<void> {
7
31
  const packageJsonPath = join(root, 'package.json');
@@ -9,20 +33,17 @@ export async function syncRootRuntimeVersions(root: string): Promise<void> {
9
33
  if (!packageJson) {
10
34
  throw new Error('package.json not found or invalid');
11
35
  }
12
- const node = runtimeCommand(root, 'node');
13
- const bun = runtimeCommand(root, 'bun');
14
- const nodeVersion = (await $`${node} --version`.cwd(root).text()).trim().replace(/^v/, '');
15
- const bunVersion = (await $`${bun} --version`.cwd(root).text()).trim();
36
+ const { node: nodeVersion, bun: bunVersion } = await runtimeVersionsFromPath(root);
16
37
  const nodeMajor = nodeVersion.split('.', 1)[0];
17
38
  if (!nodeMajor) {
18
39
  throw new Error(`Unable to derive Node major version from ${nodeVersion}`);
19
40
  }
20
41
 
21
42
  let changed = false;
22
- const engines = getOrCreateRecord(packageJson, 'engines');
43
+ const engines = ensureEngines(packageJson);
23
44
  changed = setStringProperty(engines, 'node', `>=${nodeMajor}.0.0`) || changed;
24
- changed = setStringProperty(packageJson, 'packageManager', `bun@${bunVersion}`) || changed;
25
- const devDependencies = getOrCreateRecord(packageJson, 'devDependencies');
45
+ changed = setPackageStringField(packageJson, 'packageManager', `bun@${bunVersion}`) || changed;
46
+ const devDependencies = ensureDependencyMap(packageJson, 'devDependencies');
26
47
  changed =
27
48
  setStringProperty(
28
49
  devDependencies,
@@ -42,6 +63,58 @@ export async function syncRootRuntimeVersions(root: string): Promise<void> {
42
63
  }
43
64
  }
44
65
 
66
+ /**
67
+ * Validate that package.json runtime pins agree with the LIVE PATH runtimes,
68
+ * never a stored template. Offline by design: structural alignment only
69
+ * (majors, exact bun pin, no `~major.0.0` floor) — the registry is not
70
+ * consulted. Repair: `smoo monorepo init --runtime-only` inside the devenv shell.
71
+ */
72
+ export async function validateRootRuntimeVersions(root: string): Promise<number> {
73
+ const packageJson = readJsonObject(join(root, 'package.json'));
74
+ if (!packageJson) {
75
+ console.error('package.json not found or invalid');
76
+ return 1;
77
+ }
78
+ return validateRuntimePins(packageJson, await runtimeVersionsFromPath(root));
79
+ }
80
+
81
+ export function validateRuntimePins(packageJson: PackageJson, runtime: RuntimeVersions): number {
82
+ const nodeMajor = runtime.node.split('.', 1)[0];
83
+ if (!nodeMajor) {
84
+ throw new Error(`Unable to derive Node major version from ${runtime.node}`);
85
+ }
86
+ let failures = 0;
87
+ const repair = 'run `smoo monorepo init --runtime-only` inside the devenv shell';
88
+ const enginesNode = packageJson.engines?.node ?? null;
89
+ if (enginesNode !== `>=${nodeMajor}.0.0`) {
90
+ console.error(
91
+ `package.json engines.node is ${enginesNode ?? 'missing'} but the PATH node is v${runtime.node} — expected >=${nodeMajor}.0.0; ${repair}`,
92
+ );
93
+ failures++;
94
+ }
95
+ const packageManager = packageJson.packageManager ?? null;
96
+ if (packageManager !== `bun@${runtime.bun}`) {
97
+ console.error(
98
+ `package.json packageManager is ${packageManager ?? 'missing'} but the PATH bun is ${runtime.bun} — expected bun@${runtime.bun}; ${repair}`,
99
+ );
100
+ failures++;
101
+ }
102
+ const typesNode = packageJson.devDependencies?.['@types/node'] ?? null;
103
+ const typesNodeParts = typesNode ? /^~(\d+)\.(\d+)\.(\d+)$/.exec(typesNode) : null;
104
+ if (!typesNodeParts || typesNodeParts[1] !== nodeMajor) {
105
+ console.error(
106
+ `package.json @types/node is ${typesNode ?? 'missing'} but the PATH node is v${runtime.node} — types track the runtime major (~${nodeMajor}.x.y, newest published minor); ${repair}`,
107
+ );
108
+ failures++;
109
+ } else if (typesNodeParts[2] === '0' && typesNodeParts[3] === '0') {
110
+ console.error(
111
+ `package.json @types/node is pinned to the ~${nodeMajor}.0.0 floor — tilde locks the first patch line and strands the repo on early broken releases; ${repair} to repin to the newest published ${nodeMajor}.x`,
112
+ );
113
+ failures++;
114
+ }
115
+ return failures;
116
+ }
117
+
45
118
  type RuntimeTypesPinMode = 'major' | 'exact';
46
119
 
47
120
  async function runtimeTypesRange(
@@ -51,8 +124,8 @@ async function runtimeTypesRange(
51
124
  pinMode: RuntimeTypesPinMode,
52
125
  ): Promise<string> {
53
126
  const versionsText = await $`bun pm view ${packageName} versions --json`.cwd(root).text();
54
- const versions = JSON.parse(versionsText) as unknown;
55
- if (!Array.isArray(versions) || !versions.every((version) => typeof version === 'string')) {
127
+ const versions = parseStringArrayText(versionsText);
128
+ if (!versions) {
56
129
  throw new Error(`Unable to read published ${packageName} versions`);
57
130
  }
58
131
  return runtimeTypesRangeForPublishedVersions(packageName, runtimeVersion, pinMode, versions);
@@ -71,8 +144,13 @@ export function runtimeTypesRangeForPublishedVersions(
71
144
 
72
145
  if (pinMode === 'major') {
73
146
  const runtimeMajor = parsedRuntimeVersion[0].toString();
74
- if (latestVersion(versions.filter((version) => versionMajor(version) === runtimeMajor))) {
75
- return `~${runtimeMajor}.0.0`;
147
+ // Newest published types WITHIN the runtime major — never the `~major.0.0`
148
+ // floor: tilde locks the patch line, so the floor can strand consumers on a
149
+ // broken early release (e.g. @types/node 24.0.x's URLPattern/DOM TS2403
150
+ // conflict, fixed in 24.13) with no path to the repaired minors.
151
+ const latestInMajor = latestVersion(versions.filter((version) => versionMajor(version) === runtimeMajor));
152
+ if (latestInMajor) {
153
+ return `~${latestInMajor}`;
76
154
  }
77
155
  } else if (versions.includes(runtimeVersion)) {
78
156
  return runtimeVersion;
@@ -13,13 +13,20 @@ import {
13
13
  const registryVersions: Record<string, string[]> = {
14
14
  '@biomejs/biome': ['2.3.5'],
15
15
  '@nx/js': ['23.1.0'],
16
+ '@smoothbricks/cli': ['0.10.3', '0.10.4'],
16
17
  '@smoothbricks/nx-plugin': ['0.3.0'],
17
18
  eslint: ['9.39.1'],
18
19
  'eslint-stdout': ['1.1.1', '1.1.2'],
19
20
  nx: ['23.1.0'],
20
21
  prettier: ['3.6.1'],
21
- ttsc: ['0.18.4'],
22
- typescript: ['5.9.3'],
22
+ ttsc: ['0.19.3'],
23
+ typescript: ['6.0.3'],
24
+ // alias package is not fetched from registry under this name when using fallback
25
+ '@typescript/native': ['7.0.2'],
26
+ };
27
+
28
+ const registryLatestTags: Record<string, string> = {
29
+ '@smoothbricks/cli': '0.10.4',
23
30
  };
24
31
 
25
32
  const realFetch = globalThis.fetch;
@@ -58,9 +65,9 @@ describe('tool configuration validation', () => {
58
65
  `,
59
66
  );
60
67
 
61
- expect(validateToolConfig(root)).toBeGreaterThan(0);
68
+ expect(await validateToolConfig(root)).toBeGreaterThan(0);
62
69
  await applyToolConfigDefaults(root);
63
- expect(validateToolConfig(root)).toBe(0);
70
+ expect(await validateToolConfig(root)).toBe(0);
64
71
 
65
72
  const rootPackage = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'));
66
73
  const toolingPackage = JSON.parse(await readFile(join(root, 'tooling/package.json'), 'utf8'));
@@ -69,6 +76,10 @@ describe('tool configuration validation', () => {
69
76
  expect(rootPackage.devDependencies['@smoothbricks/nx-plugin']).toBe('workspace:*');
70
77
  expect(rootPackage.devDependencies['eslint-stdout']).toBe('workspace:*');
71
78
  expect(rootPackage.devDependencies.nx).toBe('23.1.0');
79
+ expect(rootPackage.devDependencies.ttsc).toBe('0.19.3');
80
+ expect(rootPackage.devDependencies.typescript).toBe('^6.0.3');
81
+ expect(rootPackage.devDependencies['@typescript/native']).toBe('npm:typescript@^7.0.2');
82
+ expect(rootPackage.patchedDependencies['ttsc@0.19.3']).toBe('patches/ttsc@0.19.3.patch');
72
83
  expect(rootPackage.workspaces).toContain('tooling');
73
84
  expect(toolingPackage.name).toBe('@smoothbricks/tooling');
74
85
  expect(toolingPackage.dependencies['@smoothbricks/cli']).toBe('workspace:*');
@@ -97,8 +108,12 @@ describe('tool configuration validation', () => {
97
108
  'eslint-stdout': await currentEslintStdoutRange(),
98
109
  nx: '24.0.0',
99
110
  prettier: '^3.7.0',
100
- ttsc: '^0.19.0',
111
+ ttsc: '0.19.3',
101
112
  typescript: '^6.0.0',
113
+ '@typescript/native': 'npm:typescript@^7.0.2',
114
+ },
115
+ patchedDependencies: {
116
+ 'ttsc@0.19.3': 'patches/ttsc@0.19.3.patch',
102
117
  },
103
118
  });
104
119
  await writeJson(join(root, 'tooling/package.json'), {
@@ -128,18 +143,17 @@ describe('tool configuration validation', () => {
128
143
  `,
129
144
  );
130
145
 
131
- expect(validateToolConfig(root)).toBe(0);
146
+ expect(await validateToolConfig(root)).toBe(0);
132
147
  } finally {
133
148
  await rm(root, { recursive: true, force: true });
134
149
  }
135
150
  });
136
151
 
137
- it('preserves an installable CLI range when the running CLI is a linked prerelease', async () => {
152
+ it('bumps consumer CLI pin to npm latest even when the running CLI is a linked prerelease', async () => {
138
153
  const root = await mkdtemp(join(tmpdir(), 'smoo-tool-validation-'));
139
154
  try {
140
155
  const configuredRange = '^0.10.1';
141
- const runningRange = await currentCliRange();
142
- const expectedRange = runningRange.includes('-') ? configuredRange : runningRange;
156
+ const expectedRange = `^${registryLatestTags['@smoothbricks/cli']}`;
143
157
  await writeJson(join(root, 'package.json'), {
144
158
  name: '@fixture/app',
145
159
  version: '0.0.0',
@@ -152,7 +166,7 @@ describe('tool configuration validation', () => {
152
166
  dependencies: { '@smoothbricks/cli': configuredRange },
153
167
  });
154
168
 
155
- const context = readToolContext(root);
169
+ const context = await readToolContext(root);
156
170
  expect(context.policy.cliDependencyRange).toBe(expectedRange);
157
171
  applyToolingPackageDefaults(root, context.policy);
158
172
 
@@ -172,7 +186,7 @@ describe('tool configuration validation', () => {
172
186
  private: true,
173
187
  workspaces: ['packages/*', 'tooling'],
174
188
  });
175
- applyToolingPackageDefaults(root, readToolContext(root).policy);
189
+ applyToolingPackageDefaults(root, (await readToolContext(root)).policy);
176
190
 
177
191
  const toolingPackage = JSON.parse(await readFile(join(root, 'tooling/package.json'), 'utf8'));
178
192
  expect(toolingPackage.name).toBe('@smoothbricks/tooling');
@@ -196,12 +210,12 @@ describe('tool configuration validation', () => {
196
210
  eslint: '^10.0.0',
197
211
  nx: '24.0.0',
198
212
  prettier: '^3.7.0',
199
- ttsc: '^0.19.0',
213
+ ttsc: '0.19.3',
200
214
  typescript: '^6.0.0',
201
215
  },
202
216
  });
203
217
 
204
- const context = readToolContext(root);
218
+ const context = await readToolContext(root);
205
219
  await applyRootDevDependencyDefaults(root, context);
206
220
  applyToolingPackageDefaults(root, context.policy);
207
221
 
@@ -209,6 +223,8 @@ describe('tool configuration validation', () => {
209
223
  const toolingPackage = JSON.parse(await readFile(join(root, 'tooling/package.json'), 'utf8'));
210
224
  expect(rootPackage.devDependencies['eslint-stdout']).toBe(await currentEslintStdoutRange());
211
225
  expect(rootPackage.devDependencies['@smoothbricks/nx-plugin']).toBe(await currentNxPluginRange());
226
+ expect(rootPackage.devDependencies['@typescript/native']).toBe('npm:typescript@^7.0.2');
227
+ expect(rootPackage.patchedDependencies?.['ttsc@0.19.3']).toBe('patches/ttsc@0.19.3.patch');
212
228
  expect(toolingPackage.name).toBe('@fixture/tooling');
213
229
  expect(toolingPackage.dependencies['@smoothbricks/cli']).toBe(await currentCliRange());
214
230
  } finally {
@@ -218,8 +234,7 @@ describe('tool configuration validation', () => {
218
234
  });
219
235
 
220
236
  async function currentCliRange(): Promise<string> {
221
- const pkg = JSON.parse(await readFile(new URL('../../package.json', import.meta.url), 'utf8'));
222
- return `^${pkg.version}`;
237
+ return `^${registryLatestTags['@smoothbricks/cli']}`;
223
238
  }
224
239
 
225
240
  async function currentEslintStdoutRange(): Promise<string> {
@@ -235,8 +250,15 @@ const mockRegistryFetch = Object.assign(
235
250
  if (!versions) {
236
251
  return Promise.resolve(new Response('{}', { status: 404 }));
237
252
  }
253
+ const body: Record<string, unknown> = {
254
+ versions: Object.fromEntries(versions.map((version) => [version, {}])),
255
+ };
256
+ const latest = registryLatestTags[packageName];
257
+ if (latest) {
258
+ body['dist-tags'] = { latest };
259
+ }
238
260
  return Promise.resolve(
239
- new Response(JSON.stringify({ versions: Object.fromEntries(versions.map((version) => [version, {}])) }), {
261
+ new Response(JSON.stringify(body), {
240
262
  status: 200,
241
263
  headers: { 'content-type': 'application/json' },
242
264
  }),