@jskit-ai/jskit-cli 0.2.41 → 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/packageOptions.js +20 -0
- package/src/server/commandHandlers/packageCommands/generate.js +1 -30
- package/src/server/core/argParser.js +3 -2
- 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,
|
|
@@ -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
|
}));
|
|
@@ -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 = {}) {
|
|
@@ -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
|
}
|
|
@@ -209,6 +209,80 @@ function normalizePathValue(value) {
|
|
|
209
209
|
.join("/");
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
function normalizePromptChoices(rawChoices = []) {
|
|
213
|
+
return ensureArray(rawChoices)
|
|
214
|
+
.map((entry) => {
|
|
215
|
+
if (!entry || typeof entry !== "object") {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
const value = String(entry.value || "").trim();
|
|
219
|
+
const label = String(entry.label || value).trim();
|
|
220
|
+
if (!value || !label) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
return Object.freeze({ value, label });
|
|
224
|
+
})
|
|
225
|
+
.filter(Boolean);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function findPromptChoice(promptChoices = [], answer = "") {
|
|
229
|
+
const normalizedAnswer = String(answer || "").trim();
|
|
230
|
+
if (!normalizedAnswer) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (/^\d+$/.test(normalizedAnswer)) {
|
|
235
|
+
const index = Number.parseInt(normalizedAnswer, 10) - 1;
|
|
236
|
+
if (index >= 0 && index < promptChoices.length) {
|
|
237
|
+
return promptChoices[index];
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return promptChoices.find((entry) => {
|
|
242
|
+
return entry.value === normalizedAnswer || entry.label === normalizedAnswer;
|
|
243
|
+
}) || null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function promptForChoice({
|
|
247
|
+
promptText = "",
|
|
248
|
+
promptChoices = [],
|
|
249
|
+
stdin,
|
|
250
|
+
stdout,
|
|
251
|
+
required = false,
|
|
252
|
+
defaultValue = ""
|
|
253
|
+
}) {
|
|
254
|
+
const rl = createInterface({
|
|
255
|
+
input: stdin,
|
|
256
|
+
output: stdout
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
while (true) {
|
|
261
|
+
const answer = String(await rl.question(promptText)).trim();
|
|
262
|
+
if (!answer && defaultValue) {
|
|
263
|
+
return defaultValue;
|
|
264
|
+
}
|
|
265
|
+
if (!answer && !required) {
|
|
266
|
+
return "";
|
|
267
|
+
}
|
|
268
|
+
if (!answer && required) {
|
|
269
|
+
throw createCliError("A selection is required.");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const matchedChoice = findPromptChoice(promptChoices, answer);
|
|
273
|
+
if (matchedChoice) {
|
|
274
|
+
return matchedChoice.value;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const values = promptChoices.map((entry) => entry.value).join(", ");
|
|
278
|
+
stdout.write(`Invalid selection. Enter a number or one of: ${values}.
|
|
279
|
+
`);
|
|
280
|
+
}
|
|
281
|
+
} finally {
|
|
282
|
+
rl.close();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
212
286
|
function parseTransformSpec(transform) {
|
|
213
287
|
const normalized = String(transform || "").trim();
|
|
214
288
|
if (!normalized) {
|
|
@@ -359,6 +433,7 @@ async function promptForRequiredOption({
|
|
|
359
433
|
ownerId,
|
|
360
434
|
optionName,
|
|
361
435
|
optionSchema,
|
|
436
|
+
promptChoices = [],
|
|
362
437
|
stdin = process.stdin,
|
|
363
438
|
stdout = process.stdout
|
|
364
439
|
}) {
|
|
@@ -387,6 +462,24 @@ async function promptForRequiredOption({
|
|
|
387
462
|
const defaultHint = defaultValue ? ` [default: ${defaultValue}]` : "";
|
|
388
463
|
const hintSuffix = promptHint ? ` ${promptHint}` : "";
|
|
389
464
|
const promptText = `${label}${defaultHint}${hintSuffix}: `;
|
|
465
|
+
const normalizedPromptChoices = normalizePromptChoices(promptChoices);
|
|
466
|
+
|
|
467
|
+
if (normalizedPromptChoices.length > 0) {
|
|
468
|
+
stdout.write(`${label}${defaultHint}${hintSuffix}
|
|
469
|
+
`);
|
|
470
|
+
normalizedPromptChoices.forEach((entry, index) => {
|
|
471
|
+
stdout.write(` ${index + 1}) ${entry.label}
|
|
472
|
+
`);
|
|
473
|
+
});
|
|
474
|
+
return promptForChoice({
|
|
475
|
+
promptText: "Select a surface by number or id: ",
|
|
476
|
+
promptChoices: normalizedPromptChoices,
|
|
477
|
+
stdin,
|
|
478
|
+
stdout,
|
|
479
|
+
required,
|
|
480
|
+
defaultValue
|
|
481
|
+
});
|
|
482
|
+
}
|
|
390
483
|
|
|
391
484
|
let answer = "";
|
|
392
485
|
|