@smoothbricks/cli 0.10.2 → 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 +31 -23
- 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 +36 -4
- package/dist/generate/index.d.ts.map +1 -1
- package/dist/generate/index.js +0 -6
- package/dist/github-ci/index.d.ts +17 -2
- package/dist/github-ci/index.d.ts.map +1 -1
- package/dist/github-ci/index.js +192 -26
- package/dist/github-ci/outputs.d.ts +22 -0
- package/dist/github-ci/outputs.d.ts.map +1 -0
- package/dist/github-ci/outputs.js +648 -0
- 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 +28 -2
- 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 +12 -0
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +86 -28
- package/dist/monorepo/package-policy.d.ts.map +1 -1
- package/dist/monorepo/package-policy.js +78 -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 +3 -0
- package/dist/monorepo/packs/index.d.ts.map +1 -1
- package/dist/monorepo/packs/index.js +18 -6
- package/dist/monorepo/publish-workflow.d.ts +1 -0
- package/dist/monorepo/publish-workflow.d.ts.map +1 -1
- package/dist/monorepo/publish-workflow.js +242 -3
- 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 +12 -4
- package/dist/nx/index.d.ts.map +1 -1
- package/dist/nx/index.js +86 -19
- 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/github-release.d.ts +3 -0
- package/dist/release/github-release.d.ts.map +1 -1
- package/dist/release/github-release.js +11 -0
- package/dist/release/index.d.ts +20 -1
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +221 -89
- package/dist/release/orchestration.d.ts +8 -1
- package/dist/release/orchestration.d.ts.map +1 -1
- package/dist/release/orchestration.js +38 -1
- 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 +6 -0
- package/managed/raw/tooling/direnv/merge-newer-pins.sh +3 -3
- package/managed/raw/tooling/direnv/setup-environment.ts +187 -19
- package/managed/raw/tooling/git-hooks/pre-commit.sh +0 -6
- package/managed/templates/github/actions/cache-nix-devenv/action.yml +12 -10
- package/managed/templates/github/actions/cache-node-modules/action.yml +6 -2
- package/managed/templates/github/actions/cache-nx/action.yml +2 -2
- package/managed/templates/github/actions/cache-ttsc-plugins/action.yml +19 -0
- package/managed/templates/github/actions/save-nix-devenv/action.yml +11 -3
- package/managed/templates/github/actions/setup-devenv/action.yml +5 -0
- package/managed/templates/github/workflows/managed-files.yml +80 -0
- package/package.json +18 -4
- package/src/bun/preload.ts +12 -0
- package/src/cli.ts +49 -11
- package/src/generate/index.ts +0 -6
- package/src/github-ci/index.test.ts +486 -0
- package/src/github-ci/index.ts +220 -30
- package/src/github-ci/outputs.ts +505 -0
- 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 +14 -3
- package/src/monorepo/__tests__/publish-workflow.test.ts +222 -1
- package/src/monorepo/ci-workflow.ts +28 -2
- 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 +63 -0
- package/src/monorepo/managed-files.ts +94 -34
- package/src/monorepo/package-policy.test.ts +70 -51
- package/src/monorepo/package-policy.ts +104 -59
- package/src/monorepo/packed-manifest.ts +18 -52
- package/src/monorepo/packed-package.ts +43 -22
- package/src/monorepo/packs/index.test.ts +18 -1
- package/src/monorepo/packs/index.ts +20 -7
- package/src/monorepo/publish-workflow.ts +482 -3
- 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 +43 -0
- package/src/nx/index.ts +110 -23
- package/src/pr/index.ts +42 -36
- package/src/release/__tests__/candidates.test.ts +5 -4
- package/src/release/__tests__/fixture-repo.test.ts +18 -16
- package/src/release/__tests__/github-release.test.ts +11 -0
- package/src/release/__tests__/orchestration.test.ts +57 -2
- package/src/release/__tests__/trust-publisher.test.ts +98 -2
- package/src/release/candidates.ts +10 -17
- package/src/release/github-release.ts +12 -0
- package/src/release/index.ts +321 -112
- package/src/release/orchestration.ts +55 -3
- package/src/wrangler/prepare-env.ts +75 -54
- package/src/wrangler/scaffold.test.ts +9 -12
- package/src/wrangler/scaffold.ts +13 -13
package/src/monorepo/runtime.ts
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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 =
|
|
43
|
+
const engines = ensureEngines(packageJson);
|
|
23
44
|
changed = setStringProperty(engines, 'node', `>=${nodeMajor}.0.0`) || changed;
|
|
24
|
-
changed =
|
|
25
|
-
const 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 =
|
|
55
|
-
if (!
|
|
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
|
-
|
|
75
|
-
|
|
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;
|
|
@@ -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,11 @@ export interface ToolContext {
|
|
|
23
30
|
policy: ToolPolicy;
|
|
24
31
|
}
|
|
25
32
|
|
|
33
|
+
interface RegistryPackument {
|
|
34
|
+
versions: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const isRegistryPackument = typia.createIs<RegistryPackument>();
|
|
26
38
|
const rootDevDependencies: RequiredDependency[] = [
|
|
27
39
|
{ name: '@biomejs/biome', fallbackVersion: '^2.3.5', minimumVersion: '2.3.0', prefix: '^' },
|
|
28
40
|
{ name: '@nx/js', fallbackVersion: '23.1.0', minimumVersion: '23.1.0' },
|
|
@@ -44,7 +56,7 @@ const rootDevDependencies: RequiredDependency[] = [
|
|
|
44
56
|
{ name: 'nx', fallbackVersion: '23.1.0', minimumVersion: '23.1.0' },
|
|
45
57
|
{ name: 'prettier', fallbackVersion: '^3.6.1', minimumVersion: '3.6.0', prefix: '^' },
|
|
46
58
|
{ name: 'ttsc', fallbackVersion: '^0.18.4', minimumVersion: '0.18.4', prefix: '^' },
|
|
47
|
-
// Nx and typescript-eslint still load the TypeScript
|
|
59
|
+
// Nx and typescript-eslint still load the TypeScript JS API (6.x).
|
|
48
60
|
// Compilation is exclusively delegated to ttsc by the Nx plugin targets.
|
|
49
61
|
{ name: 'typescript', fallbackVersion: '^5.9.3', minimumVersion: '5.9.0', prefix: '^' },
|
|
50
62
|
];
|
|
@@ -52,7 +64,11 @@ const rootDevDependencies: RequiredDependency[] = [
|
|
|
52
64
|
const cliPackageName = '@smoothbricks/cli';
|
|
53
65
|
|
|
54
66
|
const requiredDevenvPackages = ['bun', 'git', 'git-format-staged', 'jq', 'alejandra', 'coreutils', 'gnutar'];
|
|
55
|
-
|
|
67
|
+
// Any explicit nodejs provider is fine — the pinned major is the repo's choice
|
|
68
|
+
// (Lambda parity, org convergence, …). Whether the pins in package.json agree
|
|
69
|
+
// with the runtime is validated against the live PATH (validateRootRuntimeVersions),
|
|
70
|
+
// never against a version template here.
|
|
71
|
+
const nodePackagePattern = /(^|\s)nodejs(_\d+|_latest)?(\s|#|$)/m;
|
|
56
72
|
|
|
57
73
|
export async function applyToolConfigDefaults(root: string): Promise<void> {
|
|
58
74
|
const context = readToolContext(root);
|
|
@@ -77,7 +93,7 @@ export async function applyRootDevDependencyDefaults(root: string, context: Tool
|
|
|
77
93
|
return;
|
|
78
94
|
}
|
|
79
95
|
let changed = false;
|
|
80
|
-
const devDependencies =
|
|
96
|
+
const devDependencies = ensureDependencyMap(pkg, 'devDependencies');
|
|
81
97
|
for (const dependency of rootDevDependencies) {
|
|
82
98
|
const current = devDependencies[dependency.name];
|
|
83
99
|
if (typeof current !== 'string' || !satisfiesDependencyPolicy(context.policy, current, dependency)) {
|
|
@@ -103,7 +119,7 @@ async function applyRootPackageToolDefaults(root: string, context: ToolContext):
|
|
|
103
119
|
}
|
|
104
120
|
let dependencyChanged = false;
|
|
105
121
|
let workspaceChanged = false;
|
|
106
|
-
const devDependencies =
|
|
122
|
+
const devDependencies = ensureDependencyMap(pkg, 'devDependencies');
|
|
107
123
|
for (const dependency of rootDevDependencies) {
|
|
108
124
|
const current = devDependencies[dependency.name];
|
|
109
125
|
if (typeof current !== 'string' || !satisfiesDependencyPolicy(context.policy, current, dependency)) {
|
|
@@ -134,12 +150,12 @@ export function applyToolingPackageDefaults(root: string, policy: ToolPolicy): v
|
|
|
134
150
|
const path = join(root, 'tooling', 'package.json');
|
|
135
151
|
const pkg = readJsonObject(path) ?? { name: policy.toolingPackageName, private: true, dependencies: {} };
|
|
136
152
|
let changed = false;
|
|
137
|
-
changed =
|
|
153
|
+
changed = setPackageStringField(pkg, 'name', policy.toolingPackageName) || changed;
|
|
138
154
|
if (pkg.private !== true) {
|
|
139
155
|
pkg.private = true;
|
|
140
156
|
changed = true;
|
|
141
157
|
}
|
|
142
|
-
const dependencies =
|
|
158
|
+
const dependencies = ensureDependencyMap(pkg, 'dependencies');
|
|
143
159
|
changed = setStringProperty(dependencies, cliPackageName, policy.cliDependencyRange) || changed;
|
|
144
160
|
if (changed || !existsSync(path)) {
|
|
145
161
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -171,7 +187,7 @@ export function applyDevenvPackageDefaults(root: string): void {
|
|
|
171
187
|
}
|
|
172
188
|
let content = readFileSync(path, 'utf8');
|
|
173
189
|
let changed = false;
|
|
174
|
-
if (!
|
|
190
|
+
if (!nodePackagePattern.test(content)) {
|
|
175
191
|
const next = addNixPackage(content, 'nodejs_latest', '# Node.js for workspace tooling');
|
|
176
192
|
changed = next !== content || changed;
|
|
177
193
|
content = next;
|
|
@@ -198,7 +214,7 @@ export function validateRootDevDependencies(policy: ToolPolicy, rootPackage: Pac
|
|
|
198
214
|
console.error('package.json not found or invalid');
|
|
199
215
|
return 1;
|
|
200
216
|
}
|
|
201
|
-
const devDependencies =
|
|
217
|
+
const devDependencies = pkg.devDependencies;
|
|
202
218
|
let failures = 0;
|
|
203
219
|
for (const dependency of rootDevDependencies) {
|
|
204
220
|
const version = devDependencies?.[dependency.name];
|
|
@@ -226,9 +242,9 @@ export function validateToolingPackage(root: string, policy: ToolPolicy): number
|
|
|
226
242
|
console.error('tooling/package.json not found or invalid');
|
|
227
243
|
return 1;
|
|
228
244
|
}
|
|
229
|
-
const dependencies =
|
|
245
|
+
const dependencies = pkg.dependencies;
|
|
230
246
|
let failures = 0;
|
|
231
|
-
const actualName =
|
|
247
|
+
const actualName = pkg.name ?? null;
|
|
232
248
|
if (actualName !== policy.toolingPackageName) {
|
|
233
249
|
console.error(`tooling/package.json name must be ${policy.toolingPackageName}`);
|
|
234
250
|
failures++;
|
|
@@ -262,8 +278,10 @@ export function validateDevenvPackages(root: string): number {
|
|
|
262
278
|
}
|
|
263
279
|
const content = readFileSync(path, 'utf8');
|
|
264
280
|
let failures = 0;
|
|
265
|
-
if (!
|
|
266
|
-
console.error(
|
|
281
|
+
if (!nodePackagePattern.test(content)) {
|
|
282
|
+
console.error(
|
|
283
|
+
'tooling/direnv/devenv.nix packages must include a nodejs provider (nodejs_<major> or nodejs_latest)',
|
|
284
|
+
);
|
|
267
285
|
failures++;
|
|
268
286
|
}
|
|
269
287
|
for (const name of requiredDevenvPackages) {
|
|
@@ -334,7 +352,7 @@ function formatExpectedDependency(policy: ToolPolicy, dependency: RequiredDepend
|
|
|
334
352
|
export function readToolContext(root: string): ToolContext {
|
|
335
353
|
const rootPackage = readPackageJsonObject(join(root, 'package.json'));
|
|
336
354
|
const toolingPackage = readJsonObject(join(root, 'tooling', 'package.json'));
|
|
337
|
-
const configuredCliRange =
|
|
355
|
+
const configuredCliRange = toolingPackage?.dependencies?.[cliPackageName];
|
|
338
356
|
return {
|
|
339
357
|
rootPackage,
|
|
340
358
|
policy: toolPolicy(rootPackage, typeof configuredCliRange === 'string' ? configuredCliRange : null),
|
|
@@ -342,7 +360,7 @@ export function readToolContext(root: string): ToolContext {
|
|
|
342
360
|
}
|
|
343
361
|
|
|
344
362
|
function toolPolicy(rootPackage: PackageJson | null, configuredCliRange: string | null): ToolPolicy {
|
|
345
|
-
const name =
|
|
363
|
+
const name = rootPackage?.name ?? null;
|
|
346
364
|
const isCodebase = isSmoothBricksCodebasePackageName(name ?? undefined);
|
|
347
365
|
const toolingName = toolingPackageName(name);
|
|
348
366
|
// A locally linked prerelease can be newer than every published CLI. Keep an existing
|
|
@@ -435,14 +453,6 @@ function compareVersions(left: Version, right: Version): number {
|
|
|
435
453
|
return left.major - right.major || left.minor - right.minor || left.patch - right.patch;
|
|
436
454
|
}
|
|
437
455
|
|
|
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
456
|
function stripRangePrefix(version: string): string {
|
|
447
457
|
return version.replace(/^[~^]/, '');
|
|
448
458
|
}
|
|
@@ -456,7 +466,7 @@ function addWorkspacePattern(pkg: PackageJson, pattern: string): boolean {
|
|
|
456
466
|
workspaces.push(pattern);
|
|
457
467
|
return true;
|
|
458
468
|
}
|
|
459
|
-
if (
|
|
469
|
+
if (workspaces && Array.isArray(workspaces.packages)) {
|
|
460
470
|
if (workspaces.packages.includes(pattern)) {
|
|
461
471
|
return false;
|
|
462
472
|
}
|
|
@@ -472,5 +482,5 @@ function hasWorkspacePattern(pkg: PackageJson, pattern: string): boolean {
|
|
|
472
482
|
if (Array.isArray(workspaces)) {
|
|
473
483
|
return workspaces.includes(pattern);
|
|
474
484
|
}
|
|
475
|
-
return
|
|
485
|
+
return Boolean(workspaces && Array.isArray(workspaces.packages) && workspaces.packages.includes(pattern));
|
|
476
486
|
}
|
|
@@ -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
|
@@ -7,7 +7,10 @@ import {
|
|
|
7
7
|
nxShowProjectCommand,
|
|
8
8
|
projectNamesFromNxShowProjectsOutput,
|
|
9
9
|
projectNamesWithTarget,
|
|
10
|
+
projectRootFromNxProjectJson,
|
|
11
|
+
targetDependenciesFromNxProjectJson,
|
|
10
12
|
targetNamesFromNxProjectJson,
|
|
13
|
+
targetOutputsFromNxProjectJson,
|
|
11
14
|
} from './index.js';
|
|
12
15
|
|
|
13
16
|
describe('Nx helper command construction', () => {
|
|
@@ -67,6 +70,46 @@ describe('Nx helper output formatting', () => {
|
|
|
67
70
|
]);
|
|
68
71
|
});
|
|
69
72
|
|
|
73
|
+
it('extracts resolved roots, output declarations, and same-project target dependencies', () => {
|
|
74
|
+
const metadata = {
|
|
75
|
+
root: 'packages/native',
|
|
76
|
+
targets: {
|
|
77
|
+
'build-macos': {
|
|
78
|
+
outputs: ['{projectRoot}/dist/*.dmg'],
|
|
79
|
+
dependsOn: ['^build', 'compile-macos', { target: 'package-macos', projects: 'self' }],
|
|
80
|
+
},
|
|
81
|
+
test: { outputs: [] },
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
expect(projectRootFromNxProjectJson(metadata)).toBe('packages/native');
|
|
86
|
+
expect(targetOutputsFromNxProjectJson(metadata)).toEqual(
|
|
87
|
+
new Map([
|
|
88
|
+
['build-macos', ['{projectRoot}/dist/*.dmg']],
|
|
89
|
+
['test', []],
|
|
90
|
+
]),
|
|
91
|
+
);
|
|
92
|
+
expect(targetDependenciesFromNxProjectJson(metadata)).toEqual(
|
|
93
|
+
new Map([['build-macos', ['^build', 'compile-macos', 'package-macos']]]),
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('omits cross-project dependencies from same-project target closures', () => {
|
|
98
|
+
expect(
|
|
99
|
+
targetDependenciesFromNxProjectJson({
|
|
100
|
+
targets: {
|
|
101
|
+
'rust-wasm': {
|
|
102
|
+
dependsOn: [
|
|
103
|
+
'prepare',
|
|
104
|
+
{ target: 'cargo-wasm', projects: ['lmao-rs'] },
|
|
105
|
+
{ target: 'build', projects: 'dependencies' },
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
).toEqual(new Map([['rust-wasm', ['prepare']]]));
|
|
111
|
+
});
|
|
112
|
+
|
|
70
113
|
it('treats missing target metadata as an empty project', () => {
|
|
71
114
|
expect(targetNamesFromNxProjectJson({ name: 'cli' })).toEqual([]);
|
|
72
115
|
});
|