@jskit-ai/jskit-cli 0.2.25 → 0.2.26
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/package.json +2 -2
- package/src/server/argParser.js +21 -4
- package/src/server/cliRuntime.js +335 -121
- package/src/server/commandHandlers.js +329 -14
- package/src/server/pathResolution.js +0 -64
- package/src/server/runCli.js +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/jskit-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.26",
|
|
4
4
|
"description": "Bundle and package orchestration CLI for JSKIT apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"test": "node --test"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@jskit-ai/jskit-catalog": "0.1.
|
|
23
|
+
"@jskit-ai/jskit-catalog": "0.1.26"
|
|
24
24
|
},
|
|
25
25
|
"engines": {
|
|
26
26
|
"node": "20.x"
|
package/src/server/argParser.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const KNOWN_COMMANDS = new Set([
|
|
2
2
|
"help",
|
|
3
3
|
"create",
|
|
4
|
+
"generate",
|
|
4
5
|
"list",
|
|
5
6
|
"show",
|
|
6
7
|
"view",
|
|
@@ -15,7 +16,8 @@ const KNOWN_COMMANDS = new Set([
|
|
|
15
16
|
|
|
16
17
|
const COMMAND_ALIASES = Object.freeze({
|
|
17
18
|
view: "show",
|
|
18
|
-
ls: "list"
|
|
19
|
+
ls: "list",
|
|
20
|
+
gen: "generate"
|
|
19
21
|
});
|
|
20
22
|
|
|
21
23
|
function resolveCommandAlias(rawCommand) {
|
|
@@ -44,6 +46,8 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
44
46
|
expanded: false,
|
|
45
47
|
details: false,
|
|
46
48
|
debugExports: false,
|
|
49
|
+
checkDiLabels: false,
|
|
50
|
+
verbose: false,
|
|
47
51
|
json: false,
|
|
48
52
|
all: false,
|
|
49
53
|
help: true,
|
|
@@ -67,6 +71,8 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
67
71
|
expanded: false,
|
|
68
72
|
details: false,
|
|
69
73
|
debugExports: false,
|
|
74
|
+
checkDiLabels: false,
|
|
75
|
+
verbose: false,
|
|
70
76
|
json: false,
|
|
71
77
|
all: false,
|
|
72
78
|
help: false,
|
|
@@ -101,6 +107,14 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
101
107
|
options.debugExports = true;
|
|
102
108
|
continue;
|
|
103
109
|
}
|
|
110
|
+
if (token === "--check-di-labels") {
|
|
111
|
+
options.checkDiLabels = true;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (token === "--verbose") {
|
|
115
|
+
options.verbose = true;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
104
118
|
if (token === "--json") {
|
|
105
119
|
options.json = true;
|
|
106
120
|
continue;
|
|
@@ -159,10 +173,11 @@ function printUsage(stream = process.stderr) {
|
|
|
159
173
|
stream.write("Usage: jskit <command> [options]\n\n");
|
|
160
174
|
stream.write("Commands:\n");
|
|
161
175
|
stream.write(" create package <name> Scaffold app-local package under packages/ and install it\n");
|
|
162
|
-
stream.write(" list [bundles [all]|packages] List available bundles/packages and installed status\n");
|
|
176
|
+
stream.write(" list [bundles [all]|packages|generators] List available bundles/runtime packages/generators and installed status\n");
|
|
163
177
|
stream.write(" lint-descriptors Validate bundle/package descriptor files\n");
|
|
164
178
|
stream.write(" add bundle <bundleId> Add one bundle (bundle is a package shortcut)\n");
|
|
165
|
-
stream.write(" add package <packageId> Add one package to current app (catalog/app-local/installed external)\n");
|
|
179
|
+
stream.write(" add package <packageId> Add one runtime package to current app (catalog/app-local/installed external)\n");
|
|
180
|
+
stream.write(" generate <packageId> Run one generator package\n");
|
|
166
181
|
stream.write(" position element <packageId> Re-apply positioning mutations for one installed package\n");
|
|
167
182
|
stream.write(" show <id> Show details for bundle id or package id\n");
|
|
168
183
|
stream.write(" view <id> Alias of show <id>\n");
|
|
@@ -173,7 +188,7 @@ function printUsage(stream = process.stderr) {
|
|
|
173
188
|
stream.write("\n");
|
|
174
189
|
stream.write("Options:\n");
|
|
175
190
|
stream.write(" --dry-run Print planned changes only\n");
|
|
176
|
-
stream.write(" --no-install Skip npm install during create/add/update/remove\n");
|
|
191
|
+
stream.write(" --no-install Skip npm install during create/add/generate/update/remove\n");
|
|
177
192
|
stream.write(" --scope <scope> (create package) override generated package scope\n");
|
|
178
193
|
stream.write(" --package-id <id> (create package) explicit @scope/name package id\n");
|
|
179
194
|
stream.write(" --description <text> (create package) descriptor description text\n");
|
|
@@ -181,6 +196,8 @@ function printUsage(stream = process.stderr) {
|
|
|
181
196
|
stream.write(" --expanded Show expanded/transitive package ids\n");
|
|
182
197
|
stream.write(" --details Show extra capability detail in show output\n");
|
|
183
198
|
stream.write(" --debug-exports Show export provenance/re-export source details in show output\n");
|
|
199
|
+
stream.write(" --check-di-labels (lint-descriptors) verify DI labels used by providers match descriptor container tokens\n");
|
|
200
|
+
stream.write(" --verbose Show verbose informational diagnostics\n");
|
|
184
201
|
stream.write(" --<option> <value> Package option (for packages requiring input)\n");
|
|
185
202
|
stream.write(" --json Print structured output\n");
|
|
186
203
|
stream.write(" -h, --help Show help\n");
|
package/src/server/cliRuntime.js
CHANGED
|
@@ -28,9 +28,7 @@ import {
|
|
|
28
28
|
import {
|
|
29
29
|
BUNDLES_ROOT,
|
|
30
30
|
CATALOG_PACKAGES_PATH,
|
|
31
|
-
CLI_PACKAGE_ROOT
|
|
32
|
-
MODULES_ROOT,
|
|
33
|
-
WORKSPACE_ROOT
|
|
31
|
+
CLI_PACKAGE_ROOT
|
|
34
32
|
} from "./pathResolution.js";
|
|
35
33
|
import {
|
|
36
34
|
appendTextSnippet,
|
|
@@ -55,9 +53,9 @@ const VITE_DEV_PROXY_CONFIG_RELATIVE_PATH = ".jskit/vite.dev.proxy.json";
|
|
|
55
53
|
const VITE_DEV_PROXY_CONFIG_VERSION = 1;
|
|
56
54
|
const PUBLIC_APP_CONFIG_RELATIVE_PATH = "config/public.js";
|
|
57
55
|
const SERVER_APP_CONFIG_RELATIVE_PATH = "config/server.js";
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
const
|
|
56
|
+
const PACKAGE_KIND_RUNTIME = "runtime";
|
|
57
|
+
const PACKAGE_KIND_GENERATOR = "generator";
|
|
58
|
+
const PACKAGE_KINDS = Object.freeze([PACKAGE_KIND_RUNTIME, PACKAGE_KIND_GENERATOR]);
|
|
61
59
|
const WORKSPACE_VISIBILITY_LEVELS = Object.freeze(["workspace", "workspace_user"]);
|
|
62
60
|
const WORKSPACE_VISIBILITY_SET = new Set(WORKSPACE_VISIBILITY_LEVELS);
|
|
63
61
|
const MATERIALIZED_PACKAGE_ROOTS = new Map();
|
|
@@ -95,6 +93,20 @@ function normalizeMutationExtension(value) {
|
|
|
95
93
|
return `.${extension}`;
|
|
96
94
|
}
|
|
97
95
|
|
|
96
|
+
function normalizeTemplateContextRecord(value) {
|
|
97
|
+
const record = ensureObject(value);
|
|
98
|
+
const entrypoint = String(record.entrypoint || "").trim();
|
|
99
|
+
const exportName = String(record.export || "").trim();
|
|
100
|
+
if (!entrypoint && !exportName) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return Object.freeze({
|
|
105
|
+
entrypoint,
|
|
106
|
+
export: exportName || "buildTemplateContext"
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
98
110
|
function normalizeFileMutationRecord(value) {
|
|
99
111
|
const record = ensureObject(value);
|
|
100
112
|
const op = String(record.op || "copy-file").trim().toLowerCase() || "copy-file";
|
|
@@ -111,6 +123,7 @@ function normalizeFileMutationRecord(value) {
|
|
|
111
123
|
id: String(record.id || "").trim(),
|
|
112
124
|
category: String(record.category || "").trim(),
|
|
113
125
|
reason: String(record.reason || "").trim(),
|
|
126
|
+
templateContext: normalizeTemplateContextRecord(record.templateContext),
|
|
114
127
|
when: normalizeMutationWhen(record.when)
|
|
115
128
|
};
|
|
116
129
|
}
|
|
@@ -184,6 +197,44 @@ function normalizeWhenSourceValue(value) {
|
|
|
184
197
|
return "";
|
|
185
198
|
}
|
|
186
199
|
|
|
200
|
+
function normalizeWhenComparisonValue(value) {
|
|
201
|
+
const normalizedValue = normalizeWhenSourceValue(value);
|
|
202
|
+
if (!normalizedValue.includes(",")) {
|
|
203
|
+
return normalizedValue;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return normalizedValue
|
|
207
|
+
.split(",")
|
|
208
|
+
.map((entry) => String(entry || "").trim())
|
|
209
|
+
.filter(Boolean)
|
|
210
|
+
.join(",");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function splitWhenComparisonTokens(value) {
|
|
214
|
+
return normalizeWhenComparisonValue(value)
|
|
215
|
+
.split(",")
|
|
216
|
+
.map((entry) => String(entry || "").trim())
|
|
217
|
+
.filter(Boolean);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function matchesWhenComparisonValue(optionValue, optionTokens, expectedValue) {
|
|
221
|
+
const expected = normalizeWhenComparisonValue(expectedValue);
|
|
222
|
+
if (!expected) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const expectedTokens = splitWhenComparisonTokens(expected);
|
|
227
|
+
if (expectedTokens.length > 1) {
|
|
228
|
+
return optionValue === expected;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (optionTokens.length > 1) {
|
|
232
|
+
return optionTokens.includes(expectedTokens[0]);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return optionValue === expectedTokens[0];
|
|
236
|
+
}
|
|
237
|
+
|
|
187
238
|
function resolveWhenConfigValue(configContext = {}, configPath = "") {
|
|
188
239
|
const normalizedPath = String(configPath || "").trim();
|
|
189
240
|
if (!normalizedPath) {
|
|
@@ -244,11 +295,12 @@ function shouldApplyMutationWhen(
|
|
|
244
295
|
const sourceValue = optionName
|
|
245
296
|
? readObjectPath(options, optionName)
|
|
246
297
|
: resolveWhenConfigValue(configContext, configPath);
|
|
247
|
-
const optionValue =
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
const
|
|
298
|
+
const optionValue = normalizeWhenComparisonValue(sourceValue);
|
|
299
|
+
const optionTokens = splitWhenComparisonTokens(optionValue);
|
|
300
|
+
const equals = normalizeWhenComparisonValue(when.equals);
|
|
301
|
+
const notEquals = normalizeWhenComparisonValue(when.notEquals);
|
|
302
|
+
const includes = ensureArray(when.includes).map((entry) => normalizeWhenComparisonValue(entry)).filter(Boolean);
|
|
303
|
+
const excludes = ensureArray(when.excludes).map((entry) => normalizeWhenComparisonValue(entry)).filter(Boolean);
|
|
252
304
|
|
|
253
305
|
if (equals && optionValue !== equals) {
|
|
254
306
|
return false;
|
|
@@ -256,10 +308,10 @@ function shouldApplyMutationWhen(
|
|
|
256
308
|
if (notEquals && optionValue === notEquals) {
|
|
257
309
|
return false;
|
|
258
310
|
}
|
|
259
|
-
if (includes.length > 0 && !includes.
|
|
311
|
+
if (includes.length > 0 && !includes.some((entry) => matchesWhenComparisonValue(optionValue, optionTokens, entry))) {
|
|
260
312
|
return false;
|
|
261
313
|
}
|
|
262
|
-
if (excludes.length > 0 && excludes.
|
|
314
|
+
if (excludes.length > 0 && excludes.some((entry) => matchesWhenComparisonValue(optionValue, optionTokens, entry))) {
|
|
263
315
|
return false;
|
|
264
316
|
}
|
|
265
317
|
|
|
@@ -1113,16 +1165,16 @@ function removeEnvValue(content, key, expectedValue, previous) {
|
|
|
1113
1165
|
};
|
|
1114
1166
|
}
|
|
1115
1167
|
|
|
1116
|
-
function
|
|
1117
|
-
const normalized = String(rawValue || "")
|
|
1118
|
-
.trim()
|
|
1119
|
-
.toLowerCase();
|
|
1168
|
+
function normalizePackageKind(rawValue, descriptorPath) {
|
|
1169
|
+
const normalized = String(rawValue || "").trim().toLowerCase();
|
|
1120
1170
|
if (!normalized) {
|
|
1121
|
-
|
|
1171
|
+
throw createCliError(
|
|
1172
|
+
`Invalid package descriptor at ${descriptorPath}: missing kind (expected ${PACKAGE_KINDS.join(" | ")}).`
|
|
1173
|
+
);
|
|
1122
1174
|
}
|
|
1123
|
-
if (!
|
|
1175
|
+
if (!PACKAGE_KINDS.includes(normalized)) {
|
|
1124
1176
|
throw createCliError(
|
|
1125
|
-
`Invalid package descriptor at ${descriptorPath}:
|
|
1177
|
+
`Invalid package descriptor at ${descriptorPath}: kind must be one of: ${PACKAGE_KINDS.join(", ")}.`
|
|
1126
1178
|
);
|
|
1127
1179
|
}
|
|
1128
1180
|
return normalized;
|
|
@@ -1177,13 +1229,13 @@ function validatePackageDescriptorShape(descriptor, descriptorPath) {
|
|
|
1177
1229
|
|
|
1178
1230
|
return {
|
|
1179
1231
|
...normalized,
|
|
1180
|
-
|
|
1232
|
+
kind: normalizePackageKind(normalized.kind, descriptorPath)
|
|
1181
1233
|
};
|
|
1182
1234
|
}
|
|
1183
1235
|
|
|
1184
|
-
function
|
|
1236
|
+
function isGeneratorPackageEntry(packageEntry) {
|
|
1185
1237
|
const descriptor = ensureObject(packageEntry?.descriptor);
|
|
1186
|
-
return String(descriptor.
|
|
1238
|
+
return String(descriptor.kind || "").trim().toLowerCase() === PACKAGE_KIND_GENERATOR;
|
|
1187
1239
|
}
|
|
1188
1240
|
|
|
1189
1241
|
function validateAppLocalPackageDescriptorShape(descriptor, descriptorPath, { expectedPackageId = "", fallbackVersion = "" } = {}) {
|
|
@@ -1208,7 +1260,8 @@ function validateAppLocalPackageDescriptorShape(descriptor, descriptorPath, { ex
|
|
|
1208
1260
|
return {
|
|
1209
1261
|
...normalized,
|
|
1210
1262
|
packageId,
|
|
1211
|
-
version
|
|
1263
|
+
version,
|
|
1264
|
+
kind: normalizePackageKind(normalized.kind, descriptorPath)
|
|
1212
1265
|
};
|
|
1213
1266
|
}
|
|
1214
1267
|
|
|
@@ -1277,85 +1330,6 @@ function validateBundleDescriptorShape(descriptor, descriptorPath) {
|
|
|
1277
1330
|
return normalized;
|
|
1278
1331
|
}
|
|
1279
1332
|
|
|
1280
|
-
async function loadWorkspacePackageRegistry() {
|
|
1281
|
-
if (!MODULES_ROOT || !(await fileExists(MODULES_ROOT))) {
|
|
1282
|
-
return new Map();
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
const directories = [];
|
|
1286
|
-
const levelOne = await readdir(MODULES_ROOT, { withFileTypes: true });
|
|
1287
|
-
|
|
1288
|
-
for (const entry of levelOne) {
|
|
1289
|
-
if (!entry.isDirectory()) {
|
|
1290
|
-
continue;
|
|
1291
|
-
}
|
|
1292
|
-
if (entry.name.startsWith(".") || entry.name.endsWith(".LEGACY")) {
|
|
1293
|
-
continue;
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
const absolute = path.join(MODULES_ROOT, entry.name);
|
|
1297
|
-
const descriptorPath = path.join(absolute, "package.descriptor.mjs");
|
|
1298
|
-
if (await fileExists(descriptorPath)) {
|
|
1299
|
-
directories.push(absolute);
|
|
1300
|
-
continue;
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
const nested = await readdir(absolute, { withFileTypes: true }).catch(() => []);
|
|
1304
|
-
for (const child of nested) {
|
|
1305
|
-
if (!child.isDirectory() || child.name.startsWith(".")) {
|
|
1306
|
-
continue;
|
|
1307
|
-
}
|
|
1308
|
-
const nestedAbsolute = path.join(absolute, child.name);
|
|
1309
|
-
const nestedDescriptor = path.join(nestedAbsolute, "package.descriptor.mjs");
|
|
1310
|
-
if (await fileExists(nestedDescriptor)) {
|
|
1311
|
-
directories.push(nestedAbsolute);
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
const uniqueDirectories = sortStrings([...new Set(directories)]);
|
|
1317
|
-
const registry = new Map();
|
|
1318
|
-
|
|
1319
|
-
for (const packageRoot of uniqueDirectories) {
|
|
1320
|
-
const descriptorPath = path.join(packageRoot, "package.descriptor.mjs");
|
|
1321
|
-
const descriptorModule = await import(pathToFileURL(descriptorPath).href);
|
|
1322
|
-
const descriptor = validatePackageDescriptorShape(descriptorModule?.default, descriptorPath);
|
|
1323
|
-
|
|
1324
|
-
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
1325
|
-
if (!(await fileExists(packageJsonPath))) {
|
|
1326
|
-
throw createCliError(`Missing package.json for ${descriptor.packageId} at ${packageRoot}.`);
|
|
1327
|
-
}
|
|
1328
|
-
const packageJson = await readJsonFile(packageJsonPath);
|
|
1329
|
-
const packageName = String(packageJson?.name || "").trim();
|
|
1330
|
-
if (packageName !== descriptor.packageId) {
|
|
1331
|
-
throw createCliError(
|
|
1332
|
-
`Descriptor/package mismatch at ${packageRoot}: package.descriptor.mjs has ${descriptor.packageId} but package.json has ${packageName || "(empty)"}.`
|
|
1333
|
-
);
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
const relativeDir = normalizeRelativePath(WORKSPACE_ROOT || MODULES_ROOT, packageRoot);
|
|
1337
|
-
const descriptorRelativePath = normalizeRelativePath(WORKSPACE_ROOT || MODULES_ROOT, descriptorPath);
|
|
1338
|
-
registry.set(
|
|
1339
|
-
descriptor.packageId,
|
|
1340
|
-
createPackageEntry({
|
|
1341
|
-
packageId: descriptor.packageId,
|
|
1342
|
-
version: descriptor.version,
|
|
1343
|
-
descriptor,
|
|
1344
|
-
rootDir: packageRoot,
|
|
1345
|
-
relativeDir,
|
|
1346
|
-
descriptorRelativePath,
|
|
1347
|
-
packageJson,
|
|
1348
|
-
sourceType: "packages-directory",
|
|
1349
|
-
source: {
|
|
1350
|
-
descriptorPath: descriptorRelativePath
|
|
1351
|
-
}
|
|
1352
|
-
})
|
|
1353
|
-
);
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
return registry;
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
1333
|
async function loadAppLocalPackageRegistry(appRoot) {
|
|
1360
1334
|
const localPackagesRoot = path.join(appRoot, "packages");
|
|
1361
1335
|
if (!(await fileExists(localPackagesRoot))) {
|
|
@@ -1468,17 +1442,14 @@ async function loadCatalogPackageRegistry() {
|
|
|
1468
1442
|
}
|
|
1469
1443
|
|
|
1470
1444
|
async function loadPackageRegistry() {
|
|
1471
|
-
const workspaceRegistry = await loadWorkspacePackageRegistry();
|
|
1472
1445
|
const catalogRegistry = await loadCatalogPackageRegistry();
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
if (merged.size === 0) {
|
|
1446
|
+
if (catalogRegistry.size === 0) {
|
|
1476
1447
|
throw createCliError(
|
|
1477
|
-
"Unable to load package registry
|
|
1448
|
+
"Unable to load package registry from @jskit-ai/jskit-catalog. Install it alongside @jskit-ai/jskit-cli or set JSKIT_CATALOG_PACKAGES_PATH."
|
|
1478
1449
|
);
|
|
1479
1450
|
}
|
|
1480
1451
|
|
|
1481
|
-
return
|
|
1452
|
+
return catalogRegistry;
|
|
1482
1453
|
}
|
|
1483
1454
|
|
|
1484
1455
|
async function loadInstalledNodeModulePackageEntry({ appRoot, packageId }) {
|
|
@@ -1745,6 +1716,7 @@ function createLocalPackageDescriptorTemplate({ packageId, description }) {
|
|
|
1745
1716
|
packageVersion: 1,
|
|
1746
1717
|
packageId: "${packageId}",
|
|
1747
1718
|
version: "0.1.0",
|
|
1719
|
+
kind: "runtime",
|
|
1748
1720
|
description: ${JSON.stringify(String(description || ""))},
|
|
1749
1721
|
dependsOn: [
|
|
1750
1722
|
// "@jskit-ai/kernel"
|
|
@@ -3544,20 +3516,134 @@ function interpolateFileMutationRecord(mutation, options, packageId) {
|
|
|
3544
3516
|
extension: interpolate(mutation.extension, "extension"),
|
|
3545
3517
|
id: interpolate(mutation.id, "id"),
|
|
3546
3518
|
category: interpolate(mutation.category, "category"),
|
|
3547
|
-
reason: interpolate(mutation.reason, "reason")
|
|
3519
|
+
reason: interpolate(mutation.reason, "reason"),
|
|
3520
|
+
templateContext: mutation.templateContext
|
|
3521
|
+
? {
|
|
3522
|
+
entrypoint: interpolate(mutation.templateContext.entrypoint, "templateContext.entrypoint"),
|
|
3523
|
+
export: interpolate(mutation.templateContext.export, "templateContext.export")
|
|
3524
|
+
}
|
|
3525
|
+
: null
|
|
3548
3526
|
};
|
|
3549
3527
|
}
|
|
3550
3528
|
|
|
3551
|
-
|
|
3529
|
+
function applyTemplateContextReplacements(sourceContent, replacements) {
|
|
3530
|
+
let output = String(sourceContent || "");
|
|
3531
|
+
for (const [placeholder, value] of Object.entries(ensureObject(replacements))) {
|
|
3532
|
+
const normalizedPlaceholder = String(placeholder || "");
|
|
3533
|
+
if (!normalizedPlaceholder) {
|
|
3534
|
+
continue;
|
|
3535
|
+
}
|
|
3536
|
+
output = output.split(normalizedPlaceholder).join(String(value == null ? "" : value));
|
|
3537
|
+
}
|
|
3538
|
+
return output;
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
async function copyTemplateFile(
|
|
3542
|
+
sourcePath,
|
|
3543
|
+
targetPath,
|
|
3544
|
+
options,
|
|
3545
|
+
packageId,
|
|
3546
|
+
interpolationKey,
|
|
3547
|
+
templateContextReplacements = null
|
|
3548
|
+
) {
|
|
3552
3549
|
const sourceContent = await readFile(sourcePath, "utf8");
|
|
3553
|
-
|
|
3550
|
+
let renderedContent = sourceContent.includes("${")
|
|
3554
3551
|
? interpolateOptionValue(sourceContent, options, packageId, interpolationKey)
|
|
3555
3552
|
: sourceContent;
|
|
3553
|
+
if (templateContextReplacements) {
|
|
3554
|
+
renderedContent = applyTemplateContextReplacements(renderedContent, templateContextReplacements);
|
|
3555
|
+
}
|
|
3556
3556
|
|
|
3557
3557
|
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
3558
3558
|
await writeFile(targetPath, renderedContent, "utf8");
|
|
3559
3559
|
}
|
|
3560
3560
|
|
|
3561
|
+
async function resolveTemplateContextReplacementsForMutation({
|
|
3562
|
+
packageEntry,
|
|
3563
|
+
mutation,
|
|
3564
|
+
options,
|
|
3565
|
+
appRoot,
|
|
3566
|
+
sourcePath,
|
|
3567
|
+
targetPaths
|
|
3568
|
+
} = {}) {
|
|
3569
|
+
const templateContext = ensureObject(mutation?.templateContext);
|
|
3570
|
+
const hasTemplateContext = Object.keys(templateContext).length > 0;
|
|
3571
|
+
const entrypoint = String(templateContext.entrypoint || "").trim();
|
|
3572
|
+
if (!hasTemplateContext) {
|
|
3573
|
+
return null;
|
|
3574
|
+
}
|
|
3575
|
+
if (!entrypoint) {
|
|
3576
|
+
throw createCliError(
|
|
3577
|
+
`Invalid files mutation in ${packageEntry.packageId}: templateContext.entrypoint is required when templateContext is set.`
|
|
3578
|
+
);
|
|
3579
|
+
}
|
|
3580
|
+
const exportName = String(templateContext.export || "").trim() || "buildTemplateContext";
|
|
3581
|
+
const resolvedEntrypointPath = resolveAppRelativePathWithinRoot(
|
|
3582
|
+
packageEntry.rootDir,
|
|
3583
|
+
entrypoint,
|
|
3584
|
+
`${packageEntry.packageId} files mutation templateContext.entrypoint`
|
|
3585
|
+
);
|
|
3586
|
+
const absoluteEntrypointPath = resolvedEntrypointPath.absolutePath;
|
|
3587
|
+
if (!(await fileExists(absoluteEntrypointPath))) {
|
|
3588
|
+
throw createCliError(
|
|
3589
|
+
`Invalid files mutation in ${packageEntry.packageId}: templateContext.entrypoint not found at ${entrypoint}.`
|
|
3590
|
+
);
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3593
|
+
let moduleNamespace = null;
|
|
3594
|
+
try {
|
|
3595
|
+
moduleNamespace = await import(`${pathToFileURL(absoluteEntrypointPath).href}?t=${Date.now()}_${Math.random()}`);
|
|
3596
|
+
} catch (error) {
|
|
3597
|
+
throw createCliError(
|
|
3598
|
+
`Unable to load templateContext entrypoint ${entrypoint} for ${packageEntry.packageId}: ${String(error?.message || error || "unknown error")}`
|
|
3599
|
+
);
|
|
3600
|
+
}
|
|
3601
|
+
|
|
3602
|
+
const resolver = moduleNamespace?.[exportName];
|
|
3603
|
+
if (typeof resolver !== "function") {
|
|
3604
|
+
throw createCliError(
|
|
3605
|
+
`Invalid files mutation in ${packageEntry.packageId}: templateContext export "${exportName}" is not a function.`
|
|
3606
|
+
);
|
|
3607
|
+
}
|
|
3608
|
+
|
|
3609
|
+
let replacements = null;
|
|
3610
|
+
try {
|
|
3611
|
+
replacements = await resolver({
|
|
3612
|
+
packageId: packageEntry.packageId,
|
|
3613
|
+
packageRoot: packageEntry.rootDir,
|
|
3614
|
+
appRoot,
|
|
3615
|
+
options: Object.freeze({ ...ensureObject(options) }),
|
|
3616
|
+
mutation: Object.freeze({ ...ensureObject(mutation) }),
|
|
3617
|
+
sourcePath,
|
|
3618
|
+
targetPaths: Object.freeze([...ensureArray(targetPaths)])
|
|
3619
|
+
});
|
|
3620
|
+
} catch (error) {
|
|
3621
|
+
throw createCliError(
|
|
3622
|
+
`templateContext export "${exportName}" failed for ${packageEntry.packageId}: ${String(error?.message || error || "unknown error")}`
|
|
3623
|
+
);
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
if (replacements == null) {
|
|
3627
|
+
return null;
|
|
3628
|
+
}
|
|
3629
|
+
if (!replacements || typeof replacements !== "object" || Array.isArray(replacements)) {
|
|
3630
|
+
throw createCliError(
|
|
3631
|
+
`Invalid files mutation in ${packageEntry.packageId}: templateContext export "${exportName}" must return an object map of placeholder replacements.`
|
|
3632
|
+
);
|
|
3633
|
+
}
|
|
3634
|
+
|
|
3635
|
+
const normalizedReplacements = {};
|
|
3636
|
+
for (const [placeholder, value] of Object.entries(replacements)) {
|
|
3637
|
+
const normalizedPlaceholder = String(placeholder || "").trim();
|
|
3638
|
+
if (!normalizedPlaceholder) {
|
|
3639
|
+
continue;
|
|
3640
|
+
}
|
|
3641
|
+
normalizedReplacements[normalizedPlaceholder] = String(value == null ? "" : value);
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3644
|
+
return Object.freeze(normalizedReplacements);
|
|
3645
|
+
}
|
|
3646
|
+
|
|
3561
3647
|
function normalizeSurfaceIdForMutation(value = "") {
|
|
3562
3648
|
return String(value || "")
|
|
3563
3649
|
.trim()
|
|
@@ -3738,8 +3824,10 @@ async function applyFileMutations(
|
|
|
3738
3824
|
managedFiles,
|
|
3739
3825
|
managedMigrations,
|
|
3740
3826
|
touchedFiles,
|
|
3741
|
-
warnings = []
|
|
3827
|
+
warnings = [],
|
|
3828
|
+
precomputedTemplateContextByMutationIndex = null
|
|
3742
3829
|
) {
|
|
3830
|
+
const mutationList = ensureArray(fileMutations);
|
|
3743
3831
|
const managedMigrationById = new Map();
|
|
3744
3832
|
for (const managedMigrationValue of ensureArray(managedMigrations)) {
|
|
3745
3833
|
const managedMigration = ensureObject(managedMigrationValue);
|
|
@@ -3750,7 +3838,7 @@ async function applyFileMutations(
|
|
|
3750
3838
|
managedMigrationById.set(migrationId, managedMigration);
|
|
3751
3839
|
}
|
|
3752
3840
|
|
|
3753
|
-
for (const mutationValue of
|
|
3841
|
+
for (const [mutationIndex, mutationValue] of mutationList.entries()) {
|
|
3754
3842
|
const normalizedMutation = normalizeFileMutationRecord(mutationValue);
|
|
3755
3843
|
const requiresConfigContext = Boolean(normalizedMutation.when?.config || normalizedMutation.toSurface);
|
|
3756
3844
|
const configContext = requiresConfigContext ? await loadMutationWhenConfigContext(appRoot) : {};
|
|
@@ -3788,10 +3876,27 @@ async function applyFileMutations(
|
|
|
3788
3876
|
throw createCliError(`Missing migration template source ${sourcePath} for ${packageEntry.packageId}.`);
|
|
3789
3877
|
}
|
|
3790
3878
|
|
|
3879
|
+
const hasPrecomputedTemplateContext =
|
|
3880
|
+
precomputedTemplateContextByMutationIndex instanceof Map &&
|
|
3881
|
+
precomputedTemplateContextByMutationIndex.has(mutationIndex);
|
|
3882
|
+
const templateContextReplacements = hasPrecomputedTemplateContext
|
|
3883
|
+
? precomputedTemplateContextByMutationIndex.get(mutationIndex)
|
|
3884
|
+
: await resolveTemplateContextReplacementsForMutation({
|
|
3885
|
+
packageEntry,
|
|
3886
|
+
mutation,
|
|
3887
|
+
options,
|
|
3888
|
+
appRoot,
|
|
3889
|
+
sourcePath,
|
|
3890
|
+
targetPaths: [path.join(appRoot, toDir)]
|
|
3891
|
+
});
|
|
3892
|
+
|
|
3791
3893
|
const sourceContent = await readFile(sourcePath, "utf8");
|
|
3792
|
-
|
|
3894
|
+
let renderedSourceContent = sourceContent.includes("${")
|
|
3793
3895
|
? interpolateOptionValue(sourceContent, options, packageEntry.packageId, `${mutation.id || from}.source`)
|
|
3794
3896
|
: sourceContent;
|
|
3897
|
+
if (templateContextReplacements) {
|
|
3898
|
+
renderedSourceContent = applyTemplateContextReplacements(renderedSourceContent, templateContextReplacements);
|
|
3899
|
+
}
|
|
3795
3900
|
const sourceExtension = normalizeMigrationExtension(path.extname(from), ".cjs");
|
|
3796
3901
|
const extension = normalizeMigrationExtension(mutation.extension, sourceExtension);
|
|
3797
3902
|
const sourceHash = hashBuffer(Buffer.from(renderedSourceContent, "utf8"));
|
|
@@ -3956,6 +4061,19 @@ async function applyFileMutations(
|
|
|
3956
4061
|
configContext
|
|
3957
4062
|
})
|
|
3958
4063
|
: [path.join(appRoot, to)];
|
|
4064
|
+
const hasPrecomputedTemplateContext =
|
|
4065
|
+
precomputedTemplateContextByMutationIndex instanceof Map &&
|
|
4066
|
+
precomputedTemplateContextByMutationIndex.has(mutationIndex);
|
|
4067
|
+
const templateContextReplacements = hasPrecomputedTemplateContext
|
|
4068
|
+
? precomputedTemplateContextByMutationIndex.get(mutationIndex)
|
|
4069
|
+
: await resolveTemplateContextReplacementsForMutation({
|
|
4070
|
+
packageEntry,
|
|
4071
|
+
mutation,
|
|
4072
|
+
options,
|
|
4073
|
+
appRoot,
|
|
4074
|
+
sourcePath,
|
|
4075
|
+
targetPaths
|
|
4076
|
+
});
|
|
3959
4077
|
for (const targetPath of targetPaths) {
|
|
3960
4078
|
const previous = await readFileBufferIfExists(targetPath);
|
|
3961
4079
|
await copyTemplateFile(
|
|
@@ -3963,7 +4081,8 @@ async function applyFileMutations(
|
|
|
3963
4081
|
targetPath,
|
|
3964
4082
|
options,
|
|
3965
4083
|
packageEntry.packageId,
|
|
3966
|
-
`${mutation.id || to || from}.source
|
|
4084
|
+
`${mutation.id || to || from}.source`,
|
|
4085
|
+
templateContextReplacements
|
|
3967
4086
|
);
|
|
3968
4087
|
const nextBuffer = await readFile(targetPath);
|
|
3969
4088
|
|
|
@@ -3982,6 +4101,91 @@ async function applyFileMutations(
|
|
|
3982
4101
|
}
|
|
3983
4102
|
}
|
|
3984
4103
|
|
|
4104
|
+
async function preflightFileMutationTemplateContexts(
|
|
4105
|
+
packageEntry,
|
|
4106
|
+
options,
|
|
4107
|
+
appRoot,
|
|
4108
|
+
fileMutations
|
|
4109
|
+
) {
|
|
4110
|
+
const mutationList = ensureArray(fileMutations);
|
|
4111
|
+
const replacementsByMutationIndex = new Map();
|
|
4112
|
+
|
|
4113
|
+
for (const [mutationIndex, mutationValue] of mutationList.entries()) {
|
|
4114
|
+
const normalizedMutation = normalizeFileMutationRecord(mutationValue);
|
|
4115
|
+
const requiresConfigContext = Boolean(normalizedMutation.when?.config || normalizedMutation.toSurface);
|
|
4116
|
+
const configContext = requiresConfigContext ? await loadMutationWhenConfigContext(appRoot) : {};
|
|
4117
|
+
if (
|
|
4118
|
+
!shouldApplyMutationWhen(normalizedMutation.when, {
|
|
4119
|
+
options,
|
|
4120
|
+
configContext,
|
|
4121
|
+
packageId: packageEntry.packageId,
|
|
4122
|
+
mutationContext: "files mutation"
|
|
4123
|
+
})
|
|
4124
|
+
) {
|
|
4125
|
+
continue;
|
|
4126
|
+
}
|
|
4127
|
+
|
|
4128
|
+
const mutation = interpolateFileMutationRecord(normalizedMutation, options, packageEntry.packageId);
|
|
4129
|
+
const templateContext = ensureObject(mutation.templateContext);
|
|
4130
|
+
if (Object.keys(templateContext).length < 1) {
|
|
4131
|
+
continue;
|
|
4132
|
+
}
|
|
4133
|
+
|
|
4134
|
+
const operation = mutation.op || "copy-file";
|
|
4135
|
+
if (operation !== "copy-file" && operation !== "install-migration") {
|
|
4136
|
+
continue;
|
|
4137
|
+
}
|
|
4138
|
+
|
|
4139
|
+
const from = mutation.from;
|
|
4140
|
+
const to = mutation.to;
|
|
4141
|
+
const toSurface = mutation.toSurface;
|
|
4142
|
+
if (!from) {
|
|
4143
|
+
throw createCliError(
|
|
4144
|
+
`Invalid files mutation in ${packageEntry.packageId}: "from" is required.`
|
|
4145
|
+
);
|
|
4146
|
+
}
|
|
4147
|
+
if (operation === "copy-file") {
|
|
4148
|
+
if (to && toSurface) {
|
|
4149
|
+
throw createCliError(
|
|
4150
|
+
`Invalid files mutation in ${packageEntry.packageId}: "to" and "toSurface" cannot both be set.`
|
|
4151
|
+
);
|
|
4152
|
+
}
|
|
4153
|
+
if (!to && !toSurface) {
|
|
4154
|
+
throw createCliError(
|
|
4155
|
+
`Invalid files mutation in ${packageEntry.packageId}: "from" plus one destination ("to" or "toSurface") are required.`
|
|
4156
|
+
);
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
|
|
4160
|
+
const sourcePath = path.join(packageEntry.rootDir, from);
|
|
4161
|
+
if (!(await fileExists(sourcePath))) {
|
|
4162
|
+
throw createCliError(`Missing template source ${sourcePath} for ${packageEntry.packageId}.`);
|
|
4163
|
+
}
|
|
4164
|
+
|
|
4165
|
+
const targetPaths = operation === "copy-file"
|
|
4166
|
+
? toSurface
|
|
4167
|
+
? resolveSurfaceTargetPathsForMutation({
|
|
4168
|
+
appRoot,
|
|
4169
|
+
packageId: packageEntry.packageId,
|
|
4170
|
+
mutation,
|
|
4171
|
+
configContext
|
|
4172
|
+
})
|
|
4173
|
+
: [path.join(appRoot, to)]
|
|
4174
|
+
: [path.join(appRoot, mutation.toDir || "migrations")];
|
|
4175
|
+
const replacements = await resolveTemplateContextReplacementsForMutation({
|
|
4176
|
+
packageEntry,
|
|
4177
|
+
mutation,
|
|
4178
|
+
options,
|
|
4179
|
+
appRoot,
|
|
4180
|
+
sourcePath,
|
|
4181
|
+
targetPaths
|
|
4182
|
+
});
|
|
4183
|
+
replacementsByMutationIndex.set(mutationIndex, replacements);
|
|
4184
|
+
}
|
|
4185
|
+
|
|
4186
|
+
return replacementsByMutationIndex;
|
|
4187
|
+
}
|
|
4188
|
+
|
|
3985
4189
|
async function applyTextMutations(packageEntry, appRoot, textMutations, options, managedText, touchedFiles) {
|
|
3986
4190
|
for (const mutation of textMutations) {
|
|
3987
4191
|
const when = normalizeMutationWhen(mutation?.when);
|
|
@@ -4391,9 +4595,10 @@ async function applyPackageInstall({
|
|
|
4391
4595
|
|
|
4392
4596
|
const managedRecord = createManagedRecordBase(packageEntry, packageOptions);
|
|
4393
4597
|
managedRecord.managed.migrations = cloneManagedArray(existingManaged.migrations);
|
|
4394
|
-
const
|
|
4598
|
+
const generatorPackage = isGeneratorPackageEntry(packageEntry);
|
|
4395
4599
|
const mutationWarnings = [];
|
|
4396
4600
|
const mutations = ensureObject(packageEntry.descriptor.mutations);
|
|
4601
|
+
const fileMutations = ensureArray(mutations.files);
|
|
4397
4602
|
const templateRoot = await resolvePackageTemplateRoot({ packageEntry, appRoot });
|
|
4398
4603
|
const packageEntryForMutations =
|
|
4399
4604
|
templateRoot === packageEntry.rootDir
|
|
@@ -4402,6 +4607,14 @@ async function applyPackageInstall({
|
|
|
4402
4607
|
...packageEntry,
|
|
4403
4608
|
rootDir: templateRoot
|
|
4404
4609
|
};
|
|
4610
|
+
|
|
4611
|
+
const precomputedTemplateContextByMutationIndex = await preflightFileMutationTemplateContexts(
|
|
4612
|
+
packageEntryForMutations,
|
|
4613
|
+
packageOptions,
|
|
4614
|
+
appRoot,
|
|
4615
|
+
fileMutations
|
|
4616
|
+
);
|
|
4617
|
+
|
|
4405
4618
|
const mutationDependencies = ensureObject(mutations.dependencies);
|
|
4406
4619
|
const runtimeDependencies = ensureObject(mutationDependencies.runtime);
|
|
4407
4620
|
const devDependencies = ensureObject(mutationDependencies.dev);
|
|
@@ -4471,7 +4684,7 @@ async function applyPackageInstall({
|
|
|
4471
4684
|
}
|
|
4472
4685
|
}
|
|
4473
4686
|
|
|
4474
|
-
if (
|
|
4687
|
+
if (generatorPackage) {
|
|
4475
4688
|
const removedRuntimeDependency = removePackageJsonField(appPackageJson, "dependencies", packageEntry.packageId);
|
|
4476
4689
|
const removedDevDependency = removePackageJsonField(appPackageJson, "devDependencies", packageEntry.packageId);
|
|
4477
4690
|
if (removedRuntimeDependency || removedDevDependency) {
|
|
@@ -4507,11 +4720,12 @@ async function applyPackageInstall({
|
|
|
4507
4720
|
packageEntryForMutations,
|
|
4508
4721
|
packageOptions,
|
|
4509
4722
|
appRoot,
|
|
4510
|
-
|
|
4723
|
+
fileMutations,
|
|
4511
4724
|
managedRecord.managed.files,
|
|
4512
4725
|
managedRecord.managed.migrations,
|
|
4513
4726
|
touchedFiles,
|
|
4514
|
-
mutationWarnings
|
|
4727
|
+
mutationWarnings,
|
|
4728
|
+
precomputedTemplateContextByMutationIndex
|
|
4515
4729
|
);
|
|
4516
4730
|
|
|
4517
4731
|
await applyTextMutations(
|
|
@@ -4532,7 +4746,7 @@ async function applyPackageInstall({
|
|
|
4532
4746
|
touchedFiles
|
|
4533
4747
|
);
|
|
4534
4748
|
|
|
4535
|
-
if (
|
|
4749
|
+
if (generatorPackage) {
|
|
4536
4750
|
delete lock.installedPackages[packageEntry.packageId];
|
|
4537
4751
|
} else {
|
|
4538
4752
|
managedRecord.migrationSyncVersion = packageEntry.version;
|
|
@@ -124,6 +124,18 @@ function createCommandHandlers(deps) {
|
|
|
124
124
|
return sortStrings(dependents);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
function resolvePackageKind(packageEntry) {
|
|
128
|
+
const descriptor = ensureObject(packageEntry?.descriptor);
|
|
129
|
+
const normalizedKind = String(descriptor.kind || "").trim().toLowerCase();
|
|
130
|
+
if (normalizedKind === "runtime" || normalizedKind === "generator") {
|
|
131
|
+
return normalizedKind;
|
|
132
|
+
}
|
|
133
|
+
const packageId = String(packageEntry?.packageId || descriptor.packageId || "unknown-package").trim();
|
|
134
|
+
throw createCliError(
|
|
135
|
+
`Invalid package descriptor for ${packageId}: missing/invalid kind (expected runtime or generator).`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
127
139
|
function resolvePackageOptionNames(packageEntry) {
|
|
128
140
|
const optionSchemas = ensureObject(packageEntry?.descriptor?.options);
|
|
129
141
|
return Object.keys(optionSchemas);
|
|
@@ -177,6 +189,155 @@ function createCommandHandlers(deps) {
|
|
|
177
189
|
: " This bundle does not accept inline options.";
|
|
178
190
|
throw createCliError(`Unknown option(s) for bundle ${bundleId}: ${sortedUnknown.join(", ")}.${suffix}`);
|
|
179
191
|
}
|
|
192
|
+
|
|
193
|
+
function collectDescriptorContainerTokens({ packageId, side, values, issues }) {
|
|
194
|
+
const declaredTokens = new Set();
|
|
195
|
+
const duplicateTokens = new Set();
|
|
196
|
+
let invalidCount = 0;
|
|
197
|
+
|
|
198
|
+
for (const rawValue of ensureArray(values)) {
|
|
199
|
+
if (typeof rawValue !== "string") {
|
|
200
|
+
invalidCount += 1;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const token = rawValue.trim();
|
|
204
|
+
if (!token) {
|
|
205
|
+
invalidCount += 1;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (declaredTokens.has(token)) {
|
|
209
|
+
duplicateTokens.add(token);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
declaredTokens.add(token);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (invalidCount > 0) {
|
|
216
|
+
issues.push({
|
|
217
|
+
packageId,
|
|
218
|
+
side,
|
|
219
|
+
code: "descriptor-token-invalid",
|
|
220
|
+
message: `${packageId} (${side}): metadata.apiSummary.containerTokens includes ${invalidCount} non-string or empty token value(s).`
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
for (const token of sortStrings([...duplicateTokens])) {
|
|
224
|
+
issues.push({
|
|
225
|
+
packageId,
|
|
226
|
+
side,
|
|
227
|
+
code: "descriptor-token-duplicate",
|
|
228
|
+
token,
|
|
229
|
+
message: `${packageId} (${side}): descriptor token is declared more than once: ${token}.`
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return declaredTokens;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function collectUsedContainerTokens({ packageId, side, bindings, issues }) {
|
|
237
|
+
const usedTokens = new Set();
|
|
238
|
+
for (const rawBinding of ensureArray(bindings)) {
|
|
239
|
+
const binding = ensureObject(rawBinding);
|
|
240
|
+
const tokenExpression = String(binding.tokenExpression || "").trim();
|
|
241
|
+
const token = String(binding.token || "").trim();
|
|
242
|
+
const location = String(binding.location || "").trim();
|
|
243
|
+
if (binding.tokenResolved !== true || !token) {
|
|
244
|
+
const expressionLabel = tokenExpression || "<empty>";
|
|
245
|
+
const locationSuffix = location ? ` at ${location}` : "";
|
|
246
|
+
issues.push({
|
|
247
|
+
packageId,
|
|
248
|
+
side,
|
|
249
|
+
code: "binding-token-unresolved",
|
|
250
|
+
tokenExpression: expressionLabel,
|
|
251
|
+
location,
|
|
252
|
+
message: `${packageId} (${side}): unresolved DI token expression "${expressionLabel}"${locationSuffix}.`
|
|
253
|
+
});
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
usedTokens.add(token);
|
|
257
|
+
}
|
|
258
|
+
return usedTokens;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function collectProviderIntrospectionIssues({ packageId, packageInsights, issues }) {
|
|
262
|
+
const introspection = ensureObject(packageInsights);
|
|
263
|
+
if (!introspection.available) {
|
|
264
|
+
issues.push({
|
|
265
|
+
packageId,
|
|
266
|
+
side: "",
|
|
267
|
+
code: "provider-introspection-unavailable",
|
|
268
|
+
message: `${packageId}: provider source introspection is unavailable, so DI token parity cannot be verified.`
|
|
269
|
+
});
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const notes = ensureArray(introspection.notes).map((value) => String(value || "").trim()).filter(Boolean);
|
|
274
|
+
for (const note of notes) {
|
|
275
|
+
if (
|
|
276
|
+
note.startsWith("Skipped wildcard provider entrypoint during introspection:") ||
|
|
277
|
+
note.startsWith("Provider file missing during introspection:") ||
|
|
278
|
+
note.startsWith("Failed reading provider ")
|
|
279
|
+
) {
|
|
280
|
+
issues.push({
|
|
281
|
+
packageId,
|
|
282
|
+
side: "",
|
|
283
|
+
code: "provider-introspection-incomplete",
|
|
284
|
+
message: `${packageId}: ${note}`
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function collectDiLabelParityIssuesForPackage({ packageEntry, packageInsights }) {
|
|
291
|
+
const packageId = String(packageEntry?.packageId || "").trim();
|
|
292
|
+
const descriptor = ensureObject(packageEntry?.descriptor);
|
|
293
|
+
const metadataApiSummary = ensureObject(ensureObject(descriptor.metadata).apiSummary);
|
|
294
|
+
const descriptorTokenSummary = ensureObject(metadataApiSummary.containerTokens);
|
|
295
|
+
const bindingSections = ensureObject(ensureObject(packageInsights).containerBindings);
|
|
296
|
+
const issues = [];
|
|
297
|
+
const sides = ["server", "client"];
|
|
298
|
+
|
|
299
|
+
collectProviderIntrospectionIssues({ packageId, packageInsights, issues });
|
|
300
|
+
|
|
301
|
+
for (const side of sides) {
|
|
302
|
+
const declaredTokens = collectDescriptorContainerTokens({
|
|
303
|
+
packageId,
|
|
304
|
+
side,
|
|
305
|
+
values: descriptorTokenSummary[side],
|
|
306
|
+
issues
|
|
307
|
+
});
|
|
308
|
+
const usedTokens = collectUsedContainerTokens({
|
|
309
|
+
packageId,
|
|
310
|
+
side,
|
|
311
|
+
bindings: bindingSections[side],
|
|
312
|
+
issues
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
for (const token of sortStrings([...usedTokens])) {
|
|
316
|
+
if (!declaredTokens.has(token)) {
|
|
317
|
+
issues.push({
|
|
318
|
+
packageId,
|
|
319
|
+
side,
|
|
320
|
+
code: "binding-token-undeclared",
|
|
321
|
+
token,
|
|
322
|
+
message: `${packageId} (${side}): token is used by providers but missing from metadata.apiSummary.containerTokens.${side}: ${token}.`
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
for (const token of sortStrings([...declaredTokens])) {
|
|
327
|
+
if (!usedTokens.has(token)) {
|
|
328
|
+
issues.push({
|
|
329
|
+
packageId,
|
|
330
|
+
side,
|
|
331
|
+
code: "descriptor-token-unused",
|
|
332
|
+
token,
|
|
333
|
+
message: `${packageId} (${side}): token is declared in metadata.apiSummary.containerTokens.${side} but never bound by providers: ${token}.`
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return issues;
|
|
340
|
+
}
|
|
180
341
|
|
|
181
342
|
async function commandList({ positional, options, cwd, stdout }) {
|
|
182
343
|
const packageRegistry = await loadPackageRegistry();
|
|
@@ -207,8 +368,9 @@ function createCommandHandlers(deps) {
|
|
|
207
368
|
const mode = String(positional[0] || "").trim();
|
|
208
369
|
const shouldListBundles = !mode || mode === "bundles";
|
|
209
370
|
const shouldListPackages = !mode || mode === "packages";
|
|
210
|
-
|
|
211
|
-
|
|
371
|
+
const shouldListGenerators = !mode || mode === "generators";
|
|
372
|
+
|
|
373
|
+
if (!shouldListBundles && !shouldListPackages && !shouldListGenerators) {
|
|
212
374
|
throw createCliError(`Unknown list mode: ${mode}`, { showUsage: true });
|
|
213
375
|
}
|
|
214
376
|
|
|
@@ -238,8 +400,11 @@ function createCommandHandlers(deps) {
|
|
|
238
400
|
if (lines.length > 0) {
|
|
239
401
|
lines.push("");
|
|
240
402
|
}
|
|
241
|
-
lines.push(color.heading("Available packages:"));
|
|
242
|
-
const packageIds = sortStrings([...packageRegistry.keys()])
|
|
403
|
+
lines.push(color.heading("Available runtime packages:"));
|
|
404
|
+
const packageIds = sortStrings([...packageRegistry.keys()].filter((packageId) => {
|
|
405
|
+
const packageEntry = packageRegistry.get(packageId);
|
|
406
|
+
return resolvePackageKind(packageEntry) === "runtime";
|
|
407
|
+
}));
|
|
243
408
|
for (const packageId of packageIds) {
|
|
244
409
|
const packageEntry = packageRegistry.get(packageId);
|
|
245
410
|
const installedLabel = installedPackages.has(packageId) ? " (installed)" : "";
|
|
@@ -281,6 +446,24 @@ function createCommandHandlers(deps) {
|
|
|
281
446
|
}
|
|
282
447
|
}
|
|
283
448
|
}
|
|
449
|
+
|
|
450
|
+
if (shouldListGenerators) {
|
|
451
|
+
if (lines.length > 0) {
|
|
452
|
+
lines.push("");
|
|
453
|
+
}
|
|
454
|
+
lines.push(color.heading("Available generators:"));
|
|
455
|
+
const packageIds = sortStrings([...packageRegistry.keys()].filter((packageId) => {
|
|
456
|
+
const packageEntry = packageRegistry.get(packageId);
|
|
457
|
+
return resolvePackageKind(packageEntry) === "generator";
|
|
458
|
+
}));
|
|
459
|
+
for (const packageId of packageIds) {
|
|
460
|
+
const packageEntry = packageRegistry.get(packageId);
|
|
461
|
+
const installedLabel = installedPackages.has(packageId) ? " (installed)" : "";
|
|
462
|
+
lines.push(
|
|
463
|
+
`- ${color.item(packageId)} ${color.version(`(${packageEntry.version})`)}${installedLabel ? color.installed(installedLabel) : ""}`
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
284
467
|
|
|
285
468
|
if (options.json) {
|
|
286
469
|
const payload = {
|
|
@@ -299,14 +482,42 @@ function createCommandHandlers(deps) {
|
|
|
299
482
|
})
|
|
300
483
|
: [],
|
|
301
484
|
packages: shouldListPackages
|
|
485
|
+
? sortStrings([...packageRegistry.keys()])
|
|
486
|
+
.filter((packageId) => resolvePackageKind(packageRegistry.get(packageId)) === "runtime")
|
|
487
|
+
.map((packageId) => {
|
|
488
|
+
const packageEntry = packageRegistry.get(packageId);
|
|
489
|
+
return {
|
|
490
|
+
packageId,
|
|
491
|
+
version: packageEntry.version,
|
|
492
|
+
installed: installedPackages.has(packageId)
|
|
493
|
+
};
|
|
494
|
+
})
|
|
495
|
+
: [],
|
|
496
|
+
runtimePackages: shouldListPackages
|
|
302
497
|
? sortStrings([...packageRegistry.keys()]).map((packageId) => {
|
|
303
498
|
const packageEntry = packageRegistry.get(packageId);
|
|
499
|
+
if (resolvePackageKind(packageEntry) !== "runtime") {
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
304
502
|
return {
|
|
305
503
|
packageId,
|
|
306
504
|
version: packageEntry.version,
|
|
307
505
|
installed: installedPackages.has(packageId)
|
|
308
506
|
};
|
|
309
|
-
})
|
|
507
|
+
}).filter(Boolean)
|
|
508
|
+
: [],
|
|
509
|
+
generators: shouldListGenerators
|
|
510
|
+
? sortStrings([...packageRegistry.keys()]).map((packageId) => {
|
|
511
|
+
const packageEntry = packageRegistry.get(packageId);
|
|
512
|
+
if (resolvePackageKind(packageEntry) !== "generator") {
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
packageId,
|
|
517
|
+
version: packageEntry.version,
|
|
518
|
+
installed: installedPackages.has(packageId)
|
|
519
|
+
};
|
|
520
|
+
}).filter(Boolean)
|
|
310
521
|
: [],
|
|
311
522
|
installedLocalPackages: shouldListPackages
|
|
312
523
|
? installedLocalPackageIds.map((packageId) => {
|
|
@@ -1103,10 +1314,16 @@ function createCommandHandlers(deps) {
|
|
|
1103
1314
|
}
|
|
1104
1315
|
|
|
1105
1316
|
async function commandAdd({ positional, options, cwd, io }) {
|
|
1317
|
+
const invocationMode = options?.commandMode === "generate" ? "generate" : "add";
|
|
1106
1318
|
const targetType = String(positional[0] || "").trim();
|
|
1107
1319
|
const targetId = String(positional[1] || "").trim();
|
|
1108
1320
|
|
|
1109
1321
|
if (!targetType || !targetId) {
|
|
1322
|
+
if (invocationMode === "generate") {
|
|
1323
|
+
throw createCliError("generate requires a package id (generate <packageId>).", {
|
|
1324
|
+
showUsage: true
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1110
1327
|
throw createCliError("add requires target type and id (add bundle <id> | add package <id>).", {
|
|
1111
1328
|
showUsage: true
|
|
1112
1329
|
});
|
|
@@ -1114,6 +1331,11 @@ function createCommandHandlers(deps) {
|
|
|
1114
1331
|
if (targetType !== "bundle" && targetType !== "package") {
|
|
1115
1332
|
throw createCliError(`Unsupported add target type: ${targetType}`, { showUsage: true });
|
|
1116
1333
|
}
|
|
1334
|
+
if (invocationMode === "generate" && targetType !== "package") {
|
|
1335
|
+
throw createCliError("generate requires a package id (generate <packageId>).", {
|
|
1336
|
+
showUsage: true
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1117
1339
|
|
|
1118
1340
|
const appRoot = await resolveAppRootFromCwd(cwd);
|
|
1119
1341
|
const packageRegistry = await loadPackageRegistry();
|
|
@@ -1123,10 +1345,11 @@ function createCommandHandlers(deps) {
|
|
|
1123
1345
|
const { packageJsonPath, packageJson } = await loadAppPackageJson(appRoot);
|
|
1124
1346
|
const { lockPath, lock } = await loadLockFile(appRoot);
|
|
1125
1347
|
let resolvedTargetPackageId = targetType === "package" ? resolvePackageIdInput(targetId, combinedPackageRegistry) : "";
|
|
1126
|
-
if (targetType === "package"
|
|
1348
|
+
if (targetType === "package") {
|
|
1349
|
+
const packageIdForNodeModulesLookup = resolvedTargetPackageId || targetId;
|
|
1127
1350
|
const installedNodeModuleEntry = await resolveInstalledNodeModulePackageEntry({
|
|
1128
1351
|
appRoot,
|
|
1129
|
-
packageId:
|
|
1352
|
+
packageId: packageIdForNodeModulesLookup
|
|
1130
1353
|
});
|
|
1131
1354
|
if (installedNodeModuleEntry) {
|
|
1132
1355
|
combinedPackageRegistry.set(installedNodeModuleEntry.packageId, installedNodeModuleEntry);
|
|
@@ -1157,6 +1380,17 @@ function createCommandHandlers(deps) {
|
|
|
1157
1380
|
if (!targetPackageEntry) {
|
|
1158
1381
|
throw createCliError(`Unknown package: ${targetId}`);
|
|
1159
1382
|
}
|
|
1383
|
+
const packageKind = resolvePackageKind(targetPackageEntry);
|
|
1384
|
+
if (invocationMode === "add" && packageKind === "generator") {
|
|
1385
|
+
throw createCliError(
|
|
1386
|
+
`Package ${resolvedTargetPackageId} is a generator. Use: jskit generate ${resolvedTargetPackageId}`
|
|
1387
|
+
);
|
|
1388
|
+
}
|
|
1389
|
+
if (invocationMode === "generate" && packageKind !== "generator") {
|
|
1390
|
+
throw createCliError(
|
|
1391
|
+
`Package ${resolvedTargetPackageId} is a runtime package. Use: jskit add package ${resolvedTargetPackageId}`
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1160
1394
|
validateInlineOptionsForPackage(targetPackageEntry, options.inlineOptions);
|
|
1161
1395
|
}
|
|
1162
1396
|
|
|
@@ -1164,6 +1398,17 @@ function createCommandHandlers(deps) {
|
|
|
1164
1398
|
targetPackageIds,
|
|
1165
1399
|
combinedPackageRegistry
|
|
1166
1400
|
);
|
|
1401
|
+
if (invocationMode === "add" && targetType === "bundle") {
|
|
1402
|
+
const bundledGenerators = resolvedPackageIds.filter((packageId) => {
|
|
1403
|
+
const packageEntry = combinedPackageRegistry.get(packageId);
|
|
1404
|
+
return resolvePackageKind(packageEntry) === "generator";
|
|
1405
|
+
});
|
|
1406
|
+
if (bundledGenerators.length > 0) {
|
|
1407
|
+
throw createCliError(
|
|
1408
|
+
`Bundle ${targetId} includes generator package(s): ${bundledGenerators.join(", ")}. Use: jskit generate <packageId>`
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1167
1412
|
const plannedInstalledPackageIds = sortStrings([
|
|
1168
1413
|
...new Set([
|
|
1169
1414
|
...Object.keys(ensureObject(lock.installedPackages)).map((value) => String(value || "").trim()).filter(Boolean),
|
|
@@ -1173,7 +1418,7 @@ function createCommandHandlers(deps) {
|
|
|
1173
1418
|
validatePlannedCapabilityClosure(
|
|
1174
1419
|
plannedInstalledPackageIds,
|
|
1175
1420
|
combinedPackageRegistry,
|
|
1176
|
-
|
|
1421
|
+
`${invocationMode} ${targetType} ${targetId}`
|
|
1177
1422
|
);
|
|
1178
1423
|
|
|
1179
1424
|
if (targetType === "bundle") {
|
|
@@ -1252,14 +1497,18 @@ function createCommandHandlers(deps) {
|
|
|
1252
1497
|
validatePlannedCapabilityClosure(
|
|
1253
1498
|
postInstallPackageIds,
|
|
1254
1499
|
combinedPackageRegistry,
|
|
1255
|
-
|
|
1500
|
+
`${invocationMode} ${targetType} ${targetId}`
|
|
1256
1501
|
);
|
|
1257
1502
|
}
|
|
1258
1503
|
|
|
1259
1504
|
const finalResolvedPackageIds = sortStrings([...resolvedPackageIds, ...adoptedPackageIds]);
|
|
1260
1505
|
|
|
1261
1506
|
const touchedFileList = sortStrings([...touchedFiles]);
|
|
1262
|
-
const successLabel =
|
|
1507
|
+
const successLabel = invocationMode === "generate"
|
|
1508
|
+
? "Generated with"
|
|
1509
|
+
: targetType === "bundle"
|
|
1510
|
+
? "Added bundle"
|
|
1511
|
+
: "Added package";
|
|
1263
1512
|
const installWarnings = installedPackageRecords
|
|
1264
1513
|
.flatMap((record) => ensureArray(ensureObject(record).warnings))
|
|
1265
1514
|
.map((value) => String(value || "").trim())
|
|
@@ -1275,7 +1524,7 @@ function createCommandHandlers(deps) {
|
|
|
1275
1524
|
|
|
1276
1525
|
if (options.json) {
|
|
1277
1526
|
io.stdout.write(`${JSON.stringify({
|
|
1278
|
-
targetType,
|
|
1527
|
+
targetType: invocationMode === "generate" ? "generator" : targetType,
|
|
1279
1528
|
targetId,
|
|
1280
1529
|
resolvedPackages: finalResolvedPackageIds,
|
|
1281
1530
|
touchedFiles: touchedFileList,
|
|
@@ -1310,6 +1559,32 @@ function createCommandHandlers(deps) {
|
|
|
1310
1559
|
|
|
1311
1560
|
return 0;
|
|
1312
1561
|
}
|
|
1562
|
+
|
|
1563
|
+
async function commandGenerate({ positional, options, cwd, io }) {
|
|
1564
|
+
const firstToken = String(positional[0] || "").trim();
|
|
1565
|
+
const secondToken = String(positional[1] || "").trim();
|
|
1566
|
+
if (firstToken === "bundle") {
|
|
1567
|
+
throw createCliError("generate supports packages only (generate <packageId>).", {
|
|
1568
|
+
showUsage: true
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
const targetId = firstToken === "package" ? secondToken : firstToken;
|
|
1572
|
+
if (!targetId) {
|
|
1573
|
+
throw createCliError("generate requires a package id (generate <packageId>).", {
|
|
1574
|
+
showUsage: true
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
return commandAdd({
|
|
1579
|
+
positional: ["package", targetId],
|
|
1580
|
+
options: {
|
|
1581
|
+
...options,
|
|
1582
|
+
commandMode: "generate"
|
|
1583
|
+
},
|
|
1584
|
+
cwd,
|
|
1585
|
+
io
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1313
1588
|
|
|
1314
1589
|
async function commandUpdate({ positional, options, cwd, io }) {
|
|
1315
1590
|
const targetType = String(positional[0] || "").trim();
|
|
@@ -1455,7 +1730,7 @@ function createCommandHandlers(deps) {
|
|
|
1455
1730
|
io.stdout.write(`- ${touchedFile}\n`);
|
|
1456
1731
|
}
|
|
1457
1732
|
io.stdout.write(`Lock file: ${normalizeRelativePath(appRoot, lockPath)}\n`);
|
|
1458
|
-
if (migrationWarnings.length > 0) {
|
|
1733
|
+
if (options.verbose && migrationWarnings.length > 0) {
|
|
1459
1734
|
io.stdout.write(`Warnings (${migrationWarnings.length}):\n`);
|
|
1460
1735
|
for (const warning of migrationWarnings) {
|
|
1461
1736
|
io.stdout.write(`- ${warning}\n`);
|
|
@@ -1760,19 +2035,58 @@ function createCommandHandlers(deps) {
|
|
|
1760
2035
|
async function commandLintDescriptors({ options, stdout }) {
|
|
1761
2036
|
const packageRegistry = await loadPackageRegistry();
|
|
1762
2037
|
const bundleRegistry = await loadBundleRegistry();
|
|
2038
|
+
const shouldCheckDiLabels = options.checkDiLabels === true;
|
|
2039
|
+
let diLabelIssues = [];
|
|
2040
|
+
if (shouldCheckDiLabels) {
|
|
2041
|
+
const issues = [];
|
|
2042
|
+
for (const packageId of sortStrings([...packageRegistry.keys()])) {
|
|
2043
|
+
const packageEntry = packageRegistry.get(packageId);
|
|
2044
|
+
if (!packageEntry) {
|
|
2045
|
+
continue;
|
|
2046
|
+
}
|
|
2047
|
+
const packageInsights = await inspectPackageOfferings({ packageEntry });
|
|
2048
|
+
issues.push(...collectDiLabelParityIssuesForPackage({ packageEntry, packageInsights }));
|
|
2049
|
+
}
|
|
2050
|
+
diLabelIssues = issues;
|
|
2051
|
+
}
|
|
1763
2052
|
const payload = {
|
|
1764
2053
|
packageCount: packageRegistry.size,
|
|
1765
2054
|
bundleCount: bundleRegistry.size,
|
|
1766
2055
|
packages: sortStrings([...packageRegistry.keys()]),
|
|
1767
|
-
bundles: sortStrings([...bundleRegistry.keys()])
|
|
2056
|
+
bundles: sortStrings([...bundleRegistry.keys()]),
|
|
2057
|
+
diLabelCheck: shouldCheckDiLabels
|
|
2058
|
+
? {
|
|
2059
|
+
enabled: true,
|
|
2060
|
+
issueCount: diLabelIssues.length,
|
|
2061
|
+
issues: diLabelIssues
|
|
2062
|
+
}
|
|
2063
|
+
: {
|
|
2064
|
+
enabled: false
|
|
2065
|
+
}
|
|
1768
2066
|
};
|
|
1769
2067
|
|
|
1770
2068
|
if (options.json) {
|
|
1771
2069
|
stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
1772
2070
|
} else {
|
|
1773
|
-
|
|
2071
|
+
const descriptorStatus = shouldCheckDiLabels && diLabelIssues.length > 0 ? "failed" : "passed";
|
|
2072
|
+
stdout.write(`Descriptor lint ${descriptorStatus}.\n`);
|
|
1774
2073
|
stdout.write(`Packages: ${payload.packageCount}\n`);
|
|
1775
2074
|
stdout.write(`Bundles: ${payload.bundleCount}\n`);
|
|
2075
|
+
if (shouldCheckDiLabels) {
|
|
2076
|
+
if (diLabelIssues.length === 0) {
|
|
2077
|
+
stdout.write("DI label parity check passed.\n");
|
|
2078
|
+
} else {
|
|
2079
|
+
stdout.write(`DI label parity check failed (${diLabelIssues.length} issue(s)).\n`);
|
|
2080
|
+
for (const issue of diLabelIssues) {
|
|
2081
|
+
const code = String(issue?.code || "").trim();
|
|
2082
|
+
const codeLabel = code ? `[${code}] ` : "";
|
|
2083
|
+
stdout.write(`- ${codeLabel}${String(issue?.message || "").trim()}\n`);
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
if (shouldCheckDiLabels && diLabelIssues.length > 0) {
|
|
2089
|
+
return 1;
|
|
1776
2090
|
}
|
|
1777
2091
|
return 0;
|
|
1778
2092
|
}
|
|
@@ -1782,6 +2096,7 @@ function createCommandHandlers(deps) {
|
|
|
1782
2096
|
commandShow,
|
|
1783
2097
|
commandCreate,
|
|
1784
2098
|
commandAdd,
|
|
2099
|
+
commandGenerate,
|
|
1785
2100
|
commandMigrations,
|
|
1786
2101
|
commandPosition,
|
|
1787
2102
|
commandUpdate,
|
|
@@ -8,66 +8,6 @@ import { createCliError } from "./cliError.js";
|
|
|
8
8
|
const CLI_PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
9
9
|
const require = createRequire(import.meta.url);
|
|
10
10
|
|
|
11
|
-
function isWorkspaceRoot(candidateRoot) {
|
|
12
|
-
if (!candidateRoot) {
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
return (
|
|
16
|
-
existsSync(path.join(candidateRoot, "packages")) &&
|
|
17
|
-
existsSync(path.join(candidateRoot, "packages", "kernel")) &&
|
|
18
|
-
existsSync(path.join(candidateRoot, "tooling", "jskit-cli"))
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function collectAncestorDirectories(startDirectory) {
|
|
23
|
-
const ancestors = [];
|
|
24
|
-
let current = path.resolve(startDirectory);
|
|
25
|
-
while (true) {
|
|
26
|
-
ancestors.push(current);
|
|
27
|
-
const parent = path.dirname(current);
|
|
28
|
-
if (parent === current) {
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
current = parent;
|
|
32
|
-
}
|
|
33
|
-
return ancestors;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function resolveWorkspaceRoot() {
|
|
37
|
-
const candidates = [];
|
|
38
|
-
const seen = new Set();
|
|
39
|
-
const appendCandidate = (candidatePath) => {
|
|
40
|
-
const raw = String(candidatePath || "").trim();
|
|
41
|
-
if (!raw) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
const absolute = path.resolve(raw);
|
|
45
|
-
if (seen.has(absolute)) {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
seen.add(absolute);
|
|
49
|
-
candidates.push(absolute);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
appendCandidate(process.env.JSKIT_REPO_ROOT);
|
|
53
|
-
appendCandidate(path.resolve(CLI_PACKAGE_ROOT, "../.."));
|
|
54
|
-
appendCandidate(CLI_PACKAGE_ROOT);
|
|
55
|
-
|
|
56
|
-
const cwdAncestors = collectAncestorDirectories(process.cwd());
|
|
57
|
-
for (const ancestor of cwdAncestors) {
|
|
58
|
-
appendCandidate(ancestor);
|
|
59
|
-
appendCandidate(path.join(ancestor, "jskit-ai"));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
for (const candidate of candidates) {
|
|
63
|
-
if (isWorkspaceRoot(candidate)) {
|
|
64
|
-
return candidate;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return "";
|
|
69
|
-
}
|
|
70
|
-
|
|
71
11
|
function resolveCatalogPackagesPath() {
|
|
72
12
|
const explicitPath = String(process.env.JSKIT_CATALOG_PACKAGES_PATH || "").trim();
|
|
73
13
|
if (explicitPath) {
|
|
@@ -92,15 +32,11 @@ function resolveCatalogPackagesPath() {
|
|
|
92
32
|
);
|
|
93
33
|
}
|
|
94
34
|
|
|
95
|
-
const WORKSPACE_ROOT = resolveWorkspaceRoot();
|
|
96
|
-
const MODULES_ROOT = WORKSPACE_ROOT ? path.join(WORKSPACE_ROOT, "packages") : "";
|
|
97
35
|
const BUNDLES_ROOT = path.join(CLI_PACKAGE_ROOT, "bundles");
|
|
98
36
|
const CATALOG_PACKAGES_PATH = resolveCatalogPackagesPath();
|
|
99
37
|
|
|
100
38
|
export {
|
|
101
39
|
CLI_PACKAGE_ROOT,
|
|
102
|
-
WORKSPACE_ROOT,
|
|
103
|
-
MODULES_ROOT,
|
|
104
40
|
BUNDLES_ROOT,
|
|
105
41
|
CATALOG_PACKAGES_PATH
|
|
106
42
|
};
|
package/src/server/runCli.js
CHANGED
|
@@ -64,6 +64,14 @@ function createRunCli({
|
|
|
64
64
|
io: { stdin, stdout, stderr }
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
|
+
if (command === "generate") {
|
|
68
|
+
return await commandHandlers.commandGenerate({
|
|
69
|
+
positional,
|
|
70
|
+
options,
|
|
71
|
+
cwd,
|
|
72
|
+
io: { stdin, stdout, stderr }
|
|
73
|
+
});
|
|
74
|
+
}
|
|
67
75
|
if (command === "position") {
|
|
68
76
|
return await commandHandlers.commandPosition({
|
|
69
77
|
positional,
|