@jskit-ai/jskit-cli 0.2.25 → 0.2.27
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 +438 -121
- package/src/server/commandHandlers.js +329 -14
- package/src/server/pathResolution.js +0 -64
- package/src/server/runCli.js +8 -0
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,13 +53,15 @@ 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();
|
|
64
62
|
const MATERIALIZED_PACKAGE_TEMP_DIRECTORIES = new Set();
|
|
63
|
+
const LOCAL_WORKSPACE_PACKAGE_ROOTS = new Map();
|
|
64
|
+
let LOCAL_WORKSPACE_PACKAGE_ID_INDEX = null;
|
|
65
65
|
const BUILTIN_CAPABILITY_PROVIDERS = Object.freeze({
|
|
66
66
|
"runtime.actions": Object.freeze(["@jskit-ai/kernel"])
|
|
67
67
|
});
|
|
@@ -95,6 +95,20 @@ function normalizeMutationExtension(value) {
|
|
|
95
95
|
return `.${extension}`;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
function normalizeTemplateContextRecord(value) {
|
|
99
|
+
const record = ensureObject(value);
|
|
100
|
+
const entrypoint = String(record.entrypoint || "").trim();
|
|
101
|
+
const exportName = String(record.export || "").trim();
|
|
102
|
+
if (!entrypoint && !exportName) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return Object.freeze({
|
|
107
|
+
entrypoint,
|
|
108
|
+
export: exportName || "buildTemplateContext"
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
98
112
|
function normalizeFileMutationRecord(value) {
|
|
99
113
|
const record = ensureObject(value);
|
|
100
114
|
const op = String(record.op || "copy-file").trim().toLowerCase() || "copy-file";
|
|
@@ -111,6 +125,7 @@ function normalizeFileMutationRecord(value) {
|
|
|
111
125
|
id: String(record.id || "").trim(),
|
|
112
126
|
category: String(record.category || "").trim(),
|
|
113
127
|
reason: String(record.reason || "").trim(),
|
|
128
|
+
templateContext: normalizeTemplateContextRecord(record.templateContext),
|
|
114
129
|
when: normalizeMutationWhen(record.when)
|
|
115
130
|
};
|
|
116
131
|
}
|
|
@@ -184,6 +199,44 @@ function normalizeWhenSourceValue(value) {
|
|
|
184
199
|
return "";
|
|
185
200
|
}
|
|
186
201
|
|
|
202
|
+
function normalizeWhenComparisonValue(value) {
|
|
203
|
+
const normalizedValue = normalizeWhenSourceValue(value);
|
|
204
|
+
if (!normalizedValue.includes(",")) {
|
|
205
|
+
return normalizedValue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return normalizedValue
|
|
209
|
+
.split(",")
|
|
210
|
+
.map((entry) => String(entry || "").trim())
|
|
211
|
+
.filter(Boolean)
|
|
212
|
+
.join(",");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function splitWhenComparisonTokens(value) {
|
|
216
|
+
return normalizeWhenComparisonValue(value)
|
|
217
|
+
.split(",")
|
|
218
|
+
.map((entry) => String(entry || "").trim())
|
|
219
|
+
.filter(Boolean);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function matchesWhenComparisonValue(optionValue, optionTokens, expectedValue) {
|
|
223
|
+
const expected = normalizeWhenComparisonValue(expectedValue);
|
|
224
|
+
if (!expected) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const expectedTokens = splitWhenComparisonTokens(expected);
|
|
229
|
+
if (expectedTokens.length > 1) {
|
|
230
|
+
return optionValue === expected;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (optionTokens.length > 1) {
|
|
234
|
+
return optionTokens.includes(expectedTokens[0]);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return optionValue === expectedTokens[0];
|
|
238
|
+
}
|
|
239
|
+
|
|
187
240
|
function resolveWhenConfigValue(configContext = {}, configPath = "") {
|
|
188
241
|
const normalizedPath = String(configPath || "").trim();
|
|
189
242
|
if (!normalizedPath) {
|
|
@@ -244,11 +297,12 @@ function shouldApplyMutationWhen(
|
|
|
244
297
|
const sourceValue = optionName
|
|
245
298
|
? readObjectPath(options, optionName)
|
|
246
299
|
: resolveWhenConfigValue(configContext, configPath);
|
|
247
|
-
const optionValue =
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
const
|
|
300
|
+
const optionValue = normalizeWhenComparisonValue(sourceValue);
|
|
301
|
+
const optionTokens = splitWhenComparisonTokens(optionValue);
|
|
302
|
+
const equals = normalizeWhenComparisonValue(when.equals);
|
|
303
|
+
const notEquals = normalizeWhenComparisonValue(when.notEquals);
|
|
304
|
+
const includes = ensureArray(when.includes).map((entry) => normalizeWhenComparisonValue(entry)).filter(Boolean);
|
|
305
|
+
const excludes = ensureArray(when.excludes).map((entry) => normalizeWhenComparisonValue(entry)).filter(Boolean);
|
|
252
306
|
|
|
253
307
|
if (equals && optionValue !== equals) {
|
|
254
308
|
return false;
|
|
@@ -256,10 +310,10 @@ function shouldApplyMutationWhen(
|
|
|
256
310
|
if (notEquals && optionValue === notEquals) {
|
|
257
311
|
return false;
|
|
258
312
|
}
|
|
259
|
-
if (includes.length > 0 && !includes.
|
|
313
|
+
if (includes.length > 0 && !includes.some((entry) => matchesWhenComparisonValue(optionValue, optionTokens, entry))) {
|
|
260
314
|
return false;
|
|
261
315
|
}
|
|
262
|
-
if (excludes.length > 0 && excludes.
|
|
316
|
+
if (excludes.length > 0 && excludes.some((entry) => matchesWhenComparisonValue(optionValue, optionTokens, entry))) {
|
|
263
317
|
return false;
|
|
264
318
|
}
|
|
265
319
|
|
|
@@ -1113,16 +1167,16 @@ function removeEnvValue(content, key, expectedValue, previous) {
|
|
|
1113
1167
|
};
|
|
1114
1168
|
}
|
|
1115
1169
|
|
|
1116
|
-
function
|
|
1117
|
-
const normalized = String(rawValue || "")
|
|
1118
|
-
.trim()
|
|
1119
|
-
.toLowerCase();
|
|
1170
|
+
function normalizePackageKind(rawValue, descriptorPath) {
|
|
1171
|
+
const normalized = String(rawValue || "").trim().toLowerCase();
|
|
1120
1172
|
if (!normalized) {
|
|
1121
|
-
|
|
1173
|
+
throw createCliError(
|
|
1174
|
+
`Invalid package descriptor at ${descriptorPath}: missing kind (expected ${PACKAGE_KINDS.join(" | ")}).`
|
|
1175
|
+
);
|
|
1122
1176
|
}
|
|
1123
|
-
if (!
|
|
1177
|
+
if (!PACKAGE_KINDS.includes(normalized)) {
|
|
1124
1178
|
throw createCliError(
|
|
1125
|
-
`Invalid package descriptor at ${descriptorPath}:
|
|
1179
|
+
`Invalid package descriptor at ${descriptorPath}: kind must be one of: ${PACKAGE_KINDS.join(", ")}.`
|
|
1126
1180
|
);
|
|
1127
1181
|
}
|
|
1128
1182
|
return normalized;
|
|
@@ -1177,13 +1231,13 @@ function validatePackageDescriptorShape(descriptor, descriptorPath) {
|
|
|
1177
1231
|
|
|
1178
1232
|
return {
|
|
1179
1233
|
...normalized,
|
|
1180
|
-
|
|
1234
|
+
kind: normalizePackageKind(normalized.kind, descriptorPath)
|
|
1181
1235
|
};
|
|
1182
1236
|
}
|
|
1183
1237
|
|
|
1184
|
-
function
|
|
1238
|
+
function isGeneratorPackageEntry(packageEntry) {
|
|
1185
1239
|
const descriptor = ensureObject(packageEntry?.descriptor);
|
|
1186
|
-
return String(descriptor.
|
|
1240
|
+
return String(descriptor.kind || "").trim().toLowerCase() === PACKAGE_KIND_GENERATOR;
|
|
1187
1241
|
}
|
|
1188
1242
|
|
|
1189
1243
|
function validateAppLocalPackageDescriptorShape(descriptor, descriptorPath, { expectedPackageId = "", fallbackVersion = "" } = {}) {
|
|
@@ -1208,7 +1262,8 @@ function validateAppLocalPackageDescriptorShape(descriptor, descriptorPath, { ex
|
|
|
1208
1262
|
return {
|
|
1209
1263
|
...normalized,
|
|
1210
1264
|
packageId,
|
|
1211
|
-
version
|
|
1265
|
+
version,
|
|
1266
|
+
kind: normalizePackageKind(normalized.kind, descriptorPath)
|
|
1212
1267
|
};
|
|
1213
1268
|
}
|
|
1214
1269
|
|
|
@@ -1277,85 +1332,6 @@ function validateBundleDescriptorShape(descriptor, descriptorPath) {
|
|
|
1277
1332
|
return normalized;
|
|
1278
1333
|
}
|
|
1279
1334
|
|
|
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
1335
|
async function loadAppLocalPackageRegistry(appRoot) {
|
|
1360
1336
|
const localPackagesRoot = path.join(appRoot, "packages");
|
|
1361
1337
|
if (!(await fileExists(localPackagesRoot))) {
|
|
@@ -1468,17 +1444,14 @@ async function loadCatalogPackageRegistry() {
|
|
|
1468
1444
|
}
|
|
1469
1445
|
|
|
1470
1446
|
async function loadPackageRegistry() {
|
|
1471
|
-
const workspaceRegistry = await loadWorkspacePackageRegistry();
|
|
1472
1447
|
const catalogRegistry = await loadCatalogPackageRegistry();
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
if (merged.size === 0) {
|
|
1448
|
+
if (catalogRegistry.size === 0) {
|
|
1476
1449
|
throw createCliError(
|
|
1477
|
-
"Unable to load package registry
|
|
1450
|
+
"Unable to load package registry from @jskit-ai/jskit-catalog. Install it alongside @jskit-ai/jskit-cli or set JSKIT_CATALOG_PACKAGES_PATH."
|
|
1478
1451
|
);
|
|
1479
1452
|
}
|
|
1480
1453
|
|
|
1481
|
-
return
|
|
1454
|
+
return catalogRegistry;
|
|
1482
1455
|
}
|
|
1483
1456
|
|
|
1484
1457
|
async function loadInstalledNodeModulePackageEntry({ appRoot, packageId }) {
|
|
@@ -1745,6 +1718,7 @@ function createLocalPackageDescriptorTemplate({ packageId, description }) {
|
|
|
1745
1718
|
packageVersion: 1,
|
|
1746
1719
|
packageId: "${packageId}",
|
|
1747
1720
|
version: "0.1.0",
|
|
1721
|
+
kind: "runtime",
|
|
1748
1722
|
description: ${JSON.stringify(String(description || ""))},
|
|
1749
1723
|
dependsOn: [
|
|
1750
1724
|
// "@jskit-ai/kernel"
|
|
@@ -3511,11 +3485,110 @@ async function materializePackageRootFromRegistry({ packageEntry, appRoot }) {
|
|
|
3511
3485
|
return packageRoot;
|
|
3512
3486
|
}
|
|
3513
3487
|
|
|
3488
|
+
async function resolvePackageRootFromNodeModules({ appRoot, packageId }) {
|
|
3489
|
+
const normalizedAppRoot = path.resolve(String(appRoot || "").trim());
|
|
3490
|
+
const normalizedPackageId = String(packageId || "").trim();
|
|
3491
|
+
if (!normalizedAppRoot || !normalizedPackageId) {
|
|
3492
|
+
return "";
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
const candidateRoot = path.resolve(normalizedAppRoot, "node_modules", ...normalizedPackageId.split("/"));
|
|
3496
|
+
const candidateDescriptorPath = path.join(candidateRoot, "package.descriptor.mjs");
|
|
3497
|
+
if (!(await fileExists(candidateDescriptorPath))) {
|
|
3498
|
+
return "";
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
return candidateRoot;
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
async function loadLocalWorkspacePackageIdIndex() {
|
|
3505
|
+
if (LOCAL_WORKSPACE_PACKAGE_ID_INDEX instanceof Map) {
|
|
3506
|
+
return LOCAL_WORKSPACE_PACKAGE_ID_INDEX;
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3509
|
+
const repoRoot = path.resolve(CLI_PACKAGE_ROOT, "..", "..");
|
|
3510
|
+
const parentDirectories = [
|
|
3511
|
+
path.join(repoRoot, "packages"),
|
|
3512
|
+
path.join(repoRoot, "tooling")
|
|
3513
|
+
];
|
|
3514
|
+
const packageIdIndex = new Map();
|
|
3515
|
+
|
|
3516
|
+
for (const parentDirectory of parentDirectories) {
|
|
3517
|
+
if (!(await fileExists(parentDirectory))) {
|
|
3518
|
+
continue;
|
|
3519
|
+
}
|
|
3520
|
+
|
|
3521
|
+
const entries = await readdir(parentDirectory, { withFileTypes: true });
|
|
3522
|
+
for (const entry of entries) {
|
|
3523
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) {
|
|
3524
|
+
continue;
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
const candidateRoot = path.join(parentDirectory, entry.name);
|
|
3528
|
+
const packageJsonPath = path.join(candidateRoot, "package.json");
|
|
3529
|
+
const descriptorPath = path.join(candidateRoot, "package.descriptor.mjs");
|
|
3530
|
+
if (!(await fileExists(packageJsonPath)) || !(await fileExists(descriptorPath))) {
|
|
3531
|
+
continue;
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3534
|
+
let packageJson = {};
|
|
3535
|
+
try {
|
|
3536
|
+
packageJson = await readJsonFile(packageJsonPath);
|
|
3537
|
+
} catch {
|
|
3538
|
+
continue;
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
const packageId = String(packageJson?.name || "").trim();
|
|
3542
|
+
if (!packageId.startsWith("@jskit-ai/")) {
|
|
3543
|
+
continue;
|
|
3544
|
+
}
|
|
3545
|
+
if (packageIdIndex.has(packageId)) {
|
|
3546
|
+
continue;
|
|
3547
|
+
}
|
|
3548
|
+
packageIdIndex.set(packageId, candidateRoot);
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3552
|
+
LOCAL_WORKSPACE_PACKAGE_ID_INDEX = packageIdIndex;
|
|
3553
|
+
return packageIdIndex;
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
async function resolvePackageRootFromLocalWorkspace({ packageId }) {
|
|
3557
|
+
const normalizedPackageId = String(packageId || "").trim();
|
|
3558
|
+
if (!normalizedPackageId.startsWith("@jskit-ai/")) {
|
|
3559
|
+
return "";
|
|
3560
|
+
}
|
|
3561
|
+
if (LOCAL_WORKSPACE_PACKAGE_ROOTS.has(normalizedPackageId)) {
|
|
3562
|
+
return LOCAL_WORKSPACE_PACKAGE_ROOTS.get(normalizedPackageId);
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
const packageIdIndex = await loadLocalWorkspacePackageIdIndex();
|
|
3566
|
+
const packageRoot = String(packageIdIndex.get(normalizedPackageId) || "").trim();
|
|
3567
|
+
LOCAL_WORKSPACE_PACKAGE_ROOTS.set(normalizedPackageId, packageRoot);
|
|
3568
|
+
return packageRoot;
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3514
3571
|
async function resolvePackageTemplateRoot({ packageEntry, appRoot }) {
|
|
3515
3572
|
const packageRoot = String(packageEntry?.rootDir || "").trim();
|
|
3516
3573
|
if (packageRoot) {
|
|
3517
3574
|
return packageRoot;
|
|
3518
3575
|
}
|
|
3576
|
+
|
|
3577
|
+
const installedPackageRoot = await resolvePackageRootFromNodeModules({
|
|
3578
|
+
appRoot,
|
|
3579
|
+
packageId: packageEntry?.packageId
|
|
3580
|
+
});
|
|
3581
|
+
if (installedPackageRoot) {
|
|
3582
|
+
return installedPackageRoot;
|
|
3583
|
+
}
|
|
3584
|
+
|
|
3585
|
+
const localWorkspacePackageRoot = await resolvePackageRootFromLocalWorkspace({
|
|
3586
|
+
packageId: packageEntry?.packageId
|
|
3587
|
+
});
|
|
3588
|
+
if (localWorkspacePackageRoot) {
|
|
3589
|
+
return localWorkspacePackageRoot;
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3519
3592
|
return await materializePackageRootFromRegistry({ packageEntry, appRoot });
|
|
3520
3593
|
}
|
|
3521
3594
|
|
|
@@ -3525,6 +3598,8 @@ async function cleanupMaterializedPackageRoots() {
|
|
|
3525
3598
|
}
|
|
3526
3599
|
MATERIALIZED_PACKAGE_TEMP_DIRECTORIES.clear();
|
|
3527
3600
|
MATERIALIZED_PACKAGE_ROOTS.clear();
|
|
3601
|
+
LOCAL_WORKSPACE_PACKAGE_ROOTS.clear();
|
|
3602
|
+
LOCAL_WORKSPACE_PACKAGE_ID_INDEX = null;
|
|
3528
3603
|
}
|
|
3529
3604
|
|
|
3530
3605
|
function interpolateFileMutationRecord(mutation, options, packageId) {
|
|
@@ -3544,20 +3619,134 @@ function interpolateFileMutationRecord(mutation, options, packageId) {
|
|
|
3544
3619
|
extension: interpolate(mutation.extension, "extension"),
|
|
3545
3620
|
id: interpolate(mutation.id, "id"),
|
|
3546
3621
|
category: interpolate(mutation.category, "category"),
|
|
3547
|
-
reason: interpolate(mutation.reason, "reason")
|
|
3622
|
+
reason: interpolate(mutation.reason, "reason"),
|
|
3623
|
+
templateContext: mutation.templateContext
|
|
3624
|
+
? {
|
|
3625
|
+
entrypoint: interpolate(mutation.templateContext.entrypoint, "templateContext.entrypoint"),
|
|
3626
|
+
export: interpolate(mutation.templateContext.export, "templateContext.export")
|
|
3627
|
+
}
|
|
3628
|
+
: null
|
|
3548
3629
|
};
|
|
3549
3630
|
}
|
|
3550
3631
|
|
|
3551
|
-
|
|
3632
|
+
function applyTemplateContextReplacements(sourceContent, replacements) {
|
|
3633
|
+
let output = String(sourceContent || "");
|
|
3634
|
+
for (const [placeholder, value] of Object.entries(ensureObject(replacements))) {
|
|
3635
|
+
const normalizedPlaceholder = String(placeholder || "");
|
|
3636
|
+
if (!normalizedPlaceholder) {
|
|
3637
|
+
continue;
|
|
3638
|
+
}
|
|
3639
|
+
output = output.split(normalizedPlaceholder).join(String(value == null ? "" : value));
|
|
3640
|
+
}
|
|
3641
|
+
return output;
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3644
|
+
async function copyTemplateFile(
|
|
3645
|
+
sourcePath,
|
|
3646
|
+
targetPath,
|
|
3647
|
+
options,
|
|
3648
|
+
packageId,
|
|
3649
|
+
interpolationKey,
|
|
3650
|
+
templateContextReplacements = null
|
|
3651
|
+
) {
|
|
3552
3652
|
const sourceContent = await readFile(sourcePath, "utf8");
|
|
3553
|
-
|
|
3653
|
+
let renderedContent = sourceContent.includes("${")
|
|
3554
3654
|
? interpolateOptionValue(sourceContent, options, packageId, interpolationKey)
|
|
3555
3655
|
: sourceContent;
|
|
3656
|
+
if (templateContextReplacements) {
|
|
3657
|
+
renderedContent = applyTemplateContextReplacements(renderedContent, templateContextReplacements);
|
|
3658
|
+
}
|
|
3556
3659
|
|
|
3557
3660
|
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
3558
3661
|
await writeFile(targetPath, renderedContent, "utf8");
|
|
3559
3662
|
}
|
|
3560
3663
|
|
|
3664
|
+
async function resolveTemplateContextReplacementsForMutation({
|
|
3665
|
+
packageEntry,
|
|
3666
|
+
mutation,
|
|
3667
|
+
options,
|
|
3668
|
+
appRoot,
|
|
3669
|
+
sourcePath,
|
|
3670
|
+
targetPaths
|
|
3671
|
+
} = {}) {
|
|
3672
|
+
const templateContext = ensureObject(mutation?.templateContext);
|
|
3673
|
+
const hasTemplateContext = Object.keys(templateContext).length > 0;
|
|
3674
|
+
const entrypoint = String(templateContext.entrypoint || "").trim();
|
|
3675
|
+
if (!hasTemplateContext) {
|
|
3676
|
+
return null;
|
|
3677
|
+
}
|
|
3678
|
+
if (!entrypoint) {
|
|
3679
|
+
throw createCliError(
|
|
3680
|
+
`Invalid files mutation in ${packageEntry.packageId}: templateContext.entrypoint is required when templateContext is set.`
|
|
3681
|
+
);
|
|
3682
|
+
}
|
|
3683
|
+
const exportName = String(templateContext.export || "").trim() || "buildTemplateContext";
|
|
3684
|
+
const resolvedEntrypointPath = resolveAppRelativePathWithinRoot(
|
|
3685
|
+
packageEntry.rootDir,
|
|
3686
|
+
entrypoint,
|
|
3687
|
+
`${packageEntry.packageId} files mutation templateContext.entrypoint`
|
|
3688
|
+
);
|
|
3689
|
+
const absoluteEntrypointPath = resolvedEntrypointPath.absolutePath;
|
|
3690
|
+
if (!(await fileExists(absoluteEntrypointPath))) {
|
|
3691
|
+
throw createCliError(
|
|
3692
|
+
`Invalid files mutation in ${packageEntry.packageId}: templateContext.entrypoint not found at ${entrypoint}.`
|
|
3693
|
+
);
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3696
|
+
let moduleNamespace = null;
|
|
3697
|
+
try {
|
|
3698
|
+
moduleNamespace = await import(`${pathToFileURL(absoluteEntrypointPath).href}?t=${Date.now()}_${Math.random()}`);
|
|
3699
|
+
} catch (error) {
|
|
3700
|
+
throw createCliError(
|
|
3701
|
+
`Unable to load templateContext entrypoint ${entrypoint} for ${packageEntry.packageId}: ${String(error?.message || error || "unknown error")}`
|
|
3702
|
+
);
|
|
3703
|
+
}
|
|
3704
|
+
|
|
3705
|
+
const resolver = moduleNamespace?.[exportName];
|
|
3706
|
+
if (typeof resolver !== "function") {
|
|
3707
|
+
throw createCliError(
|
|
3708
|
+
`Invalid files mutation in ${packageEntry.packageId}: templateContext export "${exportName}" is not a function.`
|
|
3709
|
+
);
|
|
3710
|
+
}
|
|
3711
|
+
|
|
3712
|
+
let replacements = null;
|
|
3713
|
+
try {
|
|
3714
|
+
replacements = await resolver({
|
|
3715
|
+
packageId: packageEntry.packageId,
|
|
3716
|
+
packageRoot: packageEntry.rootDir,
|
|
3717
|
+
appRoot,
|
|
3718
|
+
options: Object.freeze({ ...ensureObject(options) }),
|
|
3719
|
+
mutation: Object.freeze({ ...ensureObject(mutation) }),
|
|
3720
|
+
sourcePath,
|
|
3721
|
+
targetPaths: Object.freeze([...ensureArray(targetPaths)])
|
|
3722
|
+
});
|
|
3723
|
+
} catch (error) {
|
|
3724
|
+
throw createCliError(
|
|
3725
|
+
`templateContext export "${exportName}" failed for ${packageEntry.packageId}: ${String(error?.message || error || "unknown error")}`
|
|
3726
|
+
);
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3729
|
+
if (replacements == null) {
|
|
3730
|
+
return null;
|
|
3731
|
+
}
|
|
3732
|
+
if (!replacements || typeof replacements !== "object" || Array.isArray(replacements)) {
|
|
3733
|
+
throw createCliError(
|
|
3734
|
+
`Invalid files mutation in ${packageEntry.packageId}: templateContext export "${exportName}" must return an object map of placeholder replacements.`
|
|
3735
|
+
);
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3738
|
+
const normalizedReplacements = {};
|
|
3739
|
+
for (const [placeholder, value] of Object.entries(replacements)) {
|
|
3740
|
+
const normalizedPlaceholder = String(placeholder || "").trim();
|
|
3741
|
+
if (!normalizedPlaceholder) {
|
|
3742
|
+
continue;
|
|
3743
|
+
}
|
|
3744
|
+
normalizedReplacements[normalizedPlaceholder] = String(value == null ? "" : value);
|
|
3745
|
+
}
|
|
3746
|
+
|
|
3747
|
+
return Object.freeze(normalizedReplacements);
|
|
3748
|
+
}
|
|
3749
|
+
|
|
3561
3750
|
function normalizeSurfaceIdForMutation(value = "") {
|
|
3562
3751
|
return String(value || "")
|
|
3563
3752
|
.trim()
|
|
@@ -3738,8 +3927,10 @@ async function applyFileMutations(
|
|
|
3738
3927
|
managedFiles,
|
|
3739
3928
|
managedMigrations,
|
|
3740
3929
|
touchedFiles,
|
|
3741
|
-
warnings = []
|
|
3930
|
+
warnings = [],
|
|
3931
|
+
precomputedTemplateContextByMutationIndex = null
|
|
3742
3932
|
) {
|
|
3933
|
+
const mutationList = ensureArray(fileMutations);
|
|
3743
3934
|
const managedMigrationById = new Map();
|
|
3744
3935
|
for (const managedMigrationValue of ensureArray(managedMigrations)) {
|
|
3745
3936
|
const managedMigration = ensureObject(managedMigrationValue);
|
|
@@ -3750,7 +3941,7 @@ async function applyFileMutations(
|
|
|
3750
3941
|
managedMigrationById.set(migrationId, managedMigration);
|
|
3751
3942
|
}
|
|
3752
3943
|
|
|
3753
|
-
for (const mutationValue of
|
|
3944
|
+
for (const [mutationIndex, mutationValue] of mutationList.entries()) {
|
|
3754
3945
|
const normalizedMutation = normalizeFileMutationRecord(mutationValue);
|
|
3755
3946
|
const requiresConfigContext = Boolean(normalizedMutation.when?.config || normalizedMutation.toSurface);
|
|
3756
3947
|
const configContext = requiresConfigContext ? await loadMutationWhenConfigContext(appRoot) : {};
|
|
@@ -3788,10 +3979,27 @@ async function applyFileMutations(
|
|
|
3788
3979
|
throw createCliError(`Missing migration template source ${sourcePath} for ${packageEntry.packageId}.`);
|
|
3789
3980
|
}
|
|
3790
3981
|
|
|
3982
|
+
const hasPrecomputedTemplateContext =
|
|
3983
|
+
precomputedTemplateContextByMutationIndex instanceof Map &&
|
|
3984
|
+
precomputedTemplateContextByMutationIndex.has(mutationIndex);
|
|
3985
|
+
const templateContextReplacements = hasPrecomputedTemplateContext
|
|
3986
|
+
? precomputedTemplateContextByMutationIndex.get(mutationIndex)
|
|
3987
|
+
: await resolveTemplateContextReplacementsForMutation({
|
|
3988
|
+
packageEntry,
|
|
3989
|
+
mutation,
|
|
3990
|
+
options,
|
|
3991
|
+
appRoot,
|
|
3992
|
+
sourcePath,
|
|
3993
|
+
targetPaths: [path.join(appRoot, toDir)]
|
|
3994
|
+
});
|
|
3995
|
+
|
|
3791
3996
|
const sourceContent = await readFile(sourcePath, "utf8");
|
|
3792
|
-
|
|
3997
|
+
let renderedSourceContent = sourceContent.includes("${")
|
|
3793
3998
|
? interpolateOptionValue(sourceContent, options, packageEntry.packageId, `${mutation.id || from}.source`)
|
|
3794
3999
|
: sourceContent;
|
|
4000
|
+
if (templateContextReplacements) {
|
|
4001
|
+
renderedSourceContent = applyTemplateContextReplacements(renderedSourceContent, templateContextReplacements);
|
|
4002
|
+
}
|
|
3795
4003
|
const sourceExtension = normalizeMigrationExtension(path.extname(from), ".cjs");
|
|
3796
4004
|
const extension = normalizeMigrationExtension(mutation.extension, sourceExtension);
|
|
3797
4005
|
const sourceHash = hashBuffer(Buffer.from(renderedSourceContent, "utf8"));
|
|
@@ -3956,6 +4164,19 @@ async function applyFileMutations(
|
|
|
3956
4164
|
configContext
|
|
3957
4165
|
})
|
|
3958
4166
|
: [path.join(appRoot, to)];
|
|
4167
|
+
const hasPrecomputedTemplateContext =
|
|
4168
|
+
precomputedTemplateContextByMutationIndex instanceof Map &&
|
|
4169
|
+
precomputedTemplateContextByMutationIndex.has(mutationIndex);
|
|
4170
|
+
const templateContextReplacements = hasPrecomputedTemplateContext
|
|
4171
|
+
? precomputedTemplateContextByMutationIndex.get(mutationIndex)
|
|
4172
|
+
: await resolveTemplateContextReplacementsForMutation({
|
|
4173
|
+
packageEntry,
|
|
4174
|
+
mutation,
|
|
4175
|
+
options,
|
|
4176
|
+
appRoot,
|
|
4177
|
+
sourcePath,
|
|
4178
|
+
targetPaths
|
|
4179
|
+
});
|
|
3959
4180
|
for (const targetPath of targetPaths) {
|
|
3960
4181
|
const previous = await readFileBufferIfExists(targetPath);
|
|
3961
4182
|
await copyTemplateFile(
|
|
@@ -3963,7 +4184,8 @@ async function applyFileMutations(
|
|
|
3963
4184
|
targetPath,
|
|
3964
4185
|
options,
|
|
3965
4186
|
packageEntry.packageId,
|
|
3966
|
-
`${mutation.id || to || from}.source
|
|
4187
|
+
`${mutation.id || to || from}.source`,
|
|
4188
|
+
templateContextReplacements
|
|
3967
4189
|
);
|
|
3968
4190
|
const nextBuffer = await readFile(targetPath);
|
|
3969
4191
|
|
|
@@ -3982,6 +4204,91 @@ async function applyFileMutations(
|
|
|
3982
4204
|
}
|
|
3983
4205
|
}
|
|
3984
4206
|
|
|
4207
|
+
async function preflightFileMutationTemplateContexts(
|
|
4208
|
+
packageEntry,
|
|
4209
|
+
options,
|
|
4210
|
+
appRoot,
|
|
4211
|
+
fileMutations
|
|
4212
|
+
) {
|
|
4213
|
+
const mutationList = ensureArray(fileMutations);
|
|
4214
|
+
const replacementsByMutationIndex = new Map();
|
|
4215
|
+
|
|
4216
|
+
for (const [mutationIndex, mutationValue] of mutationList.entries()) {
|
|
4217
|
+
const normalizedMutation = normalizeFileMutationRecord(mutationValue);
|
|
4218
|
+
const requiresConfigContext = Boolean(normalizedMutation.when?.config || normalizedMutation.toSurface);
|
|
4219
|
+
const configContext = requiresConfigContext ? await loadMutationWhenConfigContext(appRoot) : {};
|
|
4220
|
+
if (
|
|
4221
|
+
!shouldApplyMutationWhen(normalizedMutation.when, {
|
|
4222
|
+
options,
|
|
4223
|
+
configContext,
|
|
4224
|
+
packageId: packageEntry.packageId,
|
|
4225
|
+
mutationContext: "files mutation"
|
|
4226
|
+
})
|
|
4227
|
+
) {
|
|
4228
|
+
continue;
|
|
4229
|
+
}
|
|
4230
|
+
|
|
4231
|
+
const mutation = interpolateFileMutationRecord(normalizedMutation, options, packageEntry.packageId);
|
|
4232
|
+
const templateContext = ensureObject(mutation.templateContext);
|
|
4233
|
+
if (Object.keys(templateContext).length < 1) {
|
|
4234
|
+
continue;
|
|
4235
|
+
}
|
|
4236
|
+
|
|
4237
|
+
const operation = mutation.op || "copy-file";
|
|
4238
|
+
if (operation !== "copy-file" && operation !== "install-migration") {
|
|
4239
|
+
continue;
|
|
4240
|
+
}
|
|
4241
|
+
|
|
4242
|
+
const from = mutation.from;
|
|
4243
|
+
const to = mutation.to;
|
|
4244
|
+
const toSurface = mutation.toSurface;
|
|
4245
|
+
if (!from) {
|
|
4246
|
+
throw createCliError(
|
|
4247
|
+
`Invalid files mutation in ${packageEntry.packageId}: "from" is required.`
|
|
4248
|
+
);
|
|
4249
|
+
}
|
|
4250
|
+
if (operation === "copy-file") {
|
|
4251
|
+
if (to && toSurface) {
|
|
4252
|
+
throw createCliError(
|
|
4253
|
+
`Invalid files mutation in ${packageEntry.packageId}: "to" and "toSurface" cannot both be set.`
|
|
4254
|
+
);
|
|
4255
|
+
}
|
|
4256
|
+
if (!to && !toSurface) {
|
|
4257
|
+
throw createCliError(
|
|
4258
|
+
`Invalid files mutation in ${packageEntry.packageId}: "from" plus one destination ("to" or "toSurface") are required.`
|
|
4259
|
+
);
|
|
4260
|
+
}
|
|
4261
|
+
}
|
|
4262
|
+
|
|
4263
|
+
const sourcePath = path.join(packageEntry.rootDir, from);
|
|
4264
|
+
if (!(await fileExists(sourcePath))) {
|
|
4265
|
+
throw createCliError(`Missing template source ${sourcePath} for ${packageEntry.packageId}.`);
|
|
4266
|
+
}
|
|
4267
|
+
|
|
4268
|
+
const targetPaths = operation === "copy-file"
|
|
4269
|
+
? toSurface
|
|
4270
|
+
? resolveSurfaceTargetPathsForMutation({
|
|
4271
|
+
appRoot,
|
|
4272
|
+
packageId: packageEntry.packageId,
|
|
4273
|
+
mutation,
|
|
4274
|
+
configContext
|
|
4275
|
+
})
|
|
4276
|
+
: [path.join(appRoot, to)]
|
|
4277
|
+
: [path.join(appRoot, mutation.toDir || "migrations")];
|
|
4278
|
+
const replacements = await resolveTemplateContextReplacementsForMutation({
|
|
4279
|
+
packageEntry,
|
|
4280
|
+
mutation,
|
|
4281
|
+
options,
|
|
4282
|
+
appRoot,
|
|
4283
|
+
sourcePath,
|
|
4284
|
+
targetPaths
|
|
4285
|
+
});
|
|
4286
|
+
replacementsByMutationIndex.set(mutationIndex, replacements);
|
|
4287
|
+
}
|
|
4288
|
+
|
|
4289
|
+
return replacementsByMutationIndex;
|
|
4290
|
+
}
|
|
4291
|
+
|
|
3985
4292
|
async function applyTextMutations(packageEntry, appRoot, textMutations, options, managedText, touchedFiles) {
|
|
3986
4293
|
for (const mutation of textMutations) {
|
|
3987
4294
|
const when = normalizeMutationWhen(mutation?.when);
|
|
@@ -4391,9 +4698,10 @@ async function applyPackageInstall({
|
|
|
4391
4698
|
|
|
4392
4699
|
const managedRecord = createManagedRecordBase(packageEntry, packageOptions);
|
|
4393
4700
|
managedRecord.managed.migrations = cloneManagedArray(existingManaged.migrations);
|
|
4394
|
-
const
|
|
4701
|
+
const generatorPackage = isGeneratorPackageEntry(packageEntry);
|
|
4395
4702
|
const mutationWarnings = [];
|
|
4396
4703
|
const mutations = ensureObject(packageEntry.descriptor.mutations);
|
|
4704
|
+
const fileMutations = ensureArray(mutations.files);
|
|
4397
4705
|
const templateRoot = await resolvePackageTemplateRoot({ packageEntry, appRoot });
|
|
4398
4706
|
const packageEntryForMutations =
|
|
4399
4707
|
templateRoot === packageEntry.rootDir
|
|
@@ -4402,6 +4710,14 @@ async function applyPackageInstall({
|
|
|
4402
4710
|
...packageEntry,
|
|
4403
4711
|
rootDir: templateRoot
|
|
4404
4712
|
};
|
|
4713
|
+
|
|
4714
|
+
const precomputedTemplateContextByMutationIndex = await preflightFileMutationTemplateContexts(
|
|
4715
|
+
packageEntryForMutations,
|
|
4716
|
+
packageOptions,
|
|
4717
|
+
appRoot,
|
|
4718
|
+
fileMutations
|
|
4719
|
+
);
|
|
4720
|
+
|
|
4405
4721
|
const mutationDependencies = ensureObject(mutations.dependencies);
|
|
4406
4722
|
const runtimeDependencies = ensureObject(mutationDependencies.runtime);
|
|
4407
4723
|
const devDependencies = ensureObject(mutationDependencies.dev);
|
|
@@ -4471,7 +4787,7 @@ async function applyPackageInstall({
|
|
|
4471
4787
|
}
|
|
4472
4788
|
}
|
|
4473
4789
|
|
|
4474
|
-
if (
|
|
4790
|
+
if (generatorPackage) {
|
|
4475
4791
|
const removedRuntimeDependency = removePackageJsonField(appPackageJson, "dependencies", packageEntry.packageId);
|
|
4476
4792
|
const removedDevDependency = removePackageJsonField(appPackageJson, "devDependencies", packageEntry.packageId);
|
|
4477
4793
|
if (removedRuntimeDependency || removedDevDependency) {
|
|
@@ -4507,11 +4823,12 @@ async function applyPackageInstall({
|
|
|
4507
4823
|
packageEntryForMutations,
|
|
4508
4824
|
packageOptions,
|
|
4509
4825
|
appRoot,
|
|
4510
|
-
|
|
4826
|
+
fileMutations,
|
|
4511
4827
|
managedRecord.managed.files,
|
|
4512
4828
|
managedRecord.managed.migrations,
|
|
4513
4829
|
touchedFiles,
|
|
4514
|
-
mutationWarnings
|
|
4830
|
+
mutationWarnings,
|
|
4831
|
+
precomputedTemplateContextByMutationIndex
|
|
4515
4832
|
);
|
|
4516
4833
|
|
|
4517
4834
|
await applyTextMutations(
|
|
@@ -4532,7 +4849,7 @@ async function applyPackageInstall({
|
|
|
4532
4849
|
touchedFiles
|
|
4533
4850
|
);
|
|
4534
4851
|
|
|
4535
|
-
if (
|
|
4852
|
+
if (generatorPackage) {
|
|
4536
4853
|
delete lock.installedPackages[packageEntry.packageId];
|
|
4537
4854
|
} else {
|
|
4538
4855
|
managedRecord.migrationSyncVersion = packageEntry.version;
|