@zenithbuild/cli 0.6.3 → 0.6.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/dist/build.js +429 -75
- package/dist/component-instance-ir.js +217 -0
- package/dist/component-occurrences.js +152 -0
- package/dist/index.js +9 -0
- package/dist/toolchain-paths.js +110 -0
- package/dist/version-check.js +378 -0
- package/package.json +2 -2
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import {
|
|
5
|
+
readCliPackageVersion,
|
|
6
|
+
readInstalledPackageVersion,
|
|
7
|
+
resolveBundlerBin
|
|
8
|
+
} from './toolchain-paths.js';
|
|
9
|
+
|
|
10
|
+
const PACKAGE_KEYS = [
|
|
11
|
+
['core', '@zenithbuild/core'],
|
|
12
|
+
['compiler', '@zenithbuild/compiler'],
|
|
13
|
+
['runtime', '@zenithbuild/runtime'],
|
|
14
|
+
['router', '@zenithbuild/router'],
|
|
15
|
+
['bundlerPackage', '@zenithbuild/bundler']
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
function parseVersion(version) {
|
|
19
|
+
const raw = String(version || '').trim();
|
|
20
|
+
const match = raw.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
|
|
21
|
+
if (!match) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
raw,
|
|
26
|
+
major: Number.parseInt(match[1], 10),
|
|
27
|
+
minor: Number.parseInt(match[2], 10),
|
|
28
|
+
patch: Number.parseInt(match[3], 10),
|
|
29
|
+
prerelease: match[4] || '',
|
|
30
|
+
prereleaseParts: match[4] ? match[4].split('.') : []
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function compareIdentifiers(left, right) {
|
|
35
|
+
const leftNumeric = /^\d+$/.test(left);
|
|
36
|
+
const rightNumeric = /^\d+$/.test(right);
|
|
37
|
+
if (leftNumeric && rightNumeric) {
|
|
38
|
+
return Number(left) - Number(right);
|
|
39
|
+
}
|
|
40
|
+
if (leftNumeric) {
|
|
41
|
+
return -1;
|
|
42
|
+
}
|
|
43
|
+
if (rightNumeric) {
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
return left.localeCompare(right);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function compareVersions(leftVersion, rightVersion) {
|
|
50
|
+
const left = parseVersion(leftVersion);
|
|
51
|
+
const right = parseVersion(rightVersion);
|
|
52
|
+
if (!left && !right) return 0;
|
|
53
|
+
if (!left) return -1;
|
|
54
|
+
if (!right) return 1;
|
|
55
|
+
|
|
56
|
+
const numberDelta = (
|
|
57
|
+
(left.major - right.major)
|
|
58
|
+
|| (left.minor - right.minor)
|
|
59
|
+
|| (left.patch - right.patch)
|
|
60
|
+
);
|
|
61
|
+
if (numberDelta !== 0) {
|
|
62
|
+
return numberDelta;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!left.prerelease && !right.prerelease) {
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
if (!left.prerelease) {
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
|
71
|
+
if (!right.prerelease) {
|
|
72
|
+
return -1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const len = Math.max(left.prereleaseParts.length, right.prereleaseParts.length);
|
|
76
|
+
for (let index = 0; index < len; index += 1) {
|
|
77
|
+
const leftPart = left.prereleaseParts[index];
|
|
78
|
+
const rightPart = right.prereleaseParts[index];
|
|
79
|
+
if (leftPart === undefined) {
|
|
80
|
+
return -1;
|
|
81
|
+
}
|
|
82
|
+
if (rightPart === undefined) {
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
const delta = compareIdentifiers(leftPart, rightPart);
|
|
86
|
+
if (delta !== 0) {
|
|
87
|
+
return delta;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function prereleaseChannel(parsed) {
|
|
95
|
+
if (!parsed || !parsed.prerelease) {
|
|
96
|
+
return 'stable';
|
|
97
|
+
}
|
|
98
|
+
const [label = 'prerelease', train] = parsed.prereleaseParts;
|
|
99
|
+
if (train && /^\d+$/.test(train)) {
|
|
100
|
+
return `${label}.${train}`;
|
|
101
|
+
}
|
|
102
|
+
return label;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function classifyDifference(expectedVersion, actualVersion) {
|
|
106
|
+
if (!expectedVersion || !actualVersion) {
|
|
107
|
+
return 'unknown';
|
|
108
|
+
}
|
|
109
|
+
if (expectedVersion === actualVersion) {
|
|
110
|
+
return 'exact';
|
|
111
|
+
}
|
|
112
|
+
const expected = parseVersion(expectedVersion);
|
|
113
|
+
const actual = parseVersion(actualVersion);
|
|
114
|
+
if (!expected || !actual) {
|
|
115
|
+
return 'unknown';
|
|
116
|
+
}
|
|
117
|
+
if (expected.major !== actual.major || expected.minor !== actual.minor) {
|
|
118
|
+
return 'hard';
|
|
119
|
+
}
|
|
120
|
+
if (prereleaseChannel(expected) !== prereleaseChannel(actual)) {
|
|
121
|
+
return 'hard';
|
|
122
|
+
}
|
|
123
|
+
return 'soft';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function readProjectPackage(projectRoot) {
|
|
127
|
+
if (!projectRoot) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
const manifestPath = resolve(projectRoot, 'package.json');
|
|
132
|
+
if (!existsSync(manifestPath)) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function buildFixCommand(projectRoot, targetVersion) {
|
|
142
|
+
const manifest = readProjectPackage(projectRoot);
|
|
143
|
+
const dependencyNames = [
|
|
144
|
+
'@zenithbuild/core',
|
|
145
|
+
'@zenithbuild/cli',
|
|
146
|
+
'@zenithbuild/compiler',
|
|
147
|
+
'@zenithbuild/runtime',
|
|
148
|
+
'@zenithbuild/router',
|
|
149
|
+
'@zenithbuild/bundler'
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
const deps = [];
|
|
153
|
+
const devDeps = [];
|
|
154
|
+
for (const name of dependencyNames) {
|
|
155
|
+
if (manifest?.dependencies && Object.prototype.hasOwnProperty.call(manifest.dependencies, name)) {
|
|
156
|
+
deps.push(`${name}@${targetVersion}`);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
devDeps.push(`${name}@${targetVersion}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const commands = [];
|
|
163
|
+
if (deps.length > 0) {
|
|
164
|
+
commands.push(`npm i ${deps.join(' ')}`);
|
|
165
|
+
}
|
|
166
|
+
if (devDeps.length > 0) {
|
|
167
|
+
commands.push(`npm i -D ${devDeps.join(' ')}`);
|
|
168
|
+
}
|
|
169
|
+
if (commands.length === 0) {
|
|
170
|
+
commands.push(`npm i -D ${dependencyNames.map((name) => `${name}@${targetVersion}`).join(' ')}`);
|
|
171
|
+
}
|
|
172
|
+
return commands.join(' && ');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function describeVersions(versions) {
|
|
176
|
+
const entries = [
|
|
177
|
+
['cli', versions.cli],
|
|
178
|
+
['project cli', versions.projectCli],
|
|
179
|
+
['core', versions.core],
|
|
180
|
+
['compiler', versions.compiler],
|
|
181
|
+
['runtime', versions.runtime],
|
|
182
|
+
['router', versions.router],
|
|
183
|
+
['bundler pkg', versions.bundlerPackage],
|
|
184
|
+
['bundler bin', versions.bundlerBinary]
|
|
185
|
+
];
|
|
186
|
+
return entries
|
|
187
|
+
.filter(([, version]) => typeof version === 'string' && version.length > 0)
|
|
188
|
+
.map(([label, version]) => `${label}=${version}`)
|
|
189
|
+
.join(' ');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function summarizeIssues(issues) {
|
|
193
|
+
const preview = issues.slice(0, 3).map((issue) => issue.summary);
|
|
194
|
+
const suffix = issues.length > 3 ? ` +${issues.length - 3} more` : '';
|
|
195
|
+
return `${preview.join('; ')}${suffix}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function determineTargetVersion(versions) {
|
|
199
|
+
const candidates = [
|
|
200
|
+
versions.projectCli,
|
|
201
|
+
versions.core,
|
|
202
|
+
versions.compiler,
|
|
203
|
+
versions.runtime,
|
|
204
|
+
versions.router,
|
|
205
|
+
versions.bundlerPackage,
|
|
206
|
+
versions.cli
|
|
207
|
+
].filter((value) => typeof value === 'string' && value.length > 0);
|
|
208
|
+
|
|
209
|
+
if (candidates.length === 0) {
|
|
210
|
+
return '0.0.0';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let highest = candidates[0];
|
|
214
|
+
for (const candidate of candidates.slice(1)) {
|
|
215
|
+
if (compareVersions(candidate, highest) > 0) {
|
|
216
|
+
highest = candidate;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return highest;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function getBundlerVersion(bundlerBinPath) {
|
|
223
|
+
const path = String(bundlerBinPath || '').trim();
|
|
224
|
+
if (!path) {
|
|
225
|
+
return { version: null, path: '', rawOutput: '', ok: false };
|
|
226
|
+
}
|
|
227
|
+
const result = spawnSync(path, ['--version'], { encoding: 'utf8' });
|
|
228
|
+
if (result.error) {
|
|
229
|
+
return {
|
|
230
|
+
version: null,
|
|
231
|
+
path,
|
|
232
|
+
rawOutput: result.error.message,
|
|
233
|
+
ok: false
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const rawOutput = `${result.stdout || ''}\n${result.stderr || ''}`.trim();
|
|
238
|
+
const versionMatch = rawOutput.match(/(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/);
|
|
239
|
+
return {
|
|
240
|
+
version: versionMatch ? versionMatch[1] : null,
|
|
241
|
+
path,
|
|
242
|
+
rawOutput,
|
|
243
|
+
ok: result.status === 0 && Boolean(versionMatch)
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function getLocalZenithVersions({ projectRoot, bundlerBinPath } = {}) {
|
|
248
|
+
const resolvedBundlerBin = bundlerBinPath || resolveBundlerBin(projectRoot);
|
|
249
|
+
const bundlerVersion = getBundlerVersion(resolvedBundlerBin);
|
|
250
|
+
const versions = {
|
|
251
|
+
cli: readCliPackageVersion(),
|
|
252
|
+
projectCli: readInstalledPackageVersion('@zenithbuild/cli', projectRoot),
|
|
253
|
+
bundlerBinary: bundlerVersion.version,
|
|
254
|
+
bundlerBinPath: bundlerVersion.path,
|
|
255
|
+
bundlerBinRawOutput: bundlerVersion.rawOutput,
|
|
256
|
+
targetVersion: null
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
for (const [key, packageName] of PACKAGE_KEYS) {
|
|
260
|
+
versions[key] = readInstalledPackageVersion(packageName, projectRoot);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
versions.targetVersion = determineTargetVersion(versions);
|
|
264
|
+
return versions;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function checkCompatibility(versions) {
|
|
268
|
+
const targetVersion = versions?.targetVersion || determineTargetVersion(versions || {});
|
|
269
|
+
const issues = [];
|
|
270
|
+
const fixCommand = buildFixCommand(versions?.projectRoot, targetVersion);
|
|
271
|
+
|
|
272
|
+
const addIssue = (code, summary, message) => {
|
|
273
|
+
issues.push({
|
|
274
|
+
code,
|
|
275
|
+
summary,
|
|
276
|
+
message,
|
|
277
|
+
hint: `${fixCommand} (suppress with ZENITH_SKIP_VERSION_CHECK=1)`,
|
|
278
|
+
fixCommand
|
|
279
|
+
});
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
if (versions.projectCli && versions.cli && versions.projectCli !== versions.cli) {
|
|
283
|
+
const severity = classifyDifference(versions.projectCli, versions.cli);
|
|
284
|
+
addIssue(
|
|
285
|
+
severity === 'hard' ? 'CLI_TRAIN_MISMATCH' : 'CLI_OUTDATED',
|
|
286
|
+
`cli ${versions.cli} != project ${versions.projectCli}`,
|
|
287
|
+
`Version mismatch detected (may break HMR/refs): executing CLI ${versions.cli} does not match project CLI ${versions.projectCli}.`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
for (const [key, label] of [
|
|
292
|
+
['core', 'core'],
|
|
293
|
+
['compiler', 'compiler'],
|
|
294
|
+
['runtime', 'runtime'],
|
|
295
|
+
['router', 'router'],
|
|
296
|
+
['bundlerPackage', 'bundler package']
|
|
297
|
+
]) {
|
|
298
|
+
const actual = versions[key];
|
|
299
|
+
const difference = classifyDifference(targetVersion, actual);
|
|
300
|
+
if (difference === 'hard') {
|
|
301
|
+
addIssue(
|
|
302
|
+
'VERSION_TRAIN_MISMATCH',
|
|
303
|
+
`${label} ${actual} != ${targetVersion}`,
|
|
304
|
+
`Version mismatch detected (may break HMR/refs): ${label} ${actual} is on a different Zenith train than ${targetVersion}.`
|
|
305
|
+
);
|
|
306
|
+
} else if (difference === 'soft') {
|
|
307
|
+
addIssue(
|
|
308
|
+
'VERSION_OUTDATED',
|
|
309
|
+
`${label} ${actual} != ${targetVersion}`,
|
|
310
|
+
`Version mismatch detected (may break HMR/refs): ${label} ${actual} is not aligned with ${targetVersion}.`
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const bundlerExpected = versions.bundlerPackage || targetVersion;
|
|
316
|
+
const bundlerDifference = classifyDifference(bundlerExpected, versions.bundlerBinary);
|
|
317
|
+
if (bundlerDifference === 'hard') {
|
|
318
|
+
addIssue(
|
|
319
|
+
'BUNDLER_BINARY_MISMATCH',
|
|
320
|
+
`bundler bin ${versions.bundlerBinary || 'missing'} != ${bundlerExpected}`,
|
|
321
|
+
`Version mismatch detected (may break build/IR contracts): bundler binary ${versions.bundlerBinary || 'missing'} does not match ${bundlerExpected}.`
|
|
322
|
+
);
|
|
323
|
+
} else if (bundlerDifference === 'soft') {
|
|
324
|
+
addIssue(
|
|
325
|
+
'BUNDLER_BINARY_OUTDATED',
|
|
326
|
+
`bundler bin ${versions.bundlerBinary} != ${bundlerExpected}`,
|
|
327
|
+
`Version mismatch detected (may break build/IR contracts): bundler binary ${versions.bundlerBinary} is not aligned with ${bundlerExpected}.`
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
status: issues.length === 0 ? 'ok' : 'warn',
|
|
333
|
+
issues,
|
|
334
|
+
details: {
|
|
335
|
+
targetVersion,
|
|
336
|
+
versions: {
|
|
337
|
+
...versions
|
|
338
|
+
},
|
|
339
|
+
summary: describeVersions(versions)
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export async function maybeWarnAboutZenithVersionMismatch({
|
|
345
|
+
projectRoot,
|
|
346
|
+
logger,
|
|
347
|
+
command = 'build',
|
|
348
|
+
bundlerBinPath = null
|
|
349
|
+
} = {}) {
|
|
350
|
+
if (!logger || process.env.ZENITH_SKIP_VERSION_CHECK === '1') {
|
|
351
|
+
return { status: 'ok', issues: [], details: {} };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const versions = getLocalZenithVersions({ projectRoot, bundlerBinPath });
|
|
355
|
+
versions.projectRoot = projectRoot;
|
|
356
|
+
const result = checkCompatibility(versions);
|
|
357
|
+
const onceKey = `zenith-version-check:${describeVersions(versions)}:${result.status}`;
|
|
358
|
+
const verboseTag = command === 'dev' ? 'DEV' : 'BUILD';
|
|
359
|
+
|
|
360
|
+
if (result.status === 'ok') {
|
|
361
|
+
logger.verbose(verboseTag, `toolchain versions ok ${result.details.summary}`);
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const primary = result.issues[0];
|
|
366
|
+
logger.warn(
|
|
367
|
+
`${primary.message} ${summarizeIssues(result.issues)}`,
|
|
368
|
+
{
|
|
369
|
+
onceKey,
|
|
370
|
+
hint: primary.hint
|
|
371
|
+
}
|
|
372
|
+
);
|
|
373
|
+
logger.verbose(verboseTag, `toolchain versions ${result.details.summary}`);
|
|
374
|
+
if (versions.bundlerBinPath) {
|
|
375
|
+
logger.verbose(verboseTag, `bundler bin path=${versions.bundlerBinPath}`);
|
|
376
|
+
}
|
|
377
|
+
return result;
|
|
378
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenithbuild/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "Deterministic project orchestrator for Zenith framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"prepublishOnly": "npm run build"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@zenithbuild/compiler": "0.6.
|
|
27
|
+
"@zenithbuild/compiler": "^0.6.5",
|
|
28
28
|
"picocolors": "^1.1.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|