@smoothbricks/cli 0.10.3 → 0.10.4
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 +12 -4
- 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 +118 -11
- package/dist/lib/json.d.ts.map +1 -1
- package/dist/lib/json.js +317 -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 +3 -1
- package/dist/monorepo/index.d.ts.map +1 -1
- package/dist/monorepo/index.js +6 -2
- package/dist/monorepo/lockfile.d.ts +13 -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 +52 -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 +13 -4
- 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.map +1 -1
- package/dist/monorepo/tool-validation.js +32 -23
- 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 +82 -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/git-format-staged.yml +19 -1
- 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 +150 -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 +19 -7
- 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 +291 -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 +6 -2
- package/src/monorepo/lockfile.test.ts +35 -36
- package/src/monorepo/lockfile.ts +48 -24
- package/src/monorepo/managed-files.test.ts +1 -0
- package/src/monorepo/managed-files.ts +57 -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 +13 -4
- 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.ts +35 -25
- 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 +104 -73
- package/src/wrangler/prepare-env.ts +75 -54
- package/src/wrangler/scaffold.test.ts +9 -12
- package/src/wrangler/scaffold.ts +13 -13
|
@@ -21,15 +21,22 @@ import {
|
|
|
21
21
|
checkWorkspaceConfigPolicy,
|
|
22
22
|
} from '@smoothbricks/nx-plugin/workspace-config-policy';
|
|
23
23
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
ensureNx,
|
|
25
|
+
ensurePublishConfig,
|
|
26
|
+
ensureRepositoryObject,
|
|
27
|
+
ensureScripts,
|
|
28
|
+
type PackageExportMap,
|
|
29
|
+
type PackageExports,
|
|
30
|
+
type PackageJson,
|
|
27
31
|
readJsonObject,
|
|
28
|
-
recordProperty,
|
|
29
32
|
requiredJsonObject,
|
|
30
|
-
|
|
33
|
+
type StringMap,
|
|
34
|
+
setMissingPackageStringField,
|
|
35
|
+
setMissingRepositoryField,
|
|
36
|
+
setNxName,
|
|
37
|
+
setPublishAccess,
|
|
38
|
+
setRepositoryField,
|
|
31
39
|
setStringProperty,
|
|
32
|
-
stringProperty,
|
|
33
40
|
writeJsonObject,
|
|
34
41
|
} from '../lib/json.js';
|
|
35
42
|
import {
|
|
@@ -68,12 +75,12 @@ export function applyRootScriptDefaults(root: string): void {
|
|
|
68
75
|
if (!rootPackage) {
|
|
69
76
|
return;
|
|
70
77
|
}
|
|
71
|
-
const scripts =
|
|
78
|
+
const scripts = ensureScripts(rootPackage);
|
|
72
79
|
let changed = false;
|
|
73
80
|
for (const [name, command] of Object.entries(rootScriptPolicy)) {
|
|
74
81
|
changed = setStringProperty(scripts, name, command) || changed;
|
|
75
82
|
}
|
|
76
|
-
const nx =
|
|
83
|
+
const nx = ensureNx(rootPackage);
|
|
77
84
|
if (!Array.isArray(nx.includedScripts) || nx.includedScripts.length !== 0) {
|
|
78
85
|
nx.includedScripts = [];
|
|
79
86
|
changed = true;
|
|
@@ -97,7 +104,7 @@ export function applyNxPluginDefaults(root: string): void {
|
|
|
97
104
|
|
|
98
105
|
export function applyPublicPackageDefaults(root: string): void {
|
|
99
106
|
const rootPackage = requiredJsonObject(join(root, 'package.json'));
|
|
100
|
-
const rootLicense =
|
|
107
|
+
const rootLicense = rootPackage.license;
|
|
101
108
|
const rootRepository = repositoryInfo(rootPackage);
|
|
102
109
|
|
|
103
110
|
for (const pkg of listPublicPackages(root)) {
|
|
@@ -110,18 +117,21 @@ export function applyPublicPackageDefaults(root: string): void {
|
|
|
110
117
|
rootLicense &&
|
|
111
118
|
rootLicense !== 'UNLICENSED'
|
|
112
119
|
) {
|
|
113
|
-
changed =
|
|
120
|
+
changed = setMissingPackageStringField(pkg.json, 'license', rootLicense) || changed;
|
|
114
121
|
}
|
|
115
|
-
const publishConfig =
|
|
116
|
-
changed =
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
const publishConfig = ensurePublishConfig(pkg.json);
|
|
123
|
+
changed = setPublishAccess(publishConfig, 'public') || changed;
|
|
124
|
+
|
|
125
|
+
if (typeof pkg.json.repository !== 'string') {
|
|
126
|
+
const repository = ensureRepositoryObject(pkg.json);
|
|
127
|
+
changed =
|
|
128
|
+
setRepositoryField(repository, 'type', existingRepository?.type ?? rootRepository?.type ?? 'git') || changed;
|
|
129
|
+
if (existingRepository) {
|
|
130
|
+
changed = setMissingRepositoryField(repository, 'url', existingRepository.url) || changed;
|
|
131
|
+
}
|
|
132
|
+
changed = setRepositoryField(repository, 'directory', pkg.path.replaceAll('\\', '/')) || changed;
|
|
123
133
|
}
|
|
124
|
-
|
|
134
|
+
|
|
125
135
|
changed = normalizeExportConditionOrder(pkg.json.exports) || changed;
|
|
126
136
|
if (hasDevelopmentSourceExport(pkg.json.exports)) {
|
|
127
137
|
changed = addFileEntry(pkg.json, 'src') || changed;
|
|
@@ -172,7 +182,7 @@ export function applyNxReleaseDefaults(root: string): void {
|
|
|
172
182
|
|
|
173
183
|
export function applyNxProjectNameDefaults(root: string): void {
|
|
174
184
|
const rootPackage = requiredJsonObject(join(root, 'package.json'));
|
|
175
|
-
const rootName =
|
|
185
|
+
const rootName = rootPackage.name;
|
|
176
186
|
if (!rootName) {
|
|
177
187
|
return;
|
|
178
188
|
}
|
|
@@ -181,8 +191,8 @@ export function applyNxProjectNameDefaults(root: string): void {
|
|
|
181
191
|
if (!suggestedName) {
|
|
182
192
|
continue;
|
|
183
193
|
}
|
|
184
|
-
const nx =
|
|
185
|
-
const changed =
|
|
194
|
+
const nx = ensureNx(pkg.json);
|
|
195
|
+
const changed = setNxName(nx, suggestedName);
|
|
186
196
|
if (changed) {
|
|
187
197
|
writeJsonObject(pkg.packageJsonPath, pkg.json);
|
|
188
198
|
console.log(`updated ${pkg.path}/package.json nx.name`);
|
|
@@ -198,11 +208,10 @@ export function listValidCommitScopes(root: string): ReadonlySet<string> {
|
|
|
198
208
|
|
|
199
209
|
export function listNxProjectNames(root: string): string[] {
|
|
200
210
|
const rootPackage = readJsonObject(join(root, 'package.json'));
|
|
201
|
-
const rootName = rootPackage
|
|
211
|
+
const rootName = rootPackage?.name ?? null;
|
|
202
212
|
const names: string[] = [];
|
|
203
213
|
for (const pkg of getWorkspacePackageManifests(root)) {
|
|
204
|
-
const
|
|
205
|
-
const configuredName = nx ? stringProperty(nx, 'name') : null;
|
|
214
|
+
const configuredName = pkg.json.nx?.name;
|
|
206
215
|
const suggestedName = rootName ? suggestNxProjectName(rootName, pkg.name) : null;
|
|
207
216
|
if (configuredName) {
|
|
208
217
|
names.push(configuredName);
|
|
@@ -220,11 +229,11 @@ export function validateRootPackagePolicy(root: string): number {
|
|
|
220
229
|
return 1;
|
|
221
230
|
}
|
|
222
231
|
let failures = 0;
|
|
223
|
-
if (!
|
|
232
|
+
if (!rootPackage.name) {
|
|
224
233
|
console.error('package.json must define name');
|
|
225
234
|
failures++;
|
|
226
235
|
}
|
|
227
|
-
if (!
|
|
236
|
+
if (!rootPackage.license) {
|
|
228
237
|
console.error('package.json must define repo-wide license');
|
|
229
238
|
failures++;
|
|
230
239
|
}
|
|
@@ -234,19 +243,19 @@ export function validateRootPackagePolicy(root: string): number {
|
|
|
234
243
|
}
|
|
235
244
|
failures += validateRootScripts(rootPackage);
|
|
236
245
|
failures += validateRootNxScriptInference(rootPackage);
|
|
237
|
-
const packageManager =
|
|
246
|
+
const packageManager = rootPackage.packageManager;
|
|
238
247
|
if (!packageManager?.startsWith('bun@')) {
|
|
239
248
|
console.error('package.json packageManager must use bun@<version>');
|
|
240
249
|
failures++;
|
|
241
250
|
}
|
|
242
251
|
const bunVersion = packageManager?.startsWith('bun@') ? packageManager.slice('bun@'.length) : null;
|
|
243
|
-
const devDependencies =
|
|
252
|
+
const devDependencies = rootPackage.devDependencies;
|
|
244
253
|
if (!bunVersion || !devDependencies || devDependencies['@types/bun'] !== bunVersion) {
|
|
245
254
|
console.error('package.json devDependencies.@types/bun must match packageManager bun version');
|
|
246
255
|
failures++;
|
|
247
256
|
}
|
|
248
|
-
const engines =
|
|
249
|
-
if (!engines
|
|
257
|
+
const engines = rootPackage.engines;
|
|
258
|
+
if (!engines?.node) {
|
|
250
259
|
console.error('package.json engines.node must be defined');
|
|
251
260
|
failures++;
|
|
252
261
|
}
|
|
@@ -268,7 +277,7 @@ export function validateNxReleaseConfig(root: string): number {
|
|
|
268
277
|
|
|
269
278
|
export function validateNxProjectNames(root: string): number {
|
|
270
279
|
const rootPackage = readJsonObject(join(root, 'package.json'));
|
|
271
|
-
const rootName = rootPackage
|
|
280
|
+
const rootName = rootPackage?.name ?? null;
|
|
272
281
|
if (!rootName) {
|
|
273
282
|
return 0;
|
|
274
283
|
}
|
|
@@ -278,8 +287,7 @@ export function validateNxProjectNames(root: string): number {
|
|
|
278
287
|
if (!suggestedName) {
|
|
279
288
|
continue;
|
|
280
289
|
}
|
|
281
|
-
const
|
|
282
|
-
const configuredName = nx ? stringProperty(nx, 'name') : null;
|
|
290
|
+
const configuredName = pkg.json.nx?.name ?? null;
|
|
283
291
|
if (configuredName !== suggestedName) {
|
|
284
292
|
console.error(
|
|
285
293
|
`${pkg.path}: package.json nx.name must be "${suggestedName}" so fix(${suggestedName}): maps to this project`,
|
|
@@ -318,16 +326,19 @@ export function validatePublicPackageMetadata(root: string): number {
|
|
|
318
326
|
console.error(`${pkg.path}: npm:public package must not be private`);
|
|
319
327
|
failures++;
|
|
320
328
|
}
|
|
321
|
-
if (!
|
|
329
|
+
if (!pkg.json.license) {
|
|
322
330
|
console.error(`${pkg.path}: public package must define license`);
|
|
323
331
|
failures++;
|
|
324
332
|
}
|
|
325
|
-
const publishConfig =
|
|
326
|
-
if (
|
|
333
|
+
const publishConfig = pkg.json.publishConfig;
|
|
334
|
+
if (publishConfig?.access !== 'public') {
|
|
327
335
|
console.error(`${pkg.path}: public package must define publishConfig.access = public`);
|
|
328
336
|
failures++;
|
|
329
337
|
}
|
|
330
|
-
const repository =
|
|
338
|
+
const repository =
|
|
339
|
+
pkg.json.repository !== null && pkg.json.repository !== undefined && typeof pkg.json.repository !== 'string'
|
|
340
|
+
? pkg.json.repository
|
|
341
|
+
: null;
|
|
331
342
|
const packageRepository = packageRepositoryInfo(pkg);
|
|
332
343
|
if (!packageRepository) {
|
|
333
344
|
console.error(`${pkg.path}: public package must define repository.url`);
|
|
@@ -345,11 +356,11 @@ export function validatePublicPackageMetadata(root: string): number {
|
|
|
345
356
|
);
|
|
346
357
|
failures++;
|
|
347
358
|
}
|
|
348
|
-
if (!repository
|
|
359
|
+
if (!repository?.type) {
|
|
349
360
|
console.error(`${pkg.path}: public package must define repository.type`);
|
|
350
361
|
failures++;
|
|
351
362
|
}
|
|
352
|
-
if (!repository ||
|
|
363
|
+
if (!repository || repository.directory !== pkg.path.replaceAll('\\', '/')) {
|
|
353
364
|
console.error(`${pkg.path}: public package repository.directory must be ${pkg.path.replaceAll('\\', '/')}`);
|
|
354
365
|
failures++;
|
|
355
366
|
}
|
|
@@ -357,11 +368,14 @@ export function validatePublicPackageMetadata(root: string): number {
|
|
|
357
368
|
console.error(`${pkg.path}: public package must define files`);
|
|
358
369
|
failures++;
|
|
359
370
|
}
|
|
360
|
-
|
|
371
|
+
const hasBin =
|
|
372
|
+
typeof pkg.json.bin === 'string' ||
|
|
373
|
+
(pkg.json.bin !== null && pkg.json.bin !== undefined && typeof pkg.json.bin === 'object');
|
|
374
|
+
if (!isPackageExportMap(pkg.json.exports) && !hasBin) {
|
|
361
375
|
console.error(`${pkg.path}: public package must define exports or bin`);
|
|
362
376
|
failures++;
|
|
363
377
|
}
|
|
364
|
-
if (
|
|
378
|
+
if (typeof pkg.json.types !== 'string' && !hasBin) {
|
|
365
379
|
console.error(`${pkg.path}: public library package must define types`);
|
|
366
380
|
failures++;
|
|
367
381
|
}
|
|
@@ -375,7 +389,7 @@ export function validateWorkspaceDependencies(root: string, options: PackageTarg
|
|
|
375
389
|
const workspaceNames = new Set(getWorkspacePackages(root).map((pkg) => pkg.name));
|
|
376
390
|
for (const pkg of listPackageJsonRecords(root)) {
|
|
377
391
|
for (const field of workspaceDependencyFields) {
|
|
378
|
-
const dependencies =
|
|
392
|
+
const dependencies = pkg.json[field];
|
|
379
393
|
if (!dependencies) {
|
|
380
394
|
continue;
|
|
381
395
|
}
|
|
@@ -436,10 +450,10 @@ function validateCiSkipTags(root: string, options: PackageTargetPolicyOptions):
|
|
|
436
450
|
// Helpers kept in CLI (not Nx-specific)
|
|
437
451
|
// ---------------------------------------------------------------------------
|
|
438
452
|
|
|
439
|
-
function fixWorkspaceDependencyRanges(pkg:
|
|
453
|
+
function fixWorkspaceDependencyRanges(pkg: PackageJson, workspaceNames: Set<string>): boolean {
|
|
440
454
|
let changed = false;
|
|
441
455
|
for (const field of workspaceDependencyFields) {
|
|
442
|
-
const dependencies =
|
|
456
|
+
const dependencies = pkg[field];
|
|
443
457
|
if (!dependencies) {
|
|
444
458
|
continue;
|
|
445
459
|
}
|
|
@@ -453,8 +467,8 @@ function fixWorkspaceDependencyRanges(pkg: Record<string, unknown>, workspaceNam
|
|
|
453
467
|
return changed;
|
|
454
468
|
}
|
|
455
469
|
|
|
456
|
-
function validateRootScripts(rootPackage:
|
|
457
|
-
const scripts =
|
|
470
|
+
function validateRootScripts(rootPackage: PackageJson): number {
|
|
471
|
+
const scripts = rootPackage.scripts;
|
|
458
472
|
let failures = 0;
|
|
459
473
|
for (const [name, command] of Object.entries(rootScriptPolicy)) {
|
|
460
474
|
if (scripts?.[name] !== command) {
|
|
@@ -471,9 +485,9 @@ function validateRootScripts(rootPackage: Record<string, unknown>): number {
|
|
|
471
485
|
return failures;
|
|
472
486
|
}
|
|
473
487
|
|
|
474
|
-
function validateRootNxScriptInference(rootPackage:
|
|
475
|
-
const
|
|
476
|
-
if (
|
|
488
|
+
function validateRootNxScriptInference(rootPackage: PackageJson): number {
|
|
489
|
+
const includedScripts = rootPackage.nx?.includedScripts;
|
|
490
|
+
if (Array.isArray(includedScripts) && includedScripts.length === 0) {
|
|
477
491
|
return 0;
|
|
478
492
|
}
|
|
479
493
|
console.error('package.json nx.includedScripts must be [] so root scripts do not become recursive Nx targets.');
|
|
@@ -497,12 +511,12 @@ function unscopedPackageName(packageName: string): string {
|
|
|
497
511
|
return packageName.startsWith('@') ? packageName.slice(packageName.indexOf('/') + 1) : packageName;
|
|
498
512
|
}
|
|
499
513
|
|
|
500
|
-
function recordKeysAreSorted(record:
|
|
514
|
+
function recordKeysAreSorted(record: StringMap): boolean {
|
|
501
515
|
const keys = Object.keys(record);
|
|
502
|
-
return keys.every((key, index) => index === 0 || keys[index - 1] <= key);
|
|
516
|
+
return keys.every((key, index) => index === 0 || (keys[index - 1] ?? '') <= key);
|
|
503
517
|
}
|
|
504
518
|
|
|
505
|
-
function sortRecordInPlace(record:
|
|
519
|
+
function sortRecordInPlace(record: StringMap): boolean {
|
|
506
520
|
if (recordKeysAreSorted(record)) {
|
|
507
521
|
return false;
|
|
508
522
|
}
|
|
@@ -516,8 +530,11 @@ function sortRecordInPlace(record: Record<string, unknown>): boolean {
|
|
|
516
530
|
return true;
|
|
517
531
|
}
|
|
518
532
|
|
|
519
|
-
function
|
|
520
|
-
|
|
533
|
+
function isPackageExportMap(value: PackageExports): value is PackageExportMap {
|
|
534
|
+
return value !== null && value !== undefined && typeof value === 'object';
|
|
535
|
+
}
|
|
536
|
+
function normalizeExportConditionOrder(value: PackageExports): boolean {
|
|
537
|
+
if (!isPackageExportMap(value)) {
|
|
521
538
|
return false;
|
|
522
539
|
}
|
|
523
540
|
let changed = false;
|
|
@@ -546,8 +563,8 @@ function normalizeExportConditionOrder(value: unknown): boolean {
|
|
|
546
563
|
return true;
|
|
547
564
|
}
|
|
548
565
|
|
|
549
|
-
function hasDevelopmentSourceExport(value:
|
|
550
|
-
if (!
|
|
566
|
+
function hasDevelopmentSourceExport(value: PackageExports): boolean {
|
|
567
|
+
if (!isPackageExportMap(value)) {
|
|
551
568
|
return false;
|
|
552
569
|
}
|
|
553
570
|
for (const [key, child] of Object.entries(value)) {
|
|
@@ -561,12 +578,12 @@ function hasDevelopmentSourceExport(value: unknown): boolean {
|
|
|
561
578
|
return false;
|
|
562
579
|
}
|
|
563
580
|
|
|
564
|
-
function addFileEntry(pkg:
|
|
581
|
+
function addFileEntry(pkg: PackageJson, entry: string): boolean {
|
|
565
582
|
const files = pkg.files;
|
|
566
583
|
if (!Array.isArray(files) || files.includes(entry)) {
|
|
567
584
|
return false;
|
|
568
585
|
}
|
|
569
|
-
const firstNegated = files.findIndex((file) =>
|
|
586
|
+
const firstNegated = files.findIndex((file) => file.startsWith('!'));
|
|
570
587
|
if (firstNegated === -1) {
|
|
571
588
|
files.push(entry);
|
|
572
589
|
} else {
|
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
import { execSync } from 'node:child_process';
|
|
2
1
|
import { $ } from 'bun';
|
|
3
|
-
import {
|
|
2
|
+
import { type PackageJson, parsePackageJsonText } from '../lib/json.js';
|
|
4
3
|
import { decode } from '../lib/run.js';
|
|
5
4
|
import type { PackageInfo } from '../lib/workspace.js';
|
|
6
5
|
import { getWorkspacePackages, workspaceDependencyFields } from '../lib/workspace.js';
|
|
6
|
+
import { publishPackDependencyVersion } from './lockfile.js';
|
|
7
7
|
|
|
8
|
-
export async function readPackedPackageJson(
|
|
9
|
-
root: string,
|
|
10
|
-
tarball: string,
|
|
11
|
-
packageName: string,
|
|
12
|
-
): Promise<Record<string, unknown>> {
|
|
8
|
+
export async function readPackedPackageJson(root: string, tarball: string, packageName: string): Promise<PackageJson> {
|
|
13
9
|
const result = await $`tar -xOf ${tarball} package/package.json`.cwd(root).quiet().nothrow();
|
|
14
10
|
if (result.exitCode !== 0) {
|
|
15
11
|
throw new Error(`${packageName}: unable to inspect packed package.json.`);
|
|
16
12
|
}
|
|
17
|
-
const parsed =
|
|
18
|
-
if (!
|
|
19
|
-
throw new Error(`${packageName}: packed package.json is
|
|
13
|
+
const parsed = parsePackageJsonText(decode(result.stdout));
|
|
14
|
+
if (!parsed) {
|
|
15
|
+
throw new Error(`${packageName}: packed package.json is invalid.`);
|
|
20
16
|
}
|
|
21
17
|
return parsed;
|
|
22
18
|
}
|
|
@@ -24,21 +20,21 @@ export async function readPackedPackageJson(
|
|
|
24
20
|
export function validatePackedWorkspaceDependencies(
|
|
25
21
|
root: string,
|
|
26
22
|
sourcePackage: PackageInfo,
|
|
27
|
-
packedPackage:
|
|
23
|
+
packedPackage: PackageJson,
|
|
24
|
+
options: { mode?: 'install' | 'publish' } = {},
|
|
28
25
|
): string[] {
|
|
26
|
+
const mode = options.mode ?? 'publish';
|
|
29
27
|
const workspacePackages = getWorkspacePackages(root);
|
|
30
|
-
const workspaceVersions = new Map(workspacePackages.map((pkg) => [pkg.name, pkg.version]));
|
|
31
|
-
const projectNameByPackage = new Map(workspacePackages.map((pkg) => [pkg.name, pkg.projectName]));
|
|
32
28
|
const failures: string[] = [];
|
|
33
29
|
for (const field of workspaceDependencyFields) {
|
|
34
|
-
const sourceDependencies =
|
|
35
|
-
const packedDependencies =
|
|
30
|
+
const sourceDependencies = sourcePackage.json[field];
|
|
31
|
+
const packedDependencies = packedPackage[field];
|
|
36
32
|
if (!sourceDependencies && !packedDependencies) {
|
|
37
33
|
continue;
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
for (const [name, range] of Object.entries(packedDependencies ?? {})) {
|
|
41
|
-
if (
|
|
37
|
+
if (range.startsWith('workspace:')) {
|
|
42
38
|
failures.push(`${sourcePackage.path}: packed ${field}.${name} must not contain ${range}`);
|
|
43
39
|
}
|
|
44
40
|
}
|
|
@@ -47,54 +43,24 @@ export function validatePackedWorkspaceDependencies(
|
|
|
47
43
|
continue;
|
|
48
44
|
}
|
|
49
45
|
for (const [name, sourceRange] of Object.entries(sourceDependencies)) {
|
|
50
|
-
const
|
|
51
|
-
if (!
|
|
46
|
+
const dep = workspacePackages.find((pkg) => pkg.name === name);
|
|
47
|
+
if (!dep) {
|
|
52
48
|
continue;
|
|
53
49
|
}
|
|
54
50
|
const packedRange = packedDependencies?.[name];
|
|
55
51
|
if (sourceRange !== 'workspace:*') {
|
|
56
52
|
failures.push(`${sourcePackage.path}: source ${field}.${name} must use workspace:*`);
|
|
57
53
|
}
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
// version. Accept that version as valid.
|
|
61
|
-
const projectName = projectNameByPackage.get(name);
|
|
54
|
+
// install: what day-to-day pack embeds (package.json / lockfile aligned)
|
|
55
|
+
// publish: what pre-publish sync embeds (unpublished -next → last stable tag)
|
|
62
56
|
const expectedVersion =
|
|
63
|
-
|
|
64
|
-
? (latestStableTagVersion(root, projectName) ?? workspaceVersion)
|
|
65
|
-
: workspaceVersion;
|
|
57
|
+
mode === 'publish' ? publishPackDependencyVersion(root, dep.projectName, dep.version) : dep.version;
|
|
66
58
|
if (packedRange !== expectedVersion) {
|
|
67
59
|
failures.push(
|
|
68
|
-
`${sourcePackage.path}: packed ${field}.${name} must be ${expectedVersion}, got ${
|
|
60
|
+
`${sourcePackage.path}: packed ${field}.${name} must be ${expectedVersion}, got ${packedRange ?? '<missing>'}`,
|
|
69
61
|
);
|
|
70
62
|
}
|
|
71
63
|
}
|
|
72
64
|
}
|
|
73
65
|
return failures;
|
|
74
66
|
}
|
|
75
|
-
|
|
76
|
-
function formatRange(value: unknown): string {
|
|
77
|
-
return typeof value === 'string' ? value : '<missing>';
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function latestStableTagVersion(root: string, projectName: string): string | null {
|
|
81
|
-
try {
|
|
82
|
-
const output = execSync(`git tag --list '${projectName}@*' --sort=-v:refname`, {
|
|
83
|
-
cwd: root,
|
|
84
|
-
encoding: 'utf8',
|
|
85
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
86
|
-
});
|
|
87
|
-
const prefix = `${projectName}@`;
|
|
88
|
-
for (const line of output.split('\n')) {
|
|
89
|
-
const tag = line.trim();
|
|
90
|
-
if (!tag.startsWith(prefix)) continue;
|
|
91
|
-
const version = tag.slice(prefix.length);
|
|
92
|
-
if (version && !version.includes('-')) {
|
|
93
|
-
return version;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return null;
|
|
97
|
-
} catch {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
@@ -4,7 +4,9 @@ import { dirname, join, relative } from 'node:path';
|
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
5
|
import { publint } from 'publint';
|
|
6
6
|
import { formatMessage } from 'publint/utils';
|
|
7
|
-
import
|
|
7
|
+
import typia from 'typia';
|
|
8
|
+
import type { PackageExports, PackageJson } from '../lib/json.js';
|
|
9
|
+
import { parsePackageJsonText } from '../lib/json.js';
|
|
8
10
|
import { printCommandOutput, runResult } from '../lib/run.js';
|
|
9
11
|
import type { PackageInfo } from '../lib/workspace.js';
|
|
10
12
|
import { listPublicPackages } from '../lib/workspace.js';
|
|
@@ -80,7 +82,7 @@ async function validatePackedManifest(
|
|
|
80
82
|
): Promise<number> {
|
|
81
83
|
let failures = 0;
|
|
82
84
|
const packedPackage = await readPackedPackageJson(root, packed.path, pkg.name);
|
|
83
|
-
for (const message of validatePackedWorkspaceDependencies(root, pkg, packedPackage)) {
|
|
85
|
+
for (const message of validatePackedWorkspaceDependencies(root, pkg, packedPackage, { mode: 'install' })) {
|
|
84
86
|
console.error(message);
|
|
85
87
|
failures++;
|
|
86
88
|
}
|
|
@@ -92,11 +94,10 @@ async function validateAttw(root: string, pkg: PackageInfo, packed: { path: stri
|
|
|
92
94
|
const analysis = await attw.checkPackage(await createAttwPackageFromTarball(attw, root, packed.path), {
|
|
93
95
|
excludeEntrypoints: nonJsExportEntrypoints(pkg.json.exports),
|
|
94
96
|
});
|
|
95
|
-
if (!
|
|
97
|
+
if (!isAttwAnalysis(analysis) || analysis.types === false) {
|
|
96
98
|
return 0;
|
|
97
99
|
}
|
|
98
|
-
const
|
|
99
|
-
const problems = rawProblems.filter(isReportedAttwProblem);
|
|
100
|
+
const problems = (analysis.problems ?? []).filter(isReportedAttwProblem);
|
|
100
101
|
if (problems.length === 0) {
|
|
101
102
|
return 0;
|
|
102
103
|
}
|
|
@@ -112,10 +113,30 @@ interface AttwCore {
|
|
|
112
113
|
checkPackage: (pkg: unknown, options: { excludeEntrypoints: string[] }) => Promise<unknown>;
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
interface AttwAnalysis {
|
|
117
|
+
types?: unknown;
|
|
118
|
+
problems?: unknown[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
interface AttwProblem {
|
|
122
|
+
kind: string;
|
|
123
|
+
resolutionKind?: string;
|
|
124
|
+
entrypoint?: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface AttwCoreExport {
|
|
128
|
+
Package?: unknown;
|
|
129
|
+
checkPackage?: unknown;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const isAttwAnalysis = typia.createIs<AttwAnalysis>();
|
|
133
|
+
const isAttwProblem = typia.createIs<AttwProblem>();
|
|
134
|
+
const isAttwCoreExport = typia.createIs<AttwCoreExport>();
|
|
135
|
+
|
|
115
136
|
async function loadAttwCore(): Promise<AttwCore> {
|
|
116
137
|
const packageJson = fileURLToPath(import.meta.resolve('@arethetypeswrong/core/package.json'));
|
|
117
|
-
const core = await import(pathToFileURL(join(dirname(packageJson), 'dist', 'index.js')).href);
|
|
118
|
-
if (!
|
|
138
|
+
const core: unknown = await import(pathToFileURL(join(dirname(packageJson), 'dist', 'index.js')).href);
|
|
139
|
+
if (!isAttwCoreExport(core)) {
|
|
119
140
|
throw new Error('@arethetypeswrong/core does not expose the expected API');
|
|
120
141
|
}
|
|
121
142
|
const PackageConstructor = core.Package;
|
|
@@ -146,8 +167,8 @@ async function createAttwPackageFromTarball(attw: AttwCore, root: string, tarbal
|
|
|
146
167
|
|
|
147
168
|
function createAttwPackageFromDirectory(attw: AttwCore, packageDir: string): unknown {
|
|
148
169
|
const packageJson = readJsonFile(join(packageDir, 'package.json'));
|
|
149
|
-
const packageName =
|
|
150
|
-
const packageVersion =
|
|
170
|
+
const packageName = packageJson.name;
|
|
171
|
+
const packageVersion = packageJson.version;
|
|
151
172
|
if (!packageName || !packageVersion) {
|
|
152
173
|
throw new Error('packed package.json must contain name and version');
|
|
153
174
|
}
|
|
@@ -172,16 +193,16 @@ function collectAttwFiles(root: string, current: string, packageName: string, fi
|
|
|
172
193
|
}
|
|
173
194
|
}
|
|
174
195
|
|
|
175
|
-
function readJsonFile(path: string):
|
|
176
|
-
const parsed
|
|
177
|
-
if (!
|
|
196
|
+
function readJsonFile(path: string): PackageJson {
|
|
197
|
+
const parsed = parsePackageJsonText(readFileSync(path, 'utf8'));
|
|
198
|
+
if (!parsed) {
|
|
178
199
|
throw new Error(`${path} is not a JSON object`);
|
|
179
200
|
}
|
|
180
201
|
return parsed;
|
|
181
202
|
}
|
|
182
203
|
|
|
183
|
-
function isReportedAttwProblem(problem: unknown):
|
|
184
|
-
if (!
|
|
204
|
+
function isReportedAttwProblem(problem: unknown): problem is AttwProblem {
|
|
205
|
+
if (!isAttwProblem(problem)) {
|
|
185
206
|
return false;
|
|
186
207
|
}
|
|
187
208
|
const flag = attwProblemFlag(problem.kind);
|
|
@@ -222,10 +243,7 @@ function attwProblemFlag(kind: string): string | null {
|
|
|
222
243
|
}
|
|
223
244
|
}
|
|
224
245
|
|
|
225
|
-
function formatProblemLocation(problem:
|
|
226
|
-
if (!isRecord(problem)) {
|
|
227
|
-
return '';
|
|
228
|
-
}
|
|
246
|
+
function formatProblemLocation(problem: AttwProblem): string {
|
|
229
247
|
const entrypoint = typeof problem.entrypoint === 'string' ? ` ${problem.entrypoint}` : '';
|
|
230
248
|
const resolution =
|
|
231
249
|
typeof problem.resolutionKind === 'string' ? ` ${formatResolutionKind(problem.resolutionKind)}` : '';
|
|
@@ -242,8 +260,8 @@ function formatResolutionKind(kind: string): string {
|
|
|
242
260
|
return kind;
|
|
243
261
|
}
|
|
244
262
|
|
|
245
|
-
function nonJsExportEntrypoints(exports:
|
|
246
|
-
if (
|
|
263
|
+
function nonJsExportEntrypoints(exports: PackageExports): string[] {
|
|
264
|
+
if (exports === null || exports === undefined || typeof exports === 'string') {
|
|
247
265
|
return [];
|
|
248
266
|
}
|
|
249
267
|
return Object.entries(exports)
|
|
@@ -251,13 +269,16 @@ function nonJsExportEntrypoints(exports: unknown): string[] {
|
|
|
251
269
|
.map(([key]) => key);
|
|
252
270
|
}
|
|
253
271
|
|
|
254
|
-
function exportPointsToNonJs(value:
|
|
272
|
+
function exportPointsToNonJs(value: PackageExports): boolean {
|
|
255
273
|
if (typeof value === 'string') {
|
|
256
274
|
// attw resolves every entrypoint as a module; assets can never have types.
|
|
257
275
|
// Existence of the target files is still validated by publint.
|
|
258
276
|
return value.endsWith('.wasm') || value.endsWith('.css');
|
|
259
277
|
}
|
|
260
|
-
|
|
278
|
+
if (value === null || value === undefined) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
return Object.values(value).some(exportPointsToNonJs);
|
|
261
282
|
}
|
|
262
283
|
|
|
263
284
|
async function packPackage(root: string, pkg: PackageInfo): Promise<{ path: string; arrayBuffer: ArrayBuffer }> {
|
|
@@ -3,7 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { printCommandOutput, runResult, runStatus } from '../../lib/run.js';
|
|
4
4
|
import { type ProjectTargets, readProjectTargets } from '../../nx/index.js';
|
|
5
5
|
import { syncBunLockfileVersions, validateBunLockfileVersions } from '../lockfile.js';
|
|
6
|
-
import {
|
|
6
|
+
import { warnOnManagedFileDrift } from '../managed-files.js';
|
|
7
7
|
import { fixNxSync, validateNxSync } from '../nx-sync.js';
|
|
8
8
|
import { fixPackageHygiene, validatePackageHygiene } from '../package-hygiene.js';
|
|
9
9
|
import {
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
validatePackedPublicPackagePublint,
|
|
25
25
|
validatePackedPublicPackageTypes,
|
|
26
26
|
} from '../packed-package.js';
|
|
27
|
-
import { syncRootRuntimeVersions } from '../runtime.js';
|
|
27
|
+
import { syncRootRuntimeVersions, validateRootRuntimeVersions } from '../runtime.js';
|
|
28
28
|
import { applyToolConfigDefaults, validateToolConfig } from '../tool-validation.js';
|
|
29
29
|
import { applyWranglerDefaults, validateWrangler } from '../wrangler.js';
|
|
30
30
|
|
|
@@ -79,13 +79,22 @@ const packs: MonorepoPack[] = [
|
|
|
79
79
|
async fixPreBuild(ctx) {
|
|
80
80
|
applyFixableMonorepoDefaults(ctx.root);
|
|
81
81
|
await applyToolConfigDefaults(ctx.root);
|
|
82
|
-
|
|
82
|
+
// Install/CI alignment only — do not rewrite -next to stable tags here.
|
|
83
|
+
syncBunLockfileVersions(ctx.root, { mode: 'install' });
|
|
83
84
|
await fixNxSync(ctx.root, ctx.verbose === true);
|
|
84
85
|
applyWorkspaceDependencyDefaults(ctx.root, { resolvedTargetsByProject: await readResolvedTargetsByProject(ctx) });
|
|
85
86
|
},
|
|
86
87
|
async validatePreBuild(ctx) {
|
|
88
|
+
// Runtime pins validate against the live PATH runtimes (devenv shell),
|
|
89
|
+
// never a stored template — outside devenv the PATH is not authoritative,
|
|
90
|
+
// mirroring the init-time syncRuntime gate above.
|
|
91
|
+
const runtimeFailures = ctx.syncRuntime ? await validateRootRuntimeVersions(ctx.root) : 0;
|
|
92
|
+
// Managed-file drift is derived state (CLI template x pinned version) with
|
|
93
|
+
// its own remediation flow; it warns instead of failing so validation only
|
|
94
|
+
// blocks on actual package issues. See warnOnManagedFileDrift.
|
|
95
|
+
warnOnManagedFileDrift(ctx.root);
|
|
87
96
|
return (
|
|
88
|
-
|
|
97
|
+
runtimeFailures +
|
|
89
98
|
validateRootPackagePolicy(ctx.root) +
|
|
90
99
|
validateToolConfig(ctx.root) +
|
|
91
100
|
validateNxProjectNames(ctx.root) +
|
|
@@ -420,7 +420,9 @@ function yamlLinesForStep(step: PublishWorkflowStep, options: PublishWorkflowDef
|
|
|
420
420
|
' "$GITHUB_OUTPUT"',
|
|
421
421
|
];
|
|
422
422
|
case PublishWorkflowStepKind.CheckManagedMonorepoFiles:
|
|
423
|
-
|
|
423
|
+
// Drift is derived state with its own remediation PR; publishing only
|
|
424
|
+
// blocks on actual package issues (smoo monorepo validate).
|
|
425
|
+
return conditionalRunStep(step, 'smoo monorepo check --warn');
|
|
424
426
|
case PublishWorkflowStepKind.Build:
|
|
425
427
|
return conditionalRunStep(
|
|
426
428
|
step,
|