@jskit-ai/jskit-cli 0.2.40 → 0.2.42
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 +3 -3
- package/src/server/cliRuntime/mutations/textMutations.js +11 -6
- package/src/server/cliRuntime/packageInstallFlow.js +7 -2
- package/src/server/cliRuntime/packageOptions.js +20 -0
- package/src/server/cliRuntime/packageTemplateResolution.js +23 -2
- package/src/server/commandHandlers/packageCommands/add.js +7 -2
- package/src/server/commandHandlers/packageCommands/generate.js +8 -32
- package/src/server/commandHandlers/shared.js +39 -0
- package/src/server/core/argParser.js +3 -2
- package/src/server/core/commandCatalog.js +551 -32
- package/src/server/core/createCliRunner.js +6 -0
- package/src/server/core/dispatchCli.js +28 -68
- package/src/server/core/usageHelp.js +8 -331
- package/src/server/shared/optionInterpolation.js +93 -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.42",
|
|
4
4
|
"description": "Bundle and package orchestration CLI for JSKIT apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"test": "node --test"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@jskit-ai/jskit-catalog": "0.1.
|
|
24
|
-
"@jskit-ai/kernel": "0.1.
|
|
23
|
+
"@jskit-ai/jskit-catalog": "0.1.41",
|
|
24
|
+
"@jskit-ai/kernel": "0.1.33"
|
|
25
25
|
},
|
|
26
26
|
"engines": {
|
|
27
27
|
"node": "20.x"
|
|
@@ -118,25 +118,30 @@ async function applyTextMutations(packageEntry, appRoot, textMutations, options,
|
|
|
118
118
|
const operation = String(mutation?.op || "").trim();
|
|
119
119
|
if (operation === "upsert-env") {
|
|
120
120
|
const relativeFile = String(mutation?.file || "").trim();
|
|
121
|
-
const
|
|
122
|
-
if (!relativeFile || !
|
|
121
|
+
const rawKey = String(mutation?.key || "").trim();
|
|
122
|
+
if (!relativeFile || !rawKey) {
|
|
123
123
|
throw createCliError(`Invalid upsert-env mutation in ${packageEntry.packageId}: "file" and "key" are required.`);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
const resolvedKey = interpolateOptionValue(rawKey, options, packageEntry.packageId, `${rawKey}.key`).trim();
|
|
127
|
+
if (!resolvedKey) {
|
|
128
|
+
throw createCliError(`Invalid upsert-env mutation in ${packageEntry.packageId}: resolved key is empty.`);
|
|
129
|
+
}
|
|
130
|
+
|
|
126
131
|
const absoluteFile = path.join(appRoot, relativeFile);
|
|
127
132
|
const previous = await readFileBufferIfExists(absoluteFile);
|
|
128
133
|
const previousContent = previous.exists ? previous.buffer.toString("utf8") : "";
|
|
129
|
-
const resolvedValue = interpolateOptionValue(mutation?.value || "", options, packageEntry.packageId,
|
|
130
|
-
const upserted = upsertEnvValue(previousContent,
|
|
134
|
+
const resolvedValue = interpolateOptionValue(mutation?.value || "", options, packageEntry.packageId, resolvedKey);
|
|
135
|
+
const upserted = upsertEnvValue(previousContent, resolvedKey, resolvedValue);
|
|
131
136
|
|
|
132
137
|
await mkdir(path.dirname(absoluteFile), { recursive: true });
|
|
133
138
|
await writeFile(absoluteFile, upserted.content, "utf8");
|
|
134
139
|
|
|
135
|
-
const recordKey = `${relativeFile}::${String(mutation?.id ||
|
|
140
|
+
const recordKey = `${relativeFile}::${String(mutation?.id || resolvedKey)}`;
|
|
136
141
|
managedText[recordKey] = {
|
|
137
142
|
file: relativeFile,
|
|
138
143
|
op: "upsert-env",
|
|
139
|
-
key,
|
|
144
|
+
key: resolvedKey,
|
|
140
145
|
value: resolvedValue,
|
|
141
146
|
hadPrevious: upserted.hadPrevious,
|
|
142
147
|
previousValue: upserted.previousValue,
|
|
@@ -275,7 +275,8 @@ async function applyPackageInstall({
|
|
|
275
275
|
appPackageJson,
|
|
276
276
|
lock,
|
|
277
277
|
packageRegistry,
|
|
278
|
-
touchedFiles
|
|
278
|
+
touchedFiles,
|
|
279
|
+
reportTemplateFetchStatus = null
|
|
279
280
|
}) {
|
|
280
281
|
const existingInstall = ensureObject(lock.installedPackages[packageEntry.packageId]);
|
|
281
282
|
const existingManaged = ensureObject(existingInstall.managed);
|
|
@@ -292,7 +293,11 @@ async function applyPackageInstall({
|
|
|
292
293
|
const mutationWarnings = [];
|
|
293
294
|
const mutations = ensureObject(packageEntry.descriptor.mutations);
|
|
294
295
|
const fileMutations = ensureArray(mutations.files);
|
|
295
|
-
const templateRoot = await resolvePackageTemplateRoot({
|
|
296
|
+
const templateRoot = await resolvePackageTemplateRoot({
|
|
297
|
+
packageEntry,
|
|
298
|
+
appRoot,
|
|
299
|
+
reportTemplateFetchStatus
|
|
300
|
+
});
|
|
296
301
|
const packageEntryForMutations =
|
|
297
302
|
templateRoot === packageEntry.rootDir
|
|
298
303
|
? packageEntry
|
|
@@ -91,6 +91,7 @@ function resolveSurfaceDefinitionsForOptionPolicy(configContext = {}) {
|
|
|
91
91
|
|
|
92
92
|
normalizedDefinitions[definitionId] = Object.freeze({
|
|
93
93
|
id: definitionId,
|
|
94
|
+
label: String(definition.label || "").trim(),
|
|
94
95
|
enabled: definition.enabled !== false,
|
|
95
96
|
requiresWorkspace: definition.requiresWorkspace === true
|
|
96
97
|
});
|
|
@@ -251,6 +252,23 @@ async function validateResolvedOptionPolicies({
|
|
|
251
252
|
});
|
|
252
253
|
}
|
|
253
254
|
|
|
255
|
+
function resolvePromptChoicesForOption({ schema = {}, configContext = {} } = {}) {
|
|
256
|
+
const validationType = normalizeResolvedOptionValue(schema.validationType);
|
|
257
|
+
if (validationType !== OPTION_VALIDATION_ENABLED_SURFACE_ID) {
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const surfaceDefinitions = resolveSurfaceDefinitionsForOptionPolicy(configContext);
|
|
262
|
+
return Object.values(surfaceDefinitions)
|
|
263
|
+
.filter((entry) => entry.enabled === true)
|
|
264
|
+
.map((entry) => Object.freeze({
|
|
265
|
+
value: entry.id,
|
|
266
|
+
label: entry.label && entry.label.toLowerCase() !== entry.id.toLowerCase()
|
|
267
|
+
? `${entry.id} (${entry.label})`
|
|
268
|
+
: entry.id
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
|
|
254
272
|
async function validateOptionValuesForPackage({
|
|
255
273
|
packageEntry,
|
|
256
274
|
resolvedOptions = {},
|
|
@@ -379,11 +397,13 @@ async function resolvePackageOptions(packageEntry, inlineOptions, io, { appRoot
|
|
|
379
397
|
}
|
|
380
398
|
|
|
381
399
|
if (schema.required) {
|
|
400
|
+
const promptConfigContext = appRoot ? await loadConfigContext() : {};
|
|
382
401
|
assignResolvedOption(await promptForRequiredOption({
|
|
383
402
|
ownerType: "package",
|
|
384
403
|
ownerId: packageEntry.packageId,
|
|
385
404
|
optionName,
|
|
386
405
|
optionSchema: schema,
|
|
406
|
+
promptChoices: resolvePromptChoicesForOption({ schema, configContext: promptConfigContext }),
|
|
387
407
|
stdin: io.stdin,
|
|
388
408
|
stdout: io.stdout
|
|
389
409
|
}));
|
|
@@ -68,7 +68,10 @@ async function ensureMaterializedInstallWorkspace(installRoot) {
|
|
|
68
68
|
);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
async function installCatalogPackageIntoCache({
|
|
71
|
+
async function installCatalogPackageIntoCache({
|
|
72
|
+
installRoot,
|
|
73
|
+
packageEntry
|
|
74
|
+
}) {
|
|
72
75
|
const packageId = String(packageEntry?.packageId || "").trim();
|
|
73
76
|
const version = String(packageEntry?.version || "").trim();
|
|
74
77
|
const packageSpec = version ? `${packageId}@${version}` : packageId;
|
|
@@ -124,6 +127,7 @@ async function installCatalogPackageIntoCache({ installRoot, packageEntry }) {
|
|
|
124
127
|
async function materializeCatalogPackageRoot({
|
|
125
128
|
packageEntry,
|
|
126
129
|
appRoot,
|
|
130
|
+
reportTemplateFetchStatus = null,
|
|
127
131
|
installCatalogPackage = installCatalogPackageIntoCache
|
|
128
132
|
} = {}) {
|
|
129
133
|
if (!isInternalCatalogPackageEntry(packageEntry)) {
|
|
@@ -139,12 +143,20 @@ async function materializeCatalogPackageRoot({
|
|
|
139
143
|
const installRoot = buildMaterializedInstallRoot({ appRoot, packageEntry });
|
|
140
144
|
const candidateRoot = path.join(installRoot, "node_modules", ...packageId.split("/"));
|
|
141
145
|
const descriptorPath = path.join(candidateRoot, "package.descriptor.mjs");
|
|
146
|
+
let didInstallPackage = false;
|
|
142
147
|
|
|
143
148
|
if (!(await fileExists(descriptorPath))) {
|
|
149
|
+
if (typeof reportTemplateFetchStatus === "function") {
|
|
150
|
+
reportTemplateFetchStatus({
|
|
151
|
+
packageEntry,
|
|
152
|
+
state: "start"
|
|
153
|
+
});
|
|
154
|
+
}
|
|
144
155
|
await installCatalogPackage({
|
|
145
156
|
installRoot,
|
|
146
157
|
packageEntry
|
|
147
158
|
});
|
|
159
|
+
didInstallPackage = true;
|
|
148
160
|
}
|
|
149
161
|
|
|
150
162
|
if (!(await fileExists(descriptorPath))) {
|
|
@@ -153,6 +165,13 @@ async function materializeCatalogPackageRoot({
|
|
|
153
165
|
);
|
|
154
166
|
}
|
|
155
167
|
|
|
168
|
+
if (didInstallPackage && typeof reportTemplateFetchStatus === "function") {
|
|
169
|
+
reportTemplateFetchStatus({
|
|
170
|
+
packageEntry,
|
|
171
|
+
state: "complete"
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
156
175
|
MATERIALIZED_PACKAGE_ROOTS.set(cacheKey, candidateRoot);
|
|
157
176
|
return candidateRoot;
|
|
158
177
|
}
|
|
@@ -243,6 +262,7 @@ async function resolvePackageRootFromLocalWorkspace({ packageId }) {
|
|
|
243
262
|
async function resolvePackageTemplateRoot({
|
|
244
263
|
packageEntry,
|
|
245
264
|
appRoot,
|
|
265
|
+
reportTemplateFetchStatus = null,
|
|
246
266
|
materializeCatalogRoot = materializeCatalogPackageRoot
|
|
247
267
|
} = {}) {
|
|
248
268
|
const packageRoot = String(packageEntry?.rootDir || "").trim();
|
|
@@ -267,7 +287,8 @@ async function resolvePackageTemplateRoot({
|
|
|
267
287
|
|
|
268
288
|
const materializedCatalogPackageRoot = await materializeCatalogRoot({
|
|
269
289
|
packageEntry,
|
|
270
|
-
appRoot
|
|
290
|
+
appRoot,
|
|
291
|
+
reportTemplateFetchStatus
|
|
271
292
|
});
|
|
272
293
|
if (materializedCatalogPackageRoot) {
|
|
273
294
|
return materializedCatalogPackageRoot;
|
|
@@ -38,7 +38,8 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
|
|
|
38
38
|
adoptAppLocalPackageDependencies,
|
|
39
39
|
writeJsonFile,
|
|
40
40
|
runNpmInstall,
|
|
41
|
-
renderResolvedSummary
|
|
41
|
+
renderResolvedSummary,
|
|
42
|
+
createCatalogFetchStatusReporter = () => () => {}
|
|
42
43
|
} = ctx;
|
|
43
44
|
|
|
44
45
|
const invocationMode = options?.commandMode === "generate" ? "generate" : "add";
|
|
@@ -242,6 +243,9 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
|
|
|
242
243
|
|
|
243
244
|
const packagesToInstall = [];
|
|
244
245
|
const resolvedOptionsByPackage = {};
|
|
246
|
+
const reportTemplateFetchStatus = createCatalogFetchStatusReporter(io, {
|
|
247
|
+
enabled: options.json !== true
|
|
248
|
+
});
|
|
245
249
|
const forceReapplyTarget = options?.forceReapplyTarget === true;
|
|
246
250
|
const hasInlineOptions = Object.keys(ensureObject(options.inlineOptions)).length > 0;
|
|
247
251
|
for (const packageId of resolvedPackageIds) {
|
|
@@ -293,7 +297,8 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
|
|
|
293
297
|
appPackageJson: packageJson,
|
|
294
298
|
lock,
|
|
295
299
|
packageRegistry: combinedPackageRegistry,
|
|
296
|
-
touchedFiles
|
|
300
|
+
touchedFiles,
|
|
301
|
+
reportTemplateFetchStatus
|
|
297
302
|
});
|
|
298
303
|
installedPackageRecords.push(managedRecord);
|
|
299
304
|
}
|
|
@@ -73,40 +73,11 @@ function mapDescriptorBackedSubcommandArgsToInlineOptions(
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
function resolveSubcommandRequiresInput(packageEntry = {}, subcommandName = "") {
|
|
76
|
-
const descriptor = packageEntry?.descriptor && typeof packageEntry.descriptor === "object"
|
|
77
|
-
? packageEntry.descriptor
|
|
78
|
-
: {};
|
|
79
|
-
const optionSchemas = descriptor?.options && typeof descriptor.options === "object"
|
|
80
|
-
? descriptor.options
|
|
81
|
-
: {};
|
|
82
76
|
const subcommandDefinition = resolveGeneratorSubcommandDefinitionMetadata(packageEntry, subcommandName);
|
|
83
77
|
const positionalArgs = Array.isArray(subcommandDefinition?.positionalArgs)
|
|
84
78
|
? subcommandDefinition.positionalArgs
|
|
85
79
|
: [];
|
|
86
|
-
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
const requiredOptionNames = Array.isArray(subcommandDefinition?.requiredOptionNames)
|
|
90
|
-
? subcommandDefinition.requiredOptionNames
|
|
91
|
-
: [];
|
|
92
|
-
if (requiredOptionNames.some((optionName) => String(optionName || "").trim().length > 0)) {
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const optionNames = Array.isArray(subcommandDefinition?.optionNames) && subcommandDefinition.optionNames.length > 0
|
|
97
|
-
? subcommandDefinition.optionNames
|
|
98
|
-
: Object.keys(optionSchemas);
|
|
99
|
-
for (const optionName of optionNames) {
|
|
100
|
-
const normalizedOptionName = String(optionName || "").trim();
|
|
101
|
-
if (!normalizedOptionName) {
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
const schema = optionSchemas[normalizedOptionName];
|
|
105
|
-
if (schema && typeof schema === "object" && schema.required === true) {
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return false;
|
|
80
|
+
return positionalArgs.some((arg) => arg && typeof arg === "object" && arg.required !== false);
|
|
110
81
|
}
|
|
111
82
|
|
|
112
83
|
function collectUnexpectedGeneratorSubcommandOptionNames(packageEntry = {}, subcommandName = "", inlineOptions = {}) {
|
|
@@ -230,7 +201,8 @@ async function runPackageGenerateCommand(
|
|
|
230
201
|
hasGeneratorSubcommandDefinition,
|
|
231
202
|
readdir,
|
|
232
203
|
validateInlineOptionValuesForPackage,
|
|
233
|
-
runGeneratorSubcommand
|
|
204
|
+
runGeneratorSubcommand,
|
|
205
|
+
createCatalogFetchStatusReporter = () => () => {}
|
|
234
206
|
} = ctx;
|
|
235
207
|
|
|
236
208
|
const firstToken = String(positional[0] || "").trim();
|
|
@@ -244,6 +216,9 @@ async function runPackageGenerateCommand(
|
|
|
244
216
|
const targetId = firstToken === "package" ? secondToken : firstToken;
|
|
245
217
|
const subcommandName = firstToken === "package" ? thirdToken : secondToken;
|
|
246
218
|
const subcommandArgs = firstToken === "package" ? positional.slice(3) : positional.slice(2);
|
|
219
|
+
const reportTemplateFetchStatus = createCatalogFetchStatusReporter(io, {
|
|
220
|
+
enabled: options.json !== true
|
|
221
|
+
});
|
|
247
222
|
|
|
248
223
|
async function resolveGeneratorPackageEntry(packageIdInput = "") {
|
|
249
224
|
const appRoot = await resolveAppRootFromCwd(cwd);
|
|
@@ -425,7 +400,8 @@ async function runPackageGenerateCommand(
|
|
|
425
400
|
|
|
426
401
|
const templateRoot = await resolvePackageTemplateRoot({
|
|
427
402
|
packageEntry,
|
|
428
|
-
appRoot
|
|
403
|
+
appRoot,
|
|
404
|
+
reportTemplateFetchStatus
|
|
429
405
|
});
|
|
430
406
|
const executablePackageEntry =
|
|
431
407
|
templateRoot === packageEntry.rootDir
|
|
@@ -39,6 +39,44 @@ function createCommandHandlerShared(ctx = {}) {
|
|
|
39
39
|
return lines.join("\n");
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
function createCatalogFetchStatusReporter(io = {}, { enabled = true } = {}) {
|
|
43
|
+
if (enabled !== true) {
|
|
44
|
+
return () => {};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const stdout = io?.stdout;
|
|
48
|
+
if (!stdout || typeof stdout.write !== "function") {
|
|
49
|
+
return () => {};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const activeFetchLabels = new Set();
|
|
53
|
+
return ({ packageEntry, state } = {}) => {
|
|
54
|
+
const packageId = String(packageEntry?.packageId || "").trim();
|
|
55
|
+
const version = String(packageEntry?.version || "").trim();
|
|
56
|
+
const packageLabel = version ? `${packageId}@${version}` : packageId;
|
|
57
|
+
if (!packageLabel) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (state === "start") {
|
|
62
|
+
if (activeFetchLabels.has(packageLabel)) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
activeFetchLabels.add(packageLabel);
|
|
66
|
+
stdout.write(`Fetching ${packageLabel}...\n`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (state === "complete") {
|
|
71
|
+
if (!activeFetchLabels.has(packageLabel)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
activeFetchLabels.delete(packageLabel);
|
|
75
|
+
stdout.write(`Fetching ${packageLabel}... done!\n`);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
42
80
|
async function runNpmInstall(appRoot, stderr) {
|
|
43
81
|
await new Promise((resolve, reject) => {
|
|
44
82
|
const child = spawn("npm", ["install"], {
|
|
@@ -298,6 +336,7 @@ function createCommandHandlerShared(ctx = {}) {
|
|
|
298
336
|
|
|
299
337
|
return {
|
|
300
338
|
renderResolvedSummary,
|
|
339
|
+
createCatalogFetchStatusReporter,
|
|
301
340
|
runNpmInstall,
|
|
302
341
|
getInstalledDependents,
|
|
303
342
|
resolvePackageKind,
|
|
@@ -120,8 +120,9 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
120
120
|
if (hasInlineValue) {
|
|
121
121
|
optionValueRaw = withoutPrefix.slice(withoutPrefix.indexOf("=") + 1);
|
|
122
122
|
} else {
|
|
123
|
-
const
|
|
124
|
-
|
|
123
|
+
const hasNextStringToken = typeof args[0] === "string";
|
|
124
|
+
const nextToken = hasNextStringToken ? String(args[0]) : "";
|
|
125
|
+
if (hasNextStringToken && !nextToken.startsWith("-")) {
|
|
125
126
|
optionValueRaw = args.shift();
|
|
126
127
|
}
|
|
127
128
|
}
|