@jskit-ai/jskit-cli 0.2.39 → 0.2.40
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/fileMutations.js +4 -3
- package/src/server/cliRuntime/packageOptions.js +160 -7
- package/src/server/commandHandlers/packageCommands/discoverabilityHelp.js +253 -99
- package/src/server/commandHandlers/packageCommands/generate.js +246 -47
- package/src/server/core/argParser.js +29 -10
- package/src/server/core/buildCommandDeps.js +2 -0
- package/src/server/core/createCliRunner.js +5 -1
- package/src/server/core/dispatchCli.js +3 -1
- package/src/server/core/usageHelp.js +94 -27
- package/src/server/shared/cliError.js +2 -1
- package/src/server/shared/outputFormatting.js +164 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import {
|
|
2
3
|
isHelpToken,
|
|
3
4
|
renderGenerateCatalogHelp,
|
|
4
5
|
renderGeneratePackageHelp,
|
|
5
6
|
renderGenerateSubcommandHelp
|
|
6
7
|
} from "./discoverabilityHelp.js";
|
|
8
|
+
import { interpolateOptionValue } from "../../shared/optionInterpolation.js";
|
|
7
9
|
|
|
8
10
|
function resolveGeneratorSubcommandDefinitionMetadata(packageEntry = {}, subcommandName = "") {
|
|
9
11
|
const descriptor = packageEntry?.descriptor && typeof packageEntry.descriptor === "object"
|
|
@@ -25,6 +27,51 @@ function resolveGeneratorSubcommandDefinitionMetadata(packageEntry = {}, subcomm
|
|
|
25
27
|
return definition && typeof definition === "object" ? definition : {};
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
function mapDescriptorBackedSubcommandArgsToInlineOptions(
|
|
31
|
+
packageEntry = {},
|
|
32
|
+
subcommandName = "",
|
|
33
|
+
subcommandArgs = [],
|
|
34
|
+
inlineOptions = {},
|
|
35
|
+
createCliError
|
|
36
|
+
) {
|
|
37
|
+
const definition = resolveGeneratorSubcommandDefinitionMetadata(packageEntry, subcommandName);
|
|
38
|
+
const positionalArgs = Array.isArray(definition?.positionalArgs)
|
|
39
|
+
? definition.positionalArgs
|
|
40
|
+
: [];
|
|
41
|
+
const providedArgs = Array.isArray(subcommandArgs) ? subcommandArgs : [];
|
|
42
|
+
if (providedArgs.length > positionalArgs.length) {
|
|
43
|
+
throw createCliError(
|
|
44
|
+
`Generator command "${subcommandName}" for ${String(packageEntry?.packageId || "unknown-package")} accepts at most ${positionalArgs.length} positional argument${positionalArgs.length === 1 ? "" : "s"}.`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const mappedInlineOptions = {
|
|
49
|
+
...(inlineOptions && typeof inlineOptions === "object" ? inlineOptions : {})
|
|
50
|
+
};
|
|
51
|
+
for (const [index, rawValue] of providedArgs.entries()) {
|
|
52
|
+
const positionalArg = positionalArgs[index];
|
|
53
|
+
const optionName = String(positionalArg?.name || "").trim();
|
|
54
|
+
if (!optionName) {
|
|
55
|
+
throw createCliError(
|
|
56
|
+
`Generator command "${subcommandName}" for ${String(packageEntry?.packageId || "unknown-package")} defines positional arg ${index + 1} without a name.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const value = String(rawValue || "").trim();
|
|
61
|
+
const existingValue = Object.prototype.hasOwnProperty.call(mappedInlineOptions, optionName)
|
|
62
|
+
? String(mappedInlineOptions[optionName] || "").trim()
|
|
63
|
+
: null;
|
|
64
|
+
if (existingValue != null && existingValue !== value) {
|
|
65
|
+
throw createCliError(
|
|
66
|
+
`Generator command "${subcommandName}" for ${String(packageEntry?.packageId || "unknown-package")} received both positional "${optionName}" and --${optionName} with different values.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
mappedInlineOptions[optionName] = value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return mappedInlineOptions;
|
|
73
|
+
}
|
|
74
|
+
|
|
28
75
|
function resolveSubcommandRequiresInput(packageEntry = {}, subcommandName = "") {
|
|
29
76
|
const descriptor = packageEntry?.descriptor && typeof packageEntry.descriptor === "object"
|
|
30
77
|
? packageEntry.descriptor
|
|
@@ -62,6 +109,108 @@ function resolveSubcommandRequiresInput(packageEntry = {}, subcommandName = "")
|
|
|
62
109
|
return false;
|
|
63
110
|
}
|
|
64
111
|
|
|
112
|
+
function collectUnexpectedGeneratorSubcommandOptionNames(packageEntry = {}, subcommandName = "", inlineOptions = {}) {
|
|
113
|
+
const subcommandDefinition = resolveGeneratorSubcommandDefinitionMetadata(packageEntry, subcommandName);
|
|
114
|
+
if (!Array.isArray(subcommandDefinition?.optionNames)) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const allowedOptionNameSet = new Set(
|
|
119
|
+
subcommandDefinition.optionNames
|
|
120
|
+
.map((optionName) => String(optionName || "").trim())
|
|
121
|
+
.filter(Boolean)
|
|
122
|
+
);
|
|
123
|
+
return Object.keys(inlineOptions || {})
|
|
124
|
+
.map((optionName) => String(optionName || "").trim())
|
|
125
|
+
.filter(Boolean)
|
|
126
|
+
.filter((optionName) => !allowedOptionNameSet.has(optionName))
|
|
127
|
+
.sort((left, right) => left.localeCompare(right));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function resolveCreateTargetPolicy(packageEntry = {}, subcommandName = "") {
|
|
131
|
+
const definition = resolveGeneratorSubcommandDefinitionMetadata(packageEntry, subcommandName);
|
|
132
|
+
const createTarget = definition?.createTarget;
|
|
133
|
+
return createTarget && typeof createTarget === "object" ? createTarget : {};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function normalizeRelativePathWithinApp(appRoot = "", targetPath = "", createCliError) {
|
|
137
|
+
const normalizedTargetPath = String(targetPath || "").trim().replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
138
|
+
if (!normalizedTargetPath) {
|
|
139
|
+
throw createCliError("Generator create target path cannot be empty.");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const absolutePath = path.resolve(appRoot, normalizedTargetPath);
|
|
143
|
+
const relativePath = path.relative(appRoot, absolutePath);
|
|
144
|
+
if (
|
|
145
|
+
!relativePath ||
|
|
146
|
+
relativePath === ".." ||
|
|
147
|
+
relativePath.startsWith(`..${path.sep}`) ||
|
|
148
|
+
path.isAbsolute(relativePath)
|
|
149
|
+
) {
|
|
150
|
+
throw createCliError(`Generator create target must stay within app root: ${normalizedTargetPath}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
absolutePath,
|
|
155
|
+
relativePath: relativePath.split(path.sep).join("/")
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function enforceDescriptorBackedCreateTargetPolicy({
|
|
160
|
+
packageEntry,
|
|
161
|
+
subcommandName,
|
|
162
|
+
inlineOptions = {},
|
|
163
|
+
appRoot = "",
|
|
164
|
+
packageIdInput = "",
|
|
165
|
+
createCliError,
|
|
166
|
+
readdir
|
|
167
|
+
} = {}) {
|
|
168
|
+
const policy = resolveCreateTargetPolicy(packageEntry, subcommandName);
|
|
169
|
+
const pathTemplate = String(policy.pathTemplate || "").trim();
|
|
170
|
+
if (!pathTemplate) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const forceOptionName = String(policy.forceOptionName || "force").trim() || "force";
|
|
175
|
+
const forceOverwrite = String(inlineOptions?.[forceOptionName] || "").trim().toLowerCase() === "true";
|
|
176
|
+
if (forceOverwrite) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const interpolatedTargetPath = interpolateOptionValue(
|
|
181
|
+
pathTemplate,
|
|
182
|
+
inlineOptions,
|
|
183
|
+
String(packageEntry?.packageId || "unknown-package"),
|
|
184
|
+
`${String(subcommandName || "generator")}.createTarget.pathTemplate`
|
|
185
|
+
);
|
|
186
|
+
const resolvedTargetPath = normalizeRelativePathWithinApp(appRoot, interpolatedTargetPath, createCliError);
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const entries = await readdir(resolvedTargetPath.absolutePath);
|
|
190
|
+
if (policy.allowExistingEmptyDirectory === true && entries.length < 1) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const commandLabel = `${String(packageIdInput || packageEntry?.packageId || "generator").trim()} ${String(subcommandName || "").trim()}`.trim();
|
|
195
|
+
const targetLabel = String(policy.label || "target").trim() || "target";
|
|
196
|
+
throw createCliError(
|
|
197
|
+
`${commandLabel} will not overwrite existing ${targetLabel} ${resolvedTargetPath.relativePath}. Re-run with --force to overwrite it.`
|
|
198
|
+
);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
if (error?.code === "ENOENT") {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (error?.code === "ENOTDIR") {
|
|
204
|
+
const commandLabel = `${String(packageIdInput || packageEntry?.packageId || "generator").trim()} ${String(subcommandName || "").trim()}`.trim();
|
|
205
|
+
const targetLabel = String(policy.label || "target").trim() || "target";
|
|
206
|
+
throw createCliError(
|
|
207
|
+
`${commandLabel} will not overwrite existing ${targetLabel} ${resolvedTargetPath.relativePath}. Re-run with --force to overwrite it.`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
65
214
|
async function runPackageGenerateCommand(
|
|
66
215
|
ctx = {},
|
|
67
216
|
{ positional, options, cwd, io },
|
|
@@ -79,6 +228,8 @@ async function runPackageGenerateCommand(
|
|
|
79
228
|
resolvePackageKind,
|
|
80
229
|
resolveGeneratorPrimarySubcommand,
|
|
81
230
|
hasGeneratorSubcommandDefinition,
|
|
231
|
+
readdir,
|
|
232
|
+
validateInlineOptionValuesForPackage,
|
|
82
233
|
runGeneratorSubcommand
|
|
83
234
|
} = ctx;
|
|
84
235
|
|
|
@@ -145,27 +296,12 @@ async function runPackageGenerateCommand(
|
|
|
145
296
|
}
|
|
146
297
|
|
|
147
298
|
if (isHelpToken(subcommandName)) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
299
|
+
if (subcommandArgs.length > 0) {
|
|
300
|
+
throw createCliError(
|
|
301
|
+
`Unknown generator usage: jskit generate ${targetId} help ${subcommandArgs.join(" ")}. Use: jskit generate ${targetId} <subcommand> help`
|
|
302
|
+
);
|
|
151
303
|
}
|
|
152
304
|
const { packageEntry } = await resolveGeneratorPackageEntry(targetId);
|
|
153
|
-
if (helpSubcommandName) {
|
|
154
|
-
const rendered = renderGenerateSubcommandHelp({
|
|
155
|
-
io,
|
|
156
|
-
packageEntry,
|
|
157
|
-
packageIdInput: targetId,
|
|
158
|
-
subcommandName: helpSubcommandName,
|
|
159
|
-
json: options.json
|
|
160
|
-
});
|
|
161
|
-
if (!rendered) {
|
|
162
|
-
throw createCliError(
|
|
163
|
-
`Unknown generator subcommand "${helpSubcommandName}" for ${String(packageEntry?.packageId || targetId)}.`
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
return 0;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
305
|
renderGeneratePackageHelp({
|
|
170
306
|
io,
|
|
171
307
|
packageEntry,
|
|
@@ -175,56 +311,111 @@ async function runPackageGenerateCommand(
|
|
|
175
311
|
return 0;
|
|
176
312
|
}
|
|
177
313
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
314
|
+
async function runResolvedGeneratorSubcommand({
|
|
315
|
+
appRoot,
|
|
316
|
+
packageEntry,
|
|
317
|
+
resolvedPackageId,
|
|
318
|
+
subcommandName: rawSubcommandName = "",
|
|
319
|
+
subcommandArgs: rawSubcommandArgs = []
|
|
320
|
+
} = {}) {
|
|
321
|
+
const normalizedSubcommandName = String(rawSubcommandName || "").trim().toLowerCase();
|
|
322
|
+
const normalizedSubcommandArgs = Array.isArray(rawSubcommandArgs)
|
|
323
|
+
? rawSubcommandArgs
|
|
324
|
+
: [];
|
|
184
325
|
const hasInlineOptions = Object.keys(options?.inlineOptions || {}).length > 0;
|
|
185
|
-
const hasSubcommandArgs =
|
|
186
|
-
if (!hasInlineOptions && !hasSubcommandArgs && resolveSubcommandRequiresInput(packageEntry,
|
|
326
|
+
const hasSubcommandArgs = normalizedSubcommandArgs.length > 0;
|
|
327
|
+
if (!hasInlineOptions && !hasSubcommandArgs && resolveSubcommandRequiresInput(packageEntry, normalizedSubcommandName)) {
|
|
187
328
|
const rendered = renderGenerateSubcommandHelp({
|
|
188
329
|
io,
|
|
189
330
|
packageEntry,
|
|
190
331
|
packageIdInput: targetId,
|
|
191
|
-
subcommandName,
|
|
332
|
+
subcommandName: normalizedSubcommandName,
|
|
192
333
|
json: options.json
|
|
193
334
|
});
|
|
194
335
|
if (rendered) {
|
|
195
336
|
return 0;
|
|
196
337
|
}
|
|
197
338
|
}
|
|
198
|
-
if (
|
|
339
|
+
if (normalizedSubcommandArgs.length === 1 && isHelpToken(normalizedSubcommandArgs[0])) {
|
|
199
340
|
const rendered = renderGenerateSubcommandHelp({
|
|
200
341
|
io,
|
|
201
342
|
packageEntry,
|
|
202
343
|
packageIdInput: targetId,
|
|
203
|
-
subcommandName,
|
|
344
|
+
subcommandName: normalizedSubcommandName,
|
|
204
345
|
json: options.json
|
|
205
346
|
});
|
|
206
347
|
if (!rendered) {
|
|
207
|
-
throw createCliError(`Unknown generator subcommand "${
|
|
348
|
+
throw createCliError(`Unknown generator subcommand "${normalizedSubcommandName}" for ${resolvedPackageId}.`);
|
|
208
349
|
}
|
|
209
350
|
return 0;
|
|
210
351
|
}
|
|
211
352
|
|
|
212
|
-
const
|
|
353
|
+
const subcommandDefinition = resolveGeneratorSubcommandDefinitionMetadata(packageEntry, normalizedSubcommandName);
|
|
354
|
+
const unexpectedOptionNames = collectUnexpectedGeneratorSubcommandOptionNames(
|
|
355
|
+
packageEntry,
|
|
356
|
+
normalizedSubcommandName,
|
|
357
|
+
options.inlineOptions
|
|
358
|
+
);
|
|
359
|
+
if (unexpectedOptionNames.length > 0) {
|
|
360
|
+
const commandLabel = String(targetId || resolvedPackageId || "").trim() || resolvedPackageId;
|
|
361
|
+
throw createCliError(
|
|
362
|
+
`Unknown option${unexpectedOptionNames.length === 1 ? "" : "s"} for generator command ${commandLabel} ${normalizedSubcommandName}: ${unexpectedOptionNames.map((optionName) => `--${optionName}`).join(", ")}.`,
|
|
363
|
+
{
|
|
364
|
+
renderUsage: options.json
|
|
365
|
+
? null
|
|
366
|
+
: () => {
|
|
367
|
+
renderGenerateSubcommandHelp({
|
|
368
|
+
io: {
|
|
369
|
+
...io,
|
|
370
|
+
stdout: io.stderr || io.stdout
|
|
371
|
+
},
|
|
372
|
+
packageEntry,
|
|
373
|
+
packageIdInput: targetId,
|
|
374
|
+
subcommandName: normalizedSubcommandName,
|
|
375
|
+
json: false
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
const validatedOptionNames = Array.isArray(subcommandDefinition?.optionNames)
|
|
382
|
+
? subcommandDefinition.optionNames
|
|
383
|
+
: [];
|
|
384
|
+
await validateInlineOptionValuesForPackage(packageEntry, options.inlineOptions, {
|
|
385
|
+
appRoot,
|
|
386
|
+
optionNames: validatedOptionNames
|
|
387
|
+
});
|
|
388
|
+
|
|
213
389
|
const primarySubcommand = resolveGeneratorPrimarySubcommand(packageEntry);
|
|
214
390
|
if (
|
|
215
391
|
normalizedSubcommandName &&
|
|
216
392
|
normalizedSubcommandName === primarySubcommand &&
|
|
217
393
|
!hasGeneratorSubcommandDefinition(packageEntry, normalizedSubcommandName)
|
|
218
394
|
) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
395
|
+
const inlineOptionsForPrimarySubcommand = mapDescriptorBackedSubcommandArgsToInlineOptions(
|
|
396
|
+
packageEntry,
|
|
397
|
+
normalizedSubcommandName,
|
|
398
|
+
normalizedSubcommandArgs,
|
|
399
|
+
options.inlineOptions,
|
|
400
|
+
createCliError
|
|
401
|
+
);
|
|
402
|
+
await validateInlineOptionValuesForPackage(packageEntry, inlineOptionsForPrimarySubcommand, {
|
|
403
|
+
appRoot
|
|
404
|
+
});
|
|
405
|
+
await enforceDescriptorBackedCreateTargetPolicy({
|
|
406
|
+
packageEntry,
|
|
407
|
+
subcommandName: normalizedSubcommandName,
|
|
408
|
+
inlineOptions: inlineOptionsForPrimarySubcommand,
|
|
409
|
+
appRoot,
|
|
410
|
+
packageIdInput: targetId,
|
|
411
|
+
createCliError,
|
|
412
|
+
readdir
|
|
413
|
+
});
|
|
224
414
|
return runCommandAdd({
|
|
225
415
|
positional: ["package", resolvedPackageId],
|
|
226
416
|
options: {
|
|
227
417
|
...options,
|
|
418
|
+
inlineOptions: inlineOptionsForPrimarySubcommand,
|
|
228
419
|
commandMode: "generate"
|
|
229
420
|
},
|
|
230
421
|
cwd,
|
|
@@ -246,8 +437,8 @@ async function runPackageGenerateCommand(
|
|
|
246
437
|
|
|
247
438
|
return runGeneratorSubcommand({
|
|
248
439
|
packageEntry: executablePackageEntry,
|
|
249
|
-
subcommandName,
|
|
250
|
-
subcommandArgs,
|
|
440
|
+
subcommandName: normalizedSubcommandName,
|
|
441
|
+
subcommandArgs: normalizedSubcommandArgs,
|
|
251
442
|
inlineOptions: options.inlineOptions,
|
|
252
443
|
appRoot,
|
|
253
444
|
io,
|
|
@@ -256,15 +447,23 @@ async function runPackageGenerateCommand(
|
|
|
256
447
|
});
|
|
257
448
|
}
|
|
258
449
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
...
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
450
|
+
if (subcommandName) {
|
|
451
|
+
const resolvedGeneratorPackage = await resolveGeneratorPackageEntry(targetId);
|
|
452
|
+
return runResolvedGeneratorSubcommand({
|
|
453
|
+
...resolvedGeneratorPackage,
|
|
454
|
+
subcommandName,
|
|
455
|
+
subcommandArgs
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const { packageEntry } = await resolveGeneratorPackageEntry(targetId);
|
|
460
|
+
renderGeneratePackageHelp({
|
|
461
|
+
io,
|
|
462
|
+
packageEntry,
|
|
463
|
+
packageIdInput: targetId,
|
|
464
|
+
json: options.json
|
|
267
465
|
});
|
|
466
|
+
return 0;
|
|
268
467
|
}
|
|
269
468
|
|
|
270
469
|
export { runPackageGenerateCommand };
|
|
@@ -51,6 +51,7 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
51
51
|
inlineOptions: {}
|
|
52
52
|
};
|
|
53
53
|
const positional = [];
|
|
54
|
+
const allowLooseInlineOptionParsing = command === "generate";
|
|
54
55
|
|
|
55
56
|
while (args.length > 0) {
|
|
56
57
|
const token = String(args.shift() || "");
|
|
@@ -99,27 +100,45 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
99
100
|
options.help = true;
|
|
100
101
|
continue;
|
|
101
102
|
}
|
|
103
|
+
if (token === "--force") {
|
|
104
|
+
options.inlineOptions.force = "true";
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
102
107
|
|
|
103
108
|
if (token.startsWith("--")) {
|
|
104
109
|
const withoutPrefix = token.slice(2);
|
|
105
110
|
const hasInlineValue = withoutPrefix.includes("=");
|
|
106
111
|
const optionName = hasInlineValue ? withoutPrefix.slice(0, withoutPrefix.indexOf("=")) : withoutPrefix;
|
|
107
|
-
const
|
|
108
|
-
?
|
|
109
|
-
:
|
|
110
|
-
|
|
111
|
-
if (!/^[a-z][a-z0-9-]*$/.test(optionName)) {
|
|
112
|
+
const optionNamePattern = allowLooseInlineOptionParsing
|
|
113
|
+
? /^[A-Za-z][A-Za-z0-9_-]*$/
|
|
114
|
+
: /^[a-z][a-z0-9-]*$/;
|
|
115
|
+
if (!optionNamePattern.test(optionName)) {
|
|
112
116
|
throw createCliError(`Unknown option: ${token}`, { showUsage: true });
|
|
113
117
|
}
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
|
|
119
|
+
let optionValueRaw;
|
|
120
|
+
if (hasInlineValue) {
|
|
121
|
+
optionValueRaw = withoutPrefix.slice(withoutPrefix.indexOf("=") + 1);
|
|
122
|
+
} else {
|
|
123
|
+
const nextToken = typeof args[0] === "string" ? String(args[0]) : "";
|
|
124
|
+
if (nextToken && !nextToken.startsWith("-")) {
|
|
125
|
+
optionValueRaw = args.shift();
|
|
126
|
+
}
|
|
116
127
|
}
|
|
117
|
-
|
|
118
|
-
if (!
|
|
128
|
+
|
|
129
|
+
if (!allowLooseInlineOptionParsing && typeof optionValueRaw !== "string") {
|
|
119
130
|
throw createCliError(`--${optionName} requires a value.`, { showUsage: true });
|
|
120
131
|
}
|
|
132
|
+
if (typeof optionValueRaw === "string") {
|
|
133
|
+
const optionValue = optionValueRaw.trim();
|
|
134
|
+
if (!hasInlineValue && optionValue.startsWith("-")) {
|
|
135
|
+
throw createCliError(`--${optionName} requires a value.`, { showUsage: true });
|
|
136
|
+
}
|
|
137
|
+
options.inlineOptions[optionName] = optionValue;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
121
140
|
|
|
122
|
-
options.inlineOptions[optionName] =
|
|
141
|
+
options.inlineOptions[optionName] = undefined;
|
|
123
142
|
continue;
|
|
124
143
|
}
|
|
125
144
|
|
|
@@ -18,6 +18,7 @@ function createCommandHandlerDeps(deps = {}) {
|
|
|
18
18
|
hydratePackageRegistryFromInstalledNodeModules: deps.hydratePackageRegistryFromInstalledNodeModules,
|
|
19
19
|
resolvePackageTemplateRoot: deps.resolvePackageTemplateRoot,
|
|
20
20
|
validateInlineOptionsForPackage: deps.validateInlineOptionsForPackage,
|
|
21
|
+
validateInlineOptionValuesForPackage: deps.validateInlineOptionValuesForPackage,
|
|
21
22
|
resolveLocalDependencyOrder: deps.resolveLocalDependencyOrder,
|
|
22
23
|
validatePlannedCapabilityClosure: deps.validatePlannedCapabilityClosure,
|
|
23
24
|
resolvePackageOptions: deps.resolvePackageOptions,
|
|
@@ -34,6 +35,7 @@ function createCommandHandlerDeps(deps = {}) {
|
|
|
34
35
|
writeJsonFile: deps.writeJsonFile,
|
|
35
36
|
writeFile: deps.writeFile,
|
|
36
37
|
mkdir: deps.mkdir,
|
|
38
|
+
readdir: deps.readdir,
|
|
37
39
|
path: deps.path,
|
|
38
40
|
inspectPackageOfferings: deps.inspectPackageOfferings,
|
|
39
41
|
buildFileWriteGroups: deps.buildFileWriteGroups,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
mkdir,
|
|
3
|
+
readdir,
|
|
3
4
|
rm,
|
|
4
5
|
writeFile
|
|
5
6
|
} from "node:fs/promises";
|
|
@@ -67,7 +68,8 @@ import {
|
|
|
67
68
|
} from "../cliRuntime/packageIntrospection.js";
|
|
68
69
|
import {
|
|
69
70
|
resolvePackageOptions,
|
|
70
|
-
validateInlineOptionsForPackage
|
|
71
|
+
validateInlineOptionsForPackage,
|
|
72
|
+
validateInlineOptionValuesForPackage
|
|
71
73
|
} from "../cliRuntime/packageOptions.js";
|
|
72
74
|
import {
|
|
73
75
|
resolvePackageTemplateRoot,
|
|
@@ -103,6 +105,7 @@ const commandHandlers = createCommandHandlers(
|
|
|
103
105
|
hydratePackageRegistryFromInstalledNodeModules,
|
|
104
106
|
resolvePackageTemplateRoot,
|
|
105
107
|
validateInlineOptionsForPackage,
|
|
108
|
+
validateInlineOptionValuesForPackage,
|
|
106
109
|
resolveLocalDependencyOrder,
|
|
107
110
|
validatePlannedCapabilityClosure,
|
|
108
111
|
resolvePackageOptions,
|
|
@@ -119,6 +122,7 @@ const commandHandlers = createCommandHandlers(
|
|
|
119
122
|
writeJsonFile,
|
|
120
123
|
writeFile,
|
|
121
124
|
mkdir,
|
|
125
|
+
readdir,
|
|
122
126
|
path,
|
|
123
127
|
inspectPackageOfferings,
|
|
124
128
|
buildFileWriteGroups,
|
|
@@ -122,7 +122,9 @@ function createRunCli({
|
|
|
122
122
|
throw createCliError(`Unhandled command: ${command}`, { showUsage: true });
|
|
123
123
|
} catch (error) {
|
|
124
124
|
stderr.write(`jskit: ${error?.message || String(error)}\n`);
|
|
125
|
-
if (error?.
|
|
125
|
+
if (typeof error?.renderUsage === "function") {
|
|
126
|
+
error.renderUsage();
|
|
127
|
+
} else if (error?.showUsage) {
|
|
126
128
|
printUsage(stderr);
|
|
127
129
|
}
|
|
128
130
|
return 1;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { resolveCommandAlias } from "./commandCatalog.js";
|
|
2
|
+
import {
|
|
3
|
+
createColorFormatter,
|
|
4
|
+
writeWrappedLines
|
|
5
|
+
} from "../shared/outputFormatting.js";
|
|
2
6
|
|
|
3
7
|
const COMMAND_OVERVIEW = Object.freeze([
|
|
4
8
|
Object.freeze({
|
|
@@ -114,9 +118,26 @@ const COMMAND_HELP = Object.freeze({
|
|
|
114
118
|
"No npm install runs unless --run-npm-install is passed.",
|
|
115
119
|
"Short ids resolve to @jskit-ai/<id> when available.",
|
|
116
120
|
"Running without args lists available generators.",
|
|
117
|
-
"
|
|
121
|
+
"Running with only <generatorId> shows generator help.",
|
|
118
122
|
"Use jskit generate <generatorId> <subcommand> help for subcommand-specific usage."
|
|
119
123
|
]),
|
|
124
|
+
examples: Object.freeze([
|
|
125
|
+
Object.freeze({
|
|
126
|
+
label: "Common usage",
|
|
127
|
+
lines: Object.freeze([
|
|
128
|
+
"npx jskit generate ui-generator page \\",
|
|
129
|
+
" admin/reports/index.vue"
|
|
130
|
+
])
|
|
131
|
+
}),
|
|
132
|
+
Object.freeze({
|
|
133
|
+
label: "More advanced usage",
|
|
134
|
+
lines: Object.freeze([
|
|
135
|
+
"npx jskit generate crud-ui-generator crud \\",
|
|
136
|
+
" admin/catalog/index/products \\",
|
|
137
|
+
" --resource-file packages/products/src/shared/productResource.js"
|
|
138
|
+
])
|
|
139
|
+
})
|
|
140
|
+
]),
|
|
120
141
|
fullUse: "jskit generate <generatorId> [subcommand] [subcommand args...] [--<option> <value>...] [--dry-run] [--run-npm-install] [--json] [--verbose]"
|
|
121
142
|
}),
|
|
122
143
|
list: Object.freeze({
|
|
@@ -297,22 +318,41 @@ const BARE_COMMAND_HELP = new Set([
|
|
|
297
318
|
"remove"
|
|
298
319
|
]);
|
|
299
320
|
|
|
300
|
-
function
|
|
301
|
-
|
|
321
|
+
function appendSeparatedBlocks(lines = [], blocks = []) {
|
|
322
|
+
const normalizedBlocks = Array.isArray(blocks) ? blocks : [];
|
|
323
|
+
for (const [index, block] of normalizedBlocks.entries()) {
|
|
324
|
+
if (index > 0) {
|
|
325
|
+
lines.push("");
|
|
326
|
+
}
|
|
327
|
+
const lineList = Array.isArray(block) ? block : [block];
|
|
328
|
+
for (const line of lineList) {
|
|
329
|
+
lines.push(line);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function writeHelpLines(stream, lines = []) {
|
|
335
|
+
writeWrappedLines({
|
|
336
|
+
stdout: stream,
|
|
337
|
+
lines
|
|
338
|
+
});
|
|
302
339
|
}
|
|
303
340
|
|
|
304
341
|
function printTopLevelHelp(stream = process.stderr) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
342
|
+
const color = createColorFormatter(stream);
|
|
343
|
+
const lines = [];
|
|
344
|
+
lines.push(color.heading("JSKit CLI"));
|
|
345
|
+
lines.push("");
|
|
346
|
+
lines.push("Use: jskit help <command> for command-specific usage.");
|
|
347
|
+
lines.push("");
|
|
348
|
+
lines.push(color.heading("Available commands:"));
|
|
310
349
|
for (const entry of COMMAND_OVERVIEW) {
|
|
311
|
-
|
|
350
|
+
lines.push(` ${color.item(entry.command.padEnd(16, " "))} ${entry.summary}`);
|
|
312
351
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
352
|
+
lines.push("");
|
|
353
|
+
lines.push(color.heading("Global flags:"));
|
|
354
|
+
lines.push(" --dry-run --run-npm-install --json --verbose --help");
|
|
355
|
+
writeHelpLines(stream, lines);
|
|
316
356
|
}
|
|
317
357
|
|
|
318
358
|
function printCommandHelp(stream = process.stderr, command = "") {
|
|
@@ -323,27 +363,54 @@ function printCommandHelp(stream = process.stderr, command = "") {
|
|
|
323
363
|
return;
|
|
324
364
|
}
|
|
325
365
|
|
|
326
|
-
|
|
327
|
-
|
|
366
|
+
const color = createColorFormatter(stream);
|
|
367
|
+
const lines = [];
|
|
368
|
+
lines.push(`Command: ${color.emphasis(entry.title)}`);
|
|
369
|
+
lines.push("");
|
|
370
|
+
|
|
371
|
+
let sectionNumber = 1;
|
|
328
372
|
|
|
329
|
-
|
|
330
|
-
|
|
373
|
+
lines.push(color.heading(`${sectionNumber++}) Minimal use`));
|
|
374
|
+
lines.push(` ${entry.minimalUse}`);
|
|
331
375
|
if (entry.parameters.length > 0) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
376
|
+
lines.push(color.heading(" Parameters:"));
|
|
377
|
+
appendSeparatedBlocks(
|
|
378
|
+
lines,
|
|
379
|
+
entry.parameters.map((parameter) => ` - ${parameter.name}: ${parameter.description}`)
|
|
380
|
+
);
|
|
336
381
|
}
|
|
337
|
-
|
|
382
|
+
lines.push("");
|
|
383
|
+
|
|
384
|
+
lines.push(color.heading(`${sectionNumber++}) Defaults`));
|
|
385
|
+
appendSeparatedBlocks(
|
|
386
|
+
lines,
|
|
387
|
+
entry.defaults.map((defaultLine) => ` - ${defaultLine}`)
|
|
388
|
+
);
|
|
389
|
+
lines.push("");
|
|
338
390
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
391
|
+
const examples = Array.isArray(entry.examples) ? entry.examples : [];
|
|
392
|
+
if (examples.length > 0) {
|
|
393
|
+
lines.push(color.heading(`${sectionNumber++}) Examples`));
|
|
394
|
+
appendSeparatedBlocks(
|
|
395
|
+
lines,
|
|
396
|
+
examples.map((example) => {
|
|
397
|
+
const block = [];
|
|
398
|
+
const label = String(example?.label || "").trim();
|
|
399
|
+
if (label) {
|
|
400
|
+
block.push(` - ${color.item(label)}`);
|
|
401
|
+
}
|
|
402
|
+
for (const line of Array.isArray(example?.lines) ? example.lines : []) {
|
|
403
|
+
block.push(` ${line}`);
|
|
404
|
+
}
|
|
405
|
+
return block;
|
|
406
|
+
})
|
|
407
|
+
);
|
|
408
|
+
lines.push("");
|
|
342
409
|
}
|
|
343
|
-
writeLine(stream, "");
|
|
344
410
|
|
|
345
|
-
|
|
346
|
-
|
|
411
|
+
lines.push(color.heading(`${sectionNumber++}) Full use`));
|
|
412
|
+
lines.push(` ${entry.fullUse}`);
|
|
413
|
+
writeHelpLines(stream, lines);
|
|
347
414
|
}
|
|
348
415
|
|
|
349
416
|
function printUsage(stream = process.stderr, { command = "" } = {}) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
function createCliError(message, { showUsage = false, exitCode = 1 } = {}) {
|
|
1
|
+
function createCliError(message, { showUsage = false, exitCode = 1, renderUsage = null } = {}) {
|
|
2
2
|
const error = new Error(String(message || "Unknown CLI error"));
|
|
3
3
|
error.name = "CliError";
|
|
4
4
|
error.showUsage = Boolean(showUsage);
|
|
5
5
|
error.exitCode = Number.isInteger(exitCode) ? exitCode : 1;
|
|
6
|
+
error.renderUsage = typeof renderUsage === "function" ? renderUsage : null;
|
|
6
7
|
return error;
|
|
7
8
|
}
|
|
8
9
|
|