@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.
- package/README.md +9 -13
- package/dist/bun/preload.d.ts +13 -0
- package/dist/bun/preload.d.ts.map +1 -0
- package/dist/bun/preload.js +12 -0
- package/dist/cli.js +13 -5
- package/dist/generate/index.d.ts.map +1 -1
- package/dist/generate/index.js +0 -6
- package/dist/github-ci/index.d.ts.map +1 -1
- package/dist/github-ci/index.js +52 -16
- package/dist/github-ci/outputs.d.ts.map +1 -1
- package/dist/github-ci/outputs.js +1 -2
- package/dist/lib/cli-package.js +2 -2
- package/dist/lib/json.d.ts +120 -11
- package/dist/lib/json.d.ts.map +1 -1
- package/dist/lib/json.js +319 -29
- package/dist/lib/workspace.d.ts +7 -14
- package/dist/lib/workspace.d.ts.map +1 -1
- package/dist/lib/workspace.js +76 -82
- package/dist/monorepo/ci-workflow.d.ts +2 -0
- package/dist/monorepo/ci-workflow.d.ts.map +1 -1
- package/dist/monorepo/ci-workflow.js +27 -1
- package/dist/monorepo/git-config.js +2 -2
- package/dist/monorepo/index.d.ts +4 -2
- package/dist/monorepo/index.d.ts.map +1 -1
- package/dist/monorepo/index.js +19 -3
- package/dist/monorepo/lockfile.d.ts +14 -1
- package/dist/monorepo/lockfile.d.ts.map +1 -1
- package/dist/monorepo/lockfile.js +33 -21
- package/dist/monorepo/managed-files.d.ts +11 -0
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +57 -32
- package/dist/monorepo/package-policy.d.ts.map +1 -1
- package/dist/monorepo/package-policy.js +52 -45
- package/dist/monorepo/packed-manifest.d.ts +5 -2
- package/dist/monorepo/packed-manifest.d.ts.map +1 -1
- package/dist/monorepo/packed-manifest.js +16 -47
- package/dist/monorepo/packed-package.d.ts.map +1 -1
- package/dist/monorepo/packed-package.js +28 -16
- package/dist/monorepo/packs/index.d.ts.map +1 -1
- package/dist/monorepo/packs/index.js +14 -5
- package/dist/monorepo/publish-workflow.js +3 -1
- package/dist/monorepo/runtime.d.ts +15 -0
- package/dist/monorepo/runtime.d.ts.map +1 -1
- package/dist/monorepo/runtime.js +66 -12
- package/dist/monorepo/tool-validation.d.ts +2 -2
- package/dist/monorepo/tool-validation.d.ts.map +1 -1
- package/dist/monorepo/tool-validation.js +141 -38
- package/dist/monorepo/wrangler.d.ts.map +1 -1
- package/dist/monorepo/wrangler.js +13 -13
- package/dist/nx/index.d.ts +9 -7
- package/dist/nx/index.d.ts.map +1 -1
- package/dist/nx/index.js +48 -31
- package/dist/pr/index.d.ts.map +1 -1
- package/dist/pr/index.js +28 -32
- package/dist/release/candidates.d.ts +3 -2
- package/dist/release/candidates.d.ts.map +1 -1
- package/dist/release/candidates.js +4 -5
- package/dist/release/index.d.ts +1 -0
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +88 -59
- package/dist/wrangler/prepare-env.d.ts +0 -1
- package/dist/wrangler/prepare-env.d.ts.map +1 -1
- package/dist/wrangler/prepare-env.js +100 -51
- package/dist/wrangler/scaffold.d.ts.map +1 -1
- package/dist/wrangler/scaffold.js +12 -12
- package/managed/raw/envrc +5 -3
- package/managed/raw/git-format-staged.yml +19 -1
- package/managed/raw/patches/ttsc@0.19.3.patch +67 -0
- package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +2 -1
- package/managed/raw/tooling/direnv/merge-newer-pins.sh +3 -3
- package/managed/raw/tooling/direnv/setup-environment.ts +170 -19
- package/managed/raw/tooling/git-hooks/pre-commit.sh +0 -6
- package/managed/templates/github/actions/cache-node-modules/action.yml +5 -2
- package/managed/templates/github/actions/cache-ttsc-plugins/action.yml +19 -0
- package/managed/templates/github/actions/setup-devenv/action.yml +5 -0
- package/managed/templates/github/workflows/managed-files.yml +80 -0
- package/package.json +15 -4
- package/src/bun/preload.ts +12 -0
- package/src/cli.ts +20 -8
- package/src/generate/index.ts +0 -6
- package/src/github-ci/index.ts +49 -18
- package/src/github-ci/outputs.ts +1 -2
- package/src/lib/cli-package.ts +2 -2
- package/src/lib/json.ts +293 -32
- package/src/lib/workspace.ts +88 -100
- package/src/monorepo/__tests__/ci-workflow.test.ts +3 -3
- package/src/monorepo/ci-workflow.ts +27 -1
- package/src/monorepo/git-config.ts +2 -2
- package/src/monorepo/index.ts +19 -3
- package/src/monorepo/lockfile.test.ts +35 -36
- package/src/monorepo/lockfile.ts +49 -24
- package/src/monorepo/managed-files.test.ts +1 -0
- package/src/monorepo/managed-files.ts +62 -38
- package/src/monorepo/package-policy.test.ts +20 -50
- package/src/monorepo/package-policy.ts +76 -59
- package/src/monorepo/packed-manifest.ts +18 -52
- package/src/monorepo/packed-package.ts +43 -22
- package/src/monorepo/packs/index.ts +14 -5
- package/src/monorepo/publish-workflow.ts +3 -1
- package/src/monorepo/runtime.test.ts +42 -3
- package/src/monorepo/runtime.ts +90 -12
- package/src/monorepo/tool-validation.test.ts +38 -16
- package/src/monorepo/tool-validation.ts +149 -41
- package/src/monorepo/wrangler.test.ts +11 -15
- package/src/monorepo/wrangler.ts +15 -15
- package/src/nx/index.test.ts +9 -5
- package/src/nx/index.ts +69 -42
- package/src/pr/index.ts +42 -36
- package/src/release/__tests__/candidates.test.ts +5 -4
- package/src/release/__tests__/trust-publisher.test.ts +7 -0
- package/src/release/candidates.ts +10 -17
- package/src/release/index.ts +110 -73
- package/src/wrangler/prepare-env.ts +75 -54
- package/src/wrangler/scaffold.test.ts +9 -12
- package/src/wrangler/scaffold.ts +13 -13
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
|
+
import typia from 'typia';
|
|
3
4
|
import { cliPackageVersion, isSmoothBricksCodebasePackageName } from '../lib/cli-package.js';
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ensureDependencyMap,
|
|
7
|
+
readJsonObject,
|
|
8
|
+
setPackageStringField,
|
|
9
|
+
setStringProperty,
|
|
10
|
+
writeJsonObject,
|
|
11
|
+
} from '../lib/json.js';
|
|
5
12
|
import { type PackageJson, readPackageJsonObject } from '../lib/workspace.js';
|
|
6
13
|
|
|
7
14
|
interface RequiredDependency {
|
|
@@ -23,6 +30,12 @@ export interface ToolContext {
|
|
|
23
30
|
policy: ToolPolicy;
|
|
24
31
|
}
|
|
25
32
|
|
|
33
|
+
interface RegistryPackument {
|
|
34
|
+
versions: Record<string, unknown>;
|
|
35
|
+
'dist-tags'?: Record<string, string>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const isRegistryPackument = typia.createIs<RegistryPackument>();
|
|
26
39
|
const rootDevDependencies: RequiredDependency[] = [
|
|
27
40
|
{ name: '@biomejs/biome', fallbackVersion: '^2.3.5', minimumVersion: '2.3.0', prefix: '^' },
|
|
28
41
|
{ name: '@nx/js', fallbackVersion: '23.1.0', minimumVersion: '23.1.0' },
|
|
@@ -43,28 +56,40 @@ const rootDevDependencies: RequiredDependency[] = [
|
|
|
43
56
|
},
|
|
44
57
|
{ name: 'nx', fallbackVersion: '23.1.0', minimumVersion: '23.1.0' },
|
|
45
58
|
{ name: 'prettier', fallbackVersion: '^3.6.1', minimumVersion: '3.6.0', prefix: '^' },
|
|
46
|
-
|
|
47
|
-
|
|
59
|
+
// Exact pin: Bun patchedDependencies keys are version-locked (see patches/ttsc@0.19.3.patch).
|
|
60
|
+
{ name: 'ttsc', fallbackVersion: '0.19.3' },
|
|
61
|
+
// Nx and typescript-eslint still load the TypeScript JS API (6.x).
|
|
48
62
|
// Compilation is exclusively delegated to ttsc by the Nx plugin targets.
|
|
49
|
-
{ name: 'typescript', fallbackVersion: '^
|
|
63
|
+
{ name: 'typescript', fallbackVersion: '^6.0.3', minimumVersion: '6.0.0', prefix: '^' },
|
|
64
|
+
// TS7 native compiler for ttsc (TTSC_TSGO_BINARY). Not the unscoped API package.
|
|
65
|
+
// npm alias form required — Bun cannot install @typescript/typescript6 cleanly (bun#33834).
|
|
66
|
+
{ name: '@typescript/native', fallbackVersion: 'npm:typescript@^7.0.2' },
|
|
50
67
|
];
|
|
51
68
|
|
|
52
69
|
const cliPackageName = '@smoothbricks/cli';
|
|
53
70
|
|
|
54
71
|
const requiredDevenvPackages = ['bun', 'git', 'git-format-staged', 'jq', 'alejandra', 'coreutils', 'gnutar'];
|
|
55
|
-
|
|
72
|
+
// Any explicit nodejs provider is fine — the pinned major is the repo's choice
|
|
73
|
+
// (Lambda parity, org convergence, …). Whether the pins in package.json agree
|
|
74
|
+
// with the runtime is validated against the live PATH (validateRootRuntimeVersions),
|
|
75
|
+
// never against a version template here.
|
|
76
|
+
const nodePackagePattern = /(^|\s)nodejs(_\d+|_latest)?(\s|#|$)/m;
|
|
77
|
+
|
|
78
|
+
const TTSC_PATCHED_DEPENDENCY_KEY = 'ttsc@0.19.3';
|
|
79
|
+
const TTSC_PATCH_PATH = 'patches/ttsc@0.19.3.patch';
|
|
56
80
|
|
|
57
81
|
export async function applyToolConfigDefaults(root: string): Promise<void> {
|
|
58
|
-
const context = readToolContext(root);
|
|
82
|
+
const context = await readToolContext(root);
|
|
59
83
|
await applyRootPackageToolDefaults(root, context);
|
|
60
84
|
applyToolingPackageDefaults(root, context.policy);
|
|
61
85
|
applyDevenvPackageDefaults(root);
|
|
62
86
|
}
|
|
63
87
|
|
|
64
|
-
export function validateToolConfig(root: string): number {
|
|
65
|
-
const context = readToolContext(root);
|
|
88
|
+
export async function validateToolConfig(root: string): Promise<number> {
|
|
89
|
+
const context = await readToolContext(root);
|
|
66
90
|
return (
|
|
67
91
|
validateRootDevDependencies(context.policy, context.rootPackage) +
|
|
92
|
+
validateTtscPatchedDependency(context.rootPackage) +
|
|
68
93
|
validateToolingPackage(root, context.policy) +
|
|
69
94
|
validateToolingWorkspace(context.rootPackage) +
|
|
70
95
|
validateDevenvPackages(root)
|
|
@@ -77,7 +102,7 @@ export async function applyRootDevDependencyDefaults(root: string, context: Tool
|
|
|
77
102
|
return;
|
|
78
103
|
}
|
|
79
104
|
let changed = false;
|
|
80
|
-
const devDependencies =
|
|
105
|
+
const devDependencies = ensureDependencyMap(pkg, 'devDependencies');
|
|
81
106
|
for (const dependency of rootDevDependencies) {
|
|
82
107
|
const current = devDependencies[dependency.name];
|
|
83
108
|
if (typeof current !== 'string' || !satisfiesDependencyPolicy(context.policy, current, dependency)) {
|
|
@@ -88,6 +113,7 @@ export async function applyRootDevDependencyDefaults(root: string, context: Tool
|
|
|
88
113
|
if (delete devDependencies['@smoothbricks/cli']) {
|
|
89
114
|
changed = true;
|
|
90
115
|
}
|
|
116
|
+
changed = ensureTtscPatchedDependency(pkg) || changed;
|
|
91
117
|
if (changed) {
|
|
92
118
|
writeJsonObject(join(root, 'package.json'), pkg);
|
|
93
119
|
console.log('updated package.json workspace tool dependencies');
|
|
@@ -103,7 +129,8 @@ async function applyRootPackageToolDefaults(root: string, context: ToolContext):
|
|
|
103
129
|
}
|
|
104
130
|
let dependencyChanged = false;
|
|
105
131
|
let workspaceChanged = false;
|
|
106
|
-
|
|
132
|
+
let patchChanged = false;
|
|
133
|
+
const devDependencies = ensureDependencyMap(pkg, 'devDependencies');
|
|
107
134
|
for (const dependency of rootDevDependencies) {
|
|
108
135
|
const current = devDependencies[dependency.name];
|
|
109
136
|
if (typeof current !== 'string' || !satisfiesDependencyPolicy(context.policy, current, dependency)) {
|
|
@@ -115,7 +142,8 @@ async function applyRootPackageToolDefaults(root: string, context: ToolContext):
|
|
|
115
142
|
dependencyChanged = true;
|
|
116
143
|
}
|
|
117
144
|
workspaceChanged = addWorkspacePattern(pkg, 'tooling');
|
|
118
|
-
|
|
145
|
+
patchChanged = ensureTtscPatchedDependency(pkg);
|
|
146
|
+
if (dependencyChanged || workspaceChanged || patchChanged) {
|
|
119
147
|
writeJsonObject(join(root, 'package.json'), pkg);
|
|
120
148
|
}
|
|
121
149
|
console.log(
|
|
@@ -128,18 +156,23 @@ async function applyRootPackageToolDefaults(root: string, context: ToolContext):
|
|
|
128
156
|
? 'updated package.json tooling workspace'
|
|
129
157
|
: 'unchanged package.json tooling workspace',
|
|
130
158
|
);
|
|
159
|
+
console.log(
|
|
160
|
+
patchChanged
|
|
161
|
+
? 'updated package.json patchedDependencies for ttsc'
|
|
162
|
+
: 'unchanged package.json patchedDependencies for ttsc',
|
|
163
|
+
);
|
|
131
164
|
}
|
|
132
165
|
|
|
133
166
|
export function applyToolingPackageDefaults(root: string, policy: ToolPolicy): void {
|
|
134
167
|
const path = join(root, 'tooling', 'package.json');
|
|
135
168
|
const pkg = readJsonObject(path) ?? { name: policy.toolingPackageName, private: true, dependencies: {} };
|
|
136
169
|
let changed = false;
|
|
137
|
-
changed =
|
|
170
|
+
changed = setPackageStringField(pkg, 'name', policy.toolingPackageName) || changed;
|
|
138
171
|
if (pkg.private !== true) {
|
|
139
172
|
pkg.private = true;
|
|
140
173
|
changed = true;
|
|
141
174
|
}
|
|
142
|
-
const dependencies =
|
|
175
|
+
const dependencies = ensureDependencyMap(pkg, 'dependencies');
|
|
143
176
|
changed = setStringProperty(dependencies, cliPackageName, policy.cliDependencyRange) || changed;
|
|
144
177
|
if (changed || !existsSync(path)) {
|
|
145
178
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -171,7 +204,7 @@ export function applyDevenvPackageDefaults(root: string): void {
|
|
|
171
204
|
}
|
|
172
205
|
let content = readFileSync(path, 'utf8');
|
|
173
206
|
let changed = false;
|
|
174
|
-
if (!
|
|
207
|
+
if (!nodePackagePattern.test(content)) {
|
|
175
208
|
const next = addNixPackage(content, 'nodejs_latest', '# Node.js for workspace tooling');
|
|
176
209
|
changed = next !== content || changed;
|
|
177
210
|
content = next;
|
|
@@ -198,7 +231,7 @@ export function validateRootDevDependencies(policy: ToolPolicy, rootPackage: Pac
|
|
|
198
231
|
console.error('package.json not found or invalid');
|
|
199
232
|
return 1;
|
|
200
233
|
}
|
|
201
|
-
const devDependencies =
|
|
234
|
+
const devDependencies = pkg.devDependencies;
|
|
202
235
|
let failures = 0;
|
|
203
236
|
for (const dependency of rootDevDependencies) {
|
|
204
237
|
const version = devDependencies?.[dependency.name];
|
|
@@ -219,6 +252,26 @@ export function validateRootDevDependencies(policy: ToolPolicy, rootPackage: Pac
|
|
|
219
252
|
return failures;
|
|
220
253
|
}
|
|
221
254
|
|
|
255
|
+
function ensureTtscPatchedDependency(pkg: PackageJson): boolean {
|
|
256
|
+
const patched = ensureDependencyMap(pkg, 'patchedDependencies');
|
|
257
|
+
return setStringProperty(patched, TTSC_PATCHED_DEPENDENCY_KEY, TTSC_PATCH_PATH);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function validateTtscPatchedDependency(rootPackage: PackageJson | null): number {
|
|
261
|
+
if (!rootPackage) {
|
|
262
|
+
return 0;
|
|
263
|
+
}
|
|
264
|
+
const actual = rootPackage.patchedDependencies?.[TTSC_PATCHED_DEPENDENCY_KEY];
|
|
265
|
+
if (actual === TTSC_PATCH_PATH) {
|
|
266
|
+
return 0;
|
|
267
|
+
}
|
|
268
|
+
console.error(
|
|
269
|
+
`package.json patchedDependencies.${TTSC_PATCHED_DEPENDENCY_KEY} must be ${TTSC_PATCH_PATH}` +
|
|
270
|
+
(typeof actual === 'string' ? `; found ${actual}` : ' (required for declaration-emit fix)'),
|
|
271
|
+
);
|
|
272
|
+
return 1;
|
|
273
|
+
}
|
|
274
|
+
|
|
222
275
|
export function validateToolingPackage(root: string, policy: ToolPolicy): number {
|
|
223
276
|
const path = join(root, 'tooling', 'package.json');
|
|
224
277
|
const pkg = readJsonObject(path);
|
|
@@ -226,9 +279,9 @@ export function validateToolingPackage(root: string, policy: ToolPolicy): number
|
|
|
226
279
|
console.error('tooling/package.json not found or invalid');
|
|
227
280
|
return 1;
|
|
228
281
|
}
|
|
229
|
-
const dependencies =
|
|
282
|
+
const dependencies = pkg.dependencies;
|
|
230
283
|
let failures = 0;
|
|
231
|
-
const actualName =
|
|
284
|
+
const actualName = pkg.name ?? null;
|
|
232
285
|
if (actualName !== policy.toolingPackageName) {
|
|
233
286
|
console.error(`tooling/package.json name must be ${policy.toolingPackageName}`);
|
|
234
287
|
failures++;
|
|
@@ -262,8 +315,10 @@ export function validateDevenvPackages(root: string): number {
|
|
|
262
315
|
}
|
|
263
316
|
const content = readFileSync(path, 'utf8');
|
|
264
317
|
let failures = 0;
|
|
265
|
-
if (!
|
|
266
|
-
console.error(
|
|
318
|
+
if (!nodePackagePattern.test(content)) {
|
|
319
|
+
console.error(
|
|
320
|
+
'tooling/direnv/devenv.nix packages must include a nodejs provider (nodejs_<major> or nodejs_latest)',
|
|
321
|
+
);
|
|
267
322
|
failures++;
|
|
268
323
|
}
|
|
269
324
|
for (const name of requiredDevenvPackages) {
|
|
@@ -313,6 +368,10 @@ function satisfiesDependencyPolicy(policy: ToolPolicy, version: string, dependen
|
|
|
313
368
|
if (workspaceDependencyExpected(policy, dependency)) {
|
|
314
369
|
return version === 'workspace:*';
|
|
315
370
|
}
|
|
371
|
+
// Dual-package native compiler: must stay an npm:typescript@7 alias, never unscoped "typescript".
|
|
372
|
+
if (dependency.name === '@typescript/native') {
|
|
373
|
+
return isTypeScriptNativeAlias(version);
|
|
374
|
+
}
|
|
316
375
|
if (dependency.minimumVersion === undefined) {
|
|
317
376
|
return version === dependency.fallbackVersion;
|
|
318
377
|
}
|
|
@@ -324,37 +383,41 @@ function satisfiesDependencyPolicy(policy: ToolPolicy, version: string, dependen
|
|
|
324
383
|
return compareVersions(parsed, minimum) >= 0;
|
|
325
384
|
}
|
|
326
385
|
|
|
386
|
+
function isTypeScriptNativeAlias(version: string): boolean {
|
|
387
|
+
// Accept npm:typescript@7, npm:typescript@^7.0.2, npm:typescript@~7.0.2, etc.
|
|
388
|
+
return /^npm:typescript@(?:\^|~)?7(?:\.|$)/.test(version.trim());
|
|
389
|
+
}
|
|
390
|
+
|
|
327
391
|
function formatExpectedDependency(policy: ToolPolicy, dependency: RequiredDependency): string {
|
|
328
392
|
if (workspaceDependencyExpected(policy, dependency)) {
|
|
329
393
|
return 'workspace:*';
|
|
330
394
|
}
|
|
395
|
+
if (dependency.name === '@typescript/native') {
|
|
396
|
+
return 'npm:typescript@^7 (TypeScript 7 native compiler alias for ttsc)';
|
|
397
|
+
}
|
|
331
398
|
return dependency.minimumVersion ? `>= ${dependency.minimumVersion}` : dependency.fallbackVersion;
|
|
332
399
|
}
|
|
333
400
|
|
|
334
|
-
export function readToolContext(root: string): ToolContext {
|
|
401
|
+
export async function readToolContext(root: string): Promise<ToolContext> {
|
|
335
402
|
const rootPackage = readPackageJsonObject(join(root, 'package.json'));
|
|
336
403
|
const toolingPackage = readJsonObject(join(root, 'tooling', 'package.json'));
|
|
337
|
-
const configuredCliRange =
|
|
404
|
+
const configuredCliRange = toolingPackage?.dependencies?.[cliPackageName];
|
|
338
405
|
return {
|
|
339
406
|
rootPackage,
|
|
340
|
-
policy: toolPolicy(rootPackage, typeof configuredCliRange === 'string' ? configuredCliRange : null),
|
|
407
|
+
policy: await toolPolicy(rootPackage, typeof configuredCliRange === 'string' ? configuredCliRange : null),
|
|
341
408
|
};
|
|
342
409
|
}
|
|
343
410
|
|
|
344
|
-
function toolPolicy(rootPackage: PackageJson | null, configuredCliRange: string | null): ToolPolicy {
|
|
345
|
-
const name =
|
|
411
|
+
async function toolPolicy(rootPackage: PackageJson | null, configuredCliRange: string | null): Promise<ToolPolicy> {
|
|
412
|
+
const name = rootPackage?.name ?? null;
|
|
346
413
|
const isCodebase = isSmoothBricksCodebasePackageName(name ?? undefined);
|
|
347
414
|
const toolingName = toolingPackageName(name);
|
|
348
|
-
// A locally linked prerelease can be newer than every published CLI. Keep an existing
|
|
349
|
-
// installable range instead of rewriting the consumer manifest to an unpublished version.
|
|
350
|
-
const publishedCliRange =
|
|
351
|
-
cliPackageVersion.includes('-') && configuredCliRange && parseVersion(configuredCliRange)
|
|
352
|
-
? configuredCliRange
|
|
353
|
-
: `^${cliPackageVersion}`;
|
|
354
415
|
return {
|
|
355
416
|
isSmoothBricksCodebase: isCodebase,
|
|
356
417
|
toolingPackageName: toolingName,
|
|
357
|
-
|
|
418
|
+
// Consumers pin the latest *published* CLI. Running a linked prerelease must not
|
|
419
|
+
// freeze tooling/package.json on an older range or rewrite it to an unpublished -next.
|
|
420
|
+
cliDependencyRange: isCodebase ? 'workspace:*' : await resolvePublishedCliDependencyRange(configuredCliRange),
|
|
358
421
|
};
|
|
359
422
|
}
|
|
360
423
|
|
|
@@ -379,6 +442,59 @@ async function resolveDependencyVersion(policy: ToolPolicy, dependency: Required
|
|
|
379
442
|
return `${dependency.prefix ?? ''}${latest ?? stripRangePrefix(dependency.fallbackVersion)}`;
|
|
380
443
|
}
|
|
381
444
|
|
|
445
|
+
/**
|
|
446
|
+
* Consumer monorepos pin `@smoothbricks/cli` to the latest stable release.
|
|
447
|
+
* Prefer npm `dist-tags.latest`. Never write a running `-next` package version into
|
|
448
|
+
* consumer manifests; never leave an older pin just because the running CLI is prerelease.
|
|
449
|
+
*/
|
|
450
|
+
async function resolvePublishedCliDependencyRange(configuredCliRange: string | null): Promise<string> {
|
|
451
|
+
const latest = await fetchLatestStableVersion(cliPackageName);
|
|
452
|
+
if (latest) {
|
|
453
|
+
return `^${latest}`;
|
|
454
|
+
}
|
|
455
|
+
// Registry unavailable: if the running CLI is a stable publish, use it.
|
|
456
|
+
if (!cliPackageVersion.includes('-')) {
|
|
457
|
+
return `^${cliPackageVersion}`;
|
|
458
|
+
}
|
|
459
|
+
// Linked prerelease + offline down: keep an existing installable stable pin.
|
|
460
|
+
if (configuredCliRange && parseVersion(configuredCliRange)) {
|
|
461
|
+
return configuredCliRange;
|
|
462
|
+
}
|
|
463
|
+
// Last resort: caret of the prerelease base (0.10.5-next.0 → ^0.10.5) so manifests stay valid.
|
|
464
|
+
const base = parseVersion(cliPackageVersion);
|
|
465
|
+
return base ? `^${base.major}.${base.minor}.${base.patch}` : `^${cliPackageVersion}`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async function fetchLatestStableVersion(packageName: string): Promise<string | null> {
|
|
469
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName).replace('%40', '@')}`;
|
|
470
|
+
const response = await fetch(url, { headers: { accept: 'application/vnd.npm.install-v1+json' } });
|
|
471
|
+
if (!response.ok) {
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
const body: unknown = await response.json();
|
|
475
|
+
if (!isRegistryPackument(body)) {
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
const tagged = body['dist-tags']?.latest;
|
|
479
|
+
if (typeof tagged === 'string' && parseVersion(tagged) && !tagged.includes('-')) {
|
|
480
|
+
return tagged;
|
|
481
|
+
}
|
|
482
|
+
let latest: Version | null = null;
|
|
483
|
+
for (const raw of Object.keys(body.versions)) {
|
|
484
|
+
if (raw.includes('-')) {
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
const version = parseVersion(raw);
|
|
488
|
+
if (!version) {
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (!latest || compareVersions(version, latest) > 0) {
|
|
492
|
+
latest = version;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return latest ? `${latest.major}.${latest.minor}.${latest.patch}` : null;
|
|
496
|
+
}
|
|
497
|
+
|
|
382
498
|
async function fetchLatestPatchVersion(packageName: string, minimumVersion: string): Promise<string | null> {
|
|
383
499
|
const minorRange = sameMajorMinorRange(minimumVersion);
|
|
384
500
|
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName).replace('%40', '@')}`;
|
|
@@ -435,14 +551,6 @@ function compareVersions(left: Version, right: Version): number {
|
|
|
435
551
|
return left.major - right.major || left.minor - right.minor || left.patch - right.patch;
|
|
436
552
|
}
|
|
437
553
|
|
|
438
|
-
function isRegistryPackument(value: unknown): value is { versions: Record<string, unknown> } {
|
|
439
|
-
return typeof value === 'object' && value !== null && 'versions' in value && isObjectRecord(value.versions);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
function isObjectRecord(value: unknown): value is Record<string, unknown> {
|
|
443
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
554
|
function stripRangePrefix(version: string): string {
|
|
447
555
|
return version.replace(/^[~^]/, '');
|
|
448
556
|
}
|
|
@@ -456,7 +564,7 @@ function addWorkspacePattern(pkg: PackageJson, pattern: string): boolean {
|
|
|
456
564
|
workspaces.push(pattern);
|
|
457
565
|
return true;
|
|
458
566
|
}
|
|
459
|
-
if (
|
|
567
|
+
if (workspaces && Array.isArray(workspaces.packages)) {
|
|
460
568
|
if (workspaces.packages.includes(pattern)) {
|
|
461
569
|
return false;
|
|
462
570
|
}
|
|
@@ -472,5 +580,5 @@ function hasWorkspacePattern(pkg: PackageJson, pattern: string): boolean {
|
|
|
472
580
|
if (Array.isArray(workspaces)) {
|
|
473
581
|
return workspaces.includes(pattern);
|
|
474
582
|
}
|
|
475
|
-
return
|
|
583
|
+
return Boolean(workspaces && Array.isArray(workspaces.packages) && workspaces.packages.includes(pattern));
|
|
476
584
|
}
|
|
@@ -2,6 +2,7 @@ import { describe, expect, it, spyOn } from 'bun:test';
|
|
|
2
2
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
+
import { type PackageJson, parsePackageJsonText } from '../lib/json.js';
|
|
5
6
|
import { applyWranglerDefaults, firstWranglerEnv, validateWrangler } from './wrangler.js';
|
|
6
7
|
|
|
7
8
|
describe('firstWranglerEnv', () => {
|
|
@@ -42,7 +43,7 @@ describe('applyWranglerDefaults', () => {
|
|
|
42
43
|
},
|
|
43
44
|
});
|
|
44
45
|
const typecheck = nxTargets(pkg).typecheck;
|
|
45
|
-
expect(
|
|
46
|
+
expect(typecheck?.dependsOn ?? null).toEqual(['^build', 'wrangler-types']);
|
|
46
47
|
|
|
47
48
|
const logs = captureConsoleLogs();
|
|
48
49
|
applyWranglerDefaults(root);
|
|
@@ -63,8 +64,7 @@ describe('applyWranglerDefaults', () => {
|
|
|
63
64
|
try {
|
|
64
65
|
applyWranglerDefaults(root);
|
|
65
66
|
const target = nxTargets(await readJson(join(root, 'packages/api/package.json')))['wrangler-types'];
|
|
66
|
-
|
|
67
|
-
expect(isRecord(options) ? options.command : null).toBe(
|
|
67
|
+
expect(target?.options?.command ?? null).toBe(
|
|
68
68
|
'wrangler types --env-file .dev.vars.example --include-runtime false',
|
|
69
69
|
);
|
|
70
70
|
} finally {
|
|
@@ -187,27 +187,23 @@ async function createWorkspace(
|
|
|
187
187
|
return root;
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
function nxTargets(pkg:
|
|
191
|
-
const
|
|
192
|
-
if (!
|
|
190
|
+
function nxTargets(pkg: PackageJson): NonNullable<NonNullable<PackageJson['nx']>['targets']> {
|
|
191
|
+
const targets = pkg.nx?.targets;
|
|
192
|
+
if (!targets) {
|
|
193
193
|
throw new Error('nx.targets not found');
|
|
194
194
|
}
|
|
195
|
-
return
|
|
195
|
+
return targets;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
function
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
async function readJson(path: string): Promise<Record<string, unknown>> {
|
|
203
|
-
const parsed: unknown = JSON.parse(await readFile(path, 'utf8'));
|
|
204
|
-
if (!isRecord(parsed)) {
|
|
198
|
+
async function readJson(path: string): Promise<PackageJson> {
|
|
199
|
+
const parsed = parsePackageJsonText(await readFile(path, 'utf8'));
|
|
200
|
+
if (!parsed) {
|
|
205
201
|
throw new Error('expected JSON object');
|
|
206
202
|
}
|
|
207
203
|
return parsed;
|
|
208
204
|
}
|
|
209
205
|
|
|
210
|
-
async function writeJson(path: string, value:
|
|
206
|
+
async function writeJson(path: string, value: PackageJson): Promise<void> {
|
|
211
207
|
await mkdir(join(path, '..'), { recursive: true });
|
|
212
208
|
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
213
209
|
}
|
package/src/monorepo/wrangler.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { join
|
|
3
|
-
import {
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { ensureNx, ensureNxTargets, type NxTargetConfig, type PackageJson, writeJsonObject } from '../lib/json.js';
|
|
4
4
|
import { getWorkspacePackageManifests } from '../lib/workspace.js';
|
|
5
5
|
|
|
6
6
|
interface WranglerProject {
|
|
7
7
|
label: string;
|
|
8
8
|
dir: string;
|
|
9
9
|
packageJsonPath: string;
|
|
10
|
-
json:
|
|
10
|
+
json: PackageJson;
|
|
11
11
|
tomlPath: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -20,13 +20,14 @@ export function firstWranglerEnv(tomlText: string): string | null {
|
|
|
20
20
|
function wranglerProjects(root: string): WranglerProject[] {
|
|
21
21
|
const projects: WranglerProject[] = [];
|
|
22
22
|
for (const pkg of getWorkspacePackageManifests(root)) {
|
|
23
|
-
const
|
|
23
|
+
const dir = join(root, pkg.path);
|
|
24
|
+
const tomlPath = join(dir, 'wrangler.toml');
|
|
24
25
|
if (!existsSync(tomlPath)) {
|
|
25
26
|
continue;
|
|
26
27
|
}
|
|
27
28
|
projects.push({
|
|
28
|
-
label:
|
|
29
|
-
dir
|
|
29
|
+
label: pkg.path || '.',
|
|
30
|
+
dir,
|
|
30
31
|
packageJsonPath: pkg.packageJsonPath,
|
|
31
32
|
json: pkg.json,
|
|
32
33
|
tomlPath,
|
|
@@ -56,9 +57,9 @@ export function applyWranglerDefaults(root: string): void {
|
|
|
56
57
|
}
|
|
57
58
|
for (const project of projects) {
|
|
58
59
|
const env = firstWranglerEnv(readFileSync(project.tomlPath, 'utf8'));
|
|
59
|
-
const nx =
|
|
60
|
-
const targets =
|
|
61
|
-
const desired:
|
|
60
|
+
const nx = ensureNx(project.json);
|
|
61
|
+
const targets = ensureNxTargets(nx);
|
|
62
|
+
const desired: NxTargetConfig = {
|
|
62
63
|
executor: 'nx:run-commands',
|
|
63
64
|
cache: true,
|
|
64
65
|
inputs: ['{projectRoot}/wrangler.toml', '{projectRoot}/.dev.vars.example'],
|
|
@@ -69,14 +70,14 @@ export function applyWranglerDefaults(root: string): void {
|
|
|
69
70
|
},
|
|
70
71
|
};
|
|
71
72
|
let changed = false;
|
|
72
|
-
const existing =
|
|
73
|
+
const existing = targets['wrangler-types'];
|
|
73
74
|
if (!existing || JSON.stringify(existing) !== JSON.stringify(desired)) {
|
|
74
75
|
targets['wrangler-types'] = desired;
|
|
75
76
|
changed = true;
|
|
76
77
|
}
|
|
77
|
-
const typecheck =
|
|
78
|
+
const typecheck = targets.typecheck;
|
|
78
79
|
if (typecheck) {
|
|
79
|
-
const dependsOn
|
|
80
|
+
const dependsOn = Array.isArray(typecheck.dependsOn) ? [...typecheck.dependsOn] : [];
|
|
80
81
|
if (!dependsOn.includes('wrangler-types')) {
|
|
81
82
|
dependsOn.push('wrangler-types');
|
|
82
83
|
typecheck.dependsOn = dependsOn;
|
|
@@ -116,9 +117,8 @@ export function validateWrangler(root: string): number {
|
|
|
116
117
|
console.log(`⨯ ${project.label}: missing .dev.vars.example`);
|
|
117
118
|
projectProblems++;
|
|
118
119
|
}
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
if (!targets || !recordProperty(targets, 'wrangler-types')) {
|
|
120
|
+
const targets = project.json.nx?.targets;
|
|
121
|
+
if (!targets?.['wrangler-types']) {
|
|
122
122
|
console.log(`⨯ ${project.label}: missing wrangler-types nx target`);
|
|
123
123
|
projectProblems++;
|
|
124
124
|
}
|
package/src/nx/index.test.ts
CHANGED
|
@@ -94,16 +94,20 @@ describe('Nx helper output formatting', () => {
|
|
|
94
94
|
);
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
it('
|
|
98
|
-
expect(
|
|
97
|
+
it('omits cross-project dependencies from same-project target closures', () => {
|
|
98
|
+
expect(
|
|
99
99
|
targetDependenciesFromNxProjectJson({
|
|
100
100
|
targets: {
|
|
101
|
-
|
|
102
|
-
dependsOn: [
|
|
101
|
+
'rust-wasm': {
|
|
102
|
+
dependsOn: [
|
|
103
|
+
'prepare',
|
|
104
|
+
{ target: 'cargo-wasm', projects: ['lmao-rs'] },
|
|
105
|
+
{ target: 'build', projects: 'dependencies' },
|
|
106
|
+
],
|
|
103
107
|
},
|
|
104
108
|
},
|
|
105
109
|
}),
|
|
106
|
-
).
|
|
110
|
+
).toEqual(new Map([['rust-wasm', ['prepare']]]));
|
|
107
111
|
});
|
|
108
112
|
|
|
109
113
|
it('treats missing target metadata as an empty project', () => {
|