@jskit-ai/jskit-cli 0.2.29 → 0.2.31

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.
@@ -0,0 +1,649 @@
1
+ import {
2
+ ensureArray,
3
+ ensureObject,
4
+ sortStrings
5
+ } from "../../shared/collectionUtils.js";
6
+
7
+ const JSKIT_SCOPE_PREFIX = "@jskit-ai/";
8
+
9
+ function isHelpToken(value = "") {
10
+ return String(value || "").trim().toLowerCase() === "help";
11
+ }
12
+
13
+ function toShortPackageId(packageId = "") {
14
+ const normalized = String(packageId || "").trim();
15
+ if (!normalized.startsWith(JSKIT_SCOPE_PREFIX)) {
16
+ return normalized;
17
+ }
18
+ return normalized.slice(JSKIT_SCOPE_PREFIX.length);
19
+ }
20
+
21
+ function resolvePackageSummary(entry = {}) {
22
+ const descriptor = ensureObject(entry?.descriptor);
23
+ return String(descriptor.description || "").trim();
24
+ }
25
+
26
+ function buildPackageOptionRows(packageEntry = {}) {
27
+ const descriptor = ensureObject(packageEntry?.descriptor);
28
+ const optionSchemas = ensureObject(descriptor.options);
29
+ const rows = [];
30
+
31
+ for (const optionName of sortStrings(Object.keys(optionSchemas))) {
32
+ const schema = ensureObject(optionSchemas[optionName]);
33
+ rows.push(Object.freeze({
34
+ name: optionName,
35
+ required: schema.required === true,
36
+ inputType: String(schema.inputType || "text").trim() || "text",
37
+ defaultValue: String(schema.defaultValue || "").trim(),
38
+ allowEmpty: schema.allowEmpty === true,
39
+ promptLabel: String(schema.promptLabel || "").trim(),
40
+ promptHint: String(schema.promptHint || "").trim()
41
+ }));
42
+ }
43
+
44
+ rows.sort((left, right) => {
45
+ if (left.required !== right.required) {
46
+ return left.required ? -1 : 1;
47
+ }
48
+ return String(left.name || "").localeCompare(String(right.name || ""));
49
+ });
50
+
51
+ return rows;
52
+ }
53
+
54
+ function normalizeSubcommandPositionalArgRows(rawRows = []) {
55
+ const rows = [];
56
+ for (const rawRow of ensureArray(rawRows)) {
57
+ if (typeof rawRow === "string") {
58
+ const name = String(rawRow || "").trim();
59
+ if (!name) {
60
+ continue;
61
+ }
62
+ rows.push(Object.freeze({
63
+ name,
64
+ required: true,
65
+ description: ""
66
+ }));
67
+ continue;
68
+ }
69
+
70
+ const row = ensureObject(rawRow);
71
+ const name = String(row.name || "").trim();
72
+ if (!name) {
73
+ continue;
74
+ }
75
+ rows.push(Object.freeze({
76
+ name,
77
+ required: row.required !== false,
78
+ description: String(row.description || "").trim()
79
+ }));
80
+ }
81
+ return rows;
82
+ }
83
+
84
+ function normalizeSubcommandOptionNames(rawOptionNames = []) {
85
+ const seen = new Set();
86
+ const rows = [];
87
+ for (const rawOptionName of ensureArray(rawOptionNames)) {
88
+ const optionName = String(rawOptionName || "").trim();
89
+ if (!optionName || seen.has(optionName)) {
90
+ continue;
91
+ }
92
+ seen.add(optionName);
93
+ rows.push(optionName);
94
+ }
95
+ return rows;
96
+ }
97
+
98
+ function resolveGeneratorSubcommandMetadata(packageEntry = {}) {
99
+ const descriptor = ensureObject(packageEntry?.descriptor);
100
+ const metadata = ensureObject(descriptor.metadata);
101
+ const subcommands = ensureObject(metadata.generatorSubcommands || descriptor.generatorSubcommands);
102
+ const primarySubcommand = String(metadata.generatorPrimarySubcommand || descriptor.generatorPrimarySubcommand || "")
103
+ .trim()
104
+ .toLowerCase();
105
+ const subcommandNames = new Set(sortStrings(Object.keys(subcommands)));
106
+ if (primarySubcommand) {
107
+ subcommandNames.add(primarySubcommand);
108
+ }
109
+ const rows = [];
110
+
111
+ for (const subcommandName of sortStrings([...subcommandNames])) {
112
+ const definition = ensureObject(subcommands[subcommandName]);
113
+ const entrypoint = String(definition.entrypoint || "").trim();
114
+ const exportName = String(definition.export || "runGeneratorSubcommand").trim() || "runGeneratorSubcommand";
115
+ const name = String(subcommandName || "").trim();
116
+ if (!name) {
117
+ continue;
118
+ }
119
+ const optionNames = normalizeSubcommandOptionNames(definition.optionNames);
120
+ const requiredOptionNames = normalizeSubcommandOptionNames(definition.requiredOptionNames);
121
+ rows.push(Object.freeze({
122
+ name,
123
+ entrypoint,
124
+ exportName,
125
+ primary: primarySubcommand === name.toLowerCase(),
126
+ description: String(definition.description || "").trim(),
127
+ positionalArgs: normalizeSubcommandPositionalArgRows(definition.positionalArgs),
128
+ optionNames,
129
+ requiredOptionNames
130
+ }));
131
+ }
132
+
133
+ return Object.freeze({
134
+ primarySubcommand,
135
+ subcommands: rows
136
+ });
137
+ }
138
+
139
+ function formatOptionSummary(optionRow = {}) {
140
+ const status = optionRow.required ? "required" : "optional";
141
+ const hasDefaultValue = String(optionRow.defaultValue || "").length > 0;
142
+ const optionalDefaultSuffix = optionRow.required
143
+ ? ""
144
+ : `; default: ${hasDefaultValue ? optionRow.defaultValue : "<empty>"}`;
145
+ const defaultSuffix = optionRow.required && hasDefaultValue
146
+ ? `; default: ${optionRow.defaultValue}`
147
+ : optionalDefaultSuffix;
148
+ const allowEmptySuffix = optionRow.allowEmpty ? "; allow-empty" : "";
149
+ const labelParts = [optionRow.promptLabel, optionRow.promptHint].filter(Boolean);
150
+ const label = labelParts.join(". ");
151
+ const typeSuffix = optionRow.inputType ? `<${optionRow.inputType}> ` : "";
152
+ const baseDescription = label || "No description provided.";
153
+ const description = optionRow.name === "placement-component-token"
154
+ ? `${baseDescription} Use jskit list-link-items to discover link-item tokens.`
155
+ : baseDescription;
156
+ return `- --${optionRow.name} ${typeSuffix}[${status}${defaultSuffix}${allowEmptySuffix}]: ${description}`.trim();
157
+ }
158
+
159
+ function formatPositionalArgUsageToken(arg = {}) {
160
+ const name = String(arg.name || "").trim();
161
+ if (!name) {
162
+ return "";
163
+ }
164
+ if (arg.required === false) {
165
+ return `[${name}]`;
166
+ }
167
+ return name;
168
+ }
169
+
170
+ function formatPositionalArgSummary(arg = {}) {
171
+ const name = String(arg.name || "").trim();
172
+ if (!name) {
173
+ return "";
174
+ }
175
+ const status = arg.required === false ? "optional" : "required";
176
+ const description = String(arg.description || "").trim() || "No description provided.";
177
+ return `- ${name} [${status}]: ${description}`;
178
+ }
179
+
180
+ function findGeneratorSubcommandRow(packageEntry = {}, subcommandName = "") {
181
+ const metadata = resolveGeneratorSubcommandMetadata(packageEntry);
182
+ const normalizedSubcommandName = String(subcommandName || "").trim().toLowerCase();
183
+ if (!normalizedSubcommandName) {
184
+ return null;
185
+ }
186
+ for (const row of ensureArray(metadata.subcommands)) {
187
+ if (String(row.name || "").trim().toLowerCase() === normalizedSubcommandName) {
188
+ return row;
189
+ }
190
+ }
191
+ return null;
192
+ }
193
+
194
+ function buildSubcommandOptionRows(optionRows = [], subcommandRow = {}) {
195
+ const packageOptionRows = ensureArray(optionRows);
196
+ const optionNames = ensureArray(subcommandRow.optionNames).map((value) => String(value || "").trim()).filter(Boolean);
197
+ const requiredOptionNames = new Set(
198
+ ensureArray(subcommandRow.requiredOptionNames).map((value) => String(value || "").trim()).filter(Boolean)
199
+ );
200
+ if (optionNames.length < 1) {
201
+ return packageOptionRows;
202
+ }
203
+
204
+ const optionRowsByName = new Map();
205
+ for (const optionRow of packageOptionRows) {
206
+ optionRowsByName.set(String(optionRow.name || "").trim(), optionRow);
207
+ }
208
+
209
+ const rows = [];
210
+ for (const optionName of optionNames) {
211
+ const optionRow = optionRowsByName.get(optionName);
212
+ if (optionRow) {
213
+ if (requiredOptionNames.size > 0) {
214
+ rows.push(Object.freeze({
215
+ ...optionRow,
216
+ required: requiredOptionNames.has(optionName)
217
+ }));
218
+ } else {
219
+ rows.push(optionRow);
220
+ }
221
+ }
222
+ }
223
+ return rows;
224
+ }
225
+
226
+ function renderGenerateCatalogHelp({
227
+ io,
228
+ packageRegistry,
229
+ resolvePackageKind,
230
+ json = false
231
+ } = {}) {
232
+ const generators = sortStrings([...packageRegistry.keys()])
233
+ .map((packageId) => packageRegistry.get(packageId))
234
+ .filter((entry) => resolvePackageKind(entry) === "generator")
235
+ .map((entry) => {
236
+ const packageId = String(entry?.packageId || "").trim();
237
+ return Object.freeze({
238
+ packageId,
239
+ shortId: toShortPackageId(packageId),
240
+ version: String(entry?.version || "").trim(),
241
+ description: resolvePackageSummary(entry)
242
+ });
243
+ });
244
+
245
+ if (json) {
246
+ io.stdout.write(`${JSON.stringify({
247
+ command: "generate",
248
+ generators,
249
+ usage: [
250
+ "jskit generate <generatorId> help",
251
+ "jskit generate <generatorId> [subcommand] [subcommand args...] [--<option> <value>...]",
252
+ "jskit list generators"
253
+ ]
254
+ }, null, 2)}\n`);
255
+ return;
256
+ }
257
+
258
+ const lines = [];
259
+ lines.push("Generate command");
260
+ lines.push("");
261
+ lines.push(`Available generators (${generators.length}):`);
262
+ for (const generator of generators) {
263
+ const shortIdSuffix =
264
+ generator.shortId && generator.shortId !== generator.packageId ? `${generator.shortId} ` : "";
265
+ const versionSuffix = generator.version ? ` (${generator.version})` : "";
266
+ const descriptionSuffix = generator.description ? `: ${generator.description}` : "";
267
+ lines.push(`- ${shortIdSuffix}${generator.packageId}${versionSuffix}${descriptionSuffix}`.trim());
268
+ }
269
+ lines.push("");
270
+ lines.push("Use:");
271
+ lines.push("- jskit generate <generatorId> help");
272
+ lines.push("- jskit generate <generatorId> [subcommand] [subcommand args...] [--<option> <value>...]");
273
+ lines.push("- jskit list generators");
274
+ io.stdout.write(`${lines.join("\n")}\n`);
275
+ }
276
+
277
+ function renderAddCatalogHelp({
278
+ io,
279
+ packageRegistry,
280
+ bundleRegistry,
281
+ resolvePackageKind,
282
+ json = false
283
+ } = {}) {
284
+ const bundles = sortStrings([...bundleRegistry.keys()]).map((bundleId) => {
285
+ const bundle = ensureObject(bundleRegistry.get(bundleId));
286
+ return Object.freeze({
287
+ bundleId,
288
+ version: String(bundle.version || "").trim(),
289
+ description: String(bundle.description || "").trim(),
290
+ packageCount: ensureArray(bundle.packages).length
291
+ });
292
+ });
293
+
294
+ const runtimePackages = sortStrings([...packageRegistry.keys()])
295
+ .map((packageId) => packageRegistry.get(packageId))
296
+ .filter((entry) => resolvePackageKind(entry) === "runtime")
297
+ .map((entry) => {
298
+ const packageId = String(entry?.packageId || "").trim();
299
+ return Object.freeze({
300
+ packageId,
301
+ shortId: toShortPackageId(packageId),
302
+ version: String(entry?.version || "").trim(),
303
+ description: resolvePackageSummary(entry)
304
+ });
305
+ });
306
+
307
+ if (json) {
308
+ io.stdout.write(`${JSON.stringify({
309
+ command: "add",
310
+ bundles,
311
+ runtimePackages,
312
+ usage: [
313
+ "jskit add package <packageId> help",
314
+ "jskit add bundle <bundleId> help",
315
+ "jskit add package <packageId> [--<option> <value>...]",
316
+ "jskit add bundle <bundleId> [--<option> <value>...]"
317
+ ]
318
+ }, null, 2)}\n`);
319
+ return;
320
+ }
321
+
322
+ const lines = [];
323
+ lines.push("Add command");
324
+ lines.push("");
325
+ lines.push(`Available bundles (${bundles.length}):`);
326
+ for (const bundle of bundles) {
327
+ const versionSuffix = bundle.version ? ` (${bundle.version})` : "";
328
+ const countSuffix = ` [packages:${bundle.packageCount}]`;
329
+ const descriptionSuffix = bundle.description ? `: ${bundle.description}` : "";
330
+ lines.push(`- ${bundle.bundleId}${versionSuffix}${countSuffix}${descriptionSuffix}`);
331
+ }
332
+ lines.push("");
333
+ lines.push(`Available runtime packages (${runtimePackages.length}):`);
334
+ for (const runtimePackage of runtimePackages) {
335
+ const shortIdSuffix =
336
+ runtimePackage.shortId && runtimePackage.shortId !== runtimePackage.packageId
337
+ ? `${runtimePackage.shortId} `
338
+ : "";
339
+ const versionSuffix = runtimePackage.version ? ` (${runtimePackage.version})` : "";
340
+ const descriptionSuffix = runtimePackage.description ? `: ${runtimePackage.description}` : "";
341
+ lines.push(`- ${shortIdSuffix}${runtimePackage.packageId}${versionSuffix}${descriptionSuffix}`.trim());
342
+ }
343
+ lines.push("");
344
+ lines.push("Use:");
345
+ lines.push("- jskit add package <packageId> help");
346
+ lines.push("- jskit add bundle <bundleId> help");
347
+ lines.push("- jskit add package <packageId> [--<option> <value>...]");
348
+ lines.push("- jskit add bundle <bundleId> [--<option> <value>...]");
349
+ io.stdout.write(`${lines.join("\n")}\n`);
350
+ }
351
+
352
+ function renderGeneratePackageHelp({
353
+ io,
354
+ packageEntry,
355
+ packageIdInput = "",
356
+ json = false
357
+ } = {}) {
358
+ const packageId = String(packageEntry?.packageId || "").trim();
359
+ const summary = resolvePackageSummary(packageEntry);
360
+ const optionRows = buildPackageOptionRows(packageEntry);
361
+ const generatorMetadata = resolveGeneratorSubcommandMetadata(packageEntry);
362
+ const preferredId = toShortPackageId(packageId) || packageId;
363
+ const usage = Object.freeze([
364
+ `jskit generate ${preferredId} help`,
365
+ `jskit generate ${preferredId} help <subcommand>`,
366
+ `jskit generate ${preferredId} <subcommand> help`,
367
+ `jskit generate ${preferredId} [subcommand] [subcommand args...] [--<option> <value>...]`
368
+ ]);
369
+ const resolvedFrom = String(packageIdInput || "").trim();
370
+ const resolvedFromLabel = resolvedFrom && resolvedFrom !== packageId ? resolvedFrom : "";
371
+ const hasRequiredWithDefaults = optionRows.some((row) => row.required && row.defaultValue);
372
+
373
+ if (json) {
374
+ io.stdout.write(`${JSON.stringify({
375
+ command: "generate",
376
+ targetType: "generator",
377
+ packageId,
378
+ resolvedFrom: resolvedFromLabel,
379
+ description: summary,
380
+ usage,
381
+ primarySubcommand: generatorMetadata.primarySubcommand || "",
382
+ subcommands: generatorMetadata.subcommands,
383
+ options: optionRows
384
+ }, null, 2)}\n`);
385
+ return;
386
+ }
387
+
388
+ const lines = [];
389
+ lines.push(`Generator help: ${packageId}`);
390
+ if (resolvedFromLabel) {
391
+ lines.push(`Resolved from: ${resolvedFromLabel}`);
392
+ }
393
+ if (summary) {
394
+ lines.push(`Description: ${summary}`);
395
+ }
396
+ lines.push("");
397
+ lines.push("Use:");
398
+ for (const usageLine of usage) {
399
+ lines.push(`- ${usageLine}`);
400
+ }
401
+ lines.push("");
402
+
403
+ const subcommands = ensureArray(generatorMetadata.subcommands);
404
+ if (subcommands.length > 0) {
405
+ lines.push(`Subcommands (${subcommands.length}):`);
406
+ for (const subcommand of subcommands) {
407
+ const primarySuffix = subcommand.primary ? " [primary]" : "";
408
+ const descriptionSuffix = subcommand.description ? `: ${subcommand.description}` : "";
409
+ lines.push(`- ${subcommand.name}${primarySuffix}${descriptionSuffix}`);
410
+ }
411
+ lines.push("- Use subcommand help for details: jskit generate <generatorId> <subcommand> help");
412
+ lines.push("");
413
+ }
414
+
415
+ lines.push(`Options (${optionRows.length}):`);
416
+ if (optionRows.length > 0) {
417
+ for (const optionRow of optionRows) {
418
+ lines.push(formatOptionSummary(optionRow));
419
+ }
420
+ } else {
421
+ lines.push("- No inline options.");
422
+ }
423
+ if (hasRequiredWithDefaults) {
424
+ lines.push("");
425
+ lines.push("Note: required options with defaults are auto-filled when omitted.");
426
+ }
427
+ io.stdout.write(`${lines.join("\n")}\n`);
428
+ }
429
+
430
+ function renderGenerateSubcommandHelp({
431
+ io,
432
+ packageEntry,
433
+ packageIdInput = "",
434
+ subcommandName = "",
435
+ json = false
436
+ } = {}) {
437
+ const packageId = String(packageEntry?.packageId || "").trim();
438
+ const summary = resolvePackageSummary(packageEntry);
439
+ const optionRows = buildPackageOptionRows(packageEntry);
440
+ const preferredId = toShortPackageId(packageId) || packageId;
441
+ const normalizedSubcommandName = String(subcommandName || "").trim();
442
+ const subcommandRow = findGeneratorSubcommandRow(packageEntry, normalizedSubcommandName);
443
+ if (!subcommandRow) {
444
+ return false;
445
+ }
446
+
447
+ const resolvedFrom = String(packageIdInput || "").trim();
448
+ const resolvedFromLabel = resolvedFrom && resolvedFrom !== packageId ? resolvedFrom : "";
449
+ const description = String(subcommandRow.description || "").trim();
450
+ const effectiveDescription = description || (
451
+ subcommandRow.primary
452
+ ? "Primary generator command."
453
+ : "No description provided."
454
+ );
455
+ const positionalArgs = ensureArray(subcommandRow.positionalArgs);
456
+ const positionalArgTokens = positionalArgs
457
+ .map((arg) => formatPositionalArgUsageToken(arg))
458
+ .filter(Boolean);
459
+ const invocationLine = `jskit generate ${preferredId} ${subcommandRow.name}${positionalArgTokens.length > 0
460
+ ? ` ${positionalArgTokens.join(" ")}`
461
+ : ""} [--<option> <value>...]`;
462
+ const usage = Object.freeze([
463
+ invocationLine,
464
+ `jskit generate ${preferredId} ${subcommandRow.name} help`
465
+ ]);
466
+ const subcommandOptionRows = buildSubcommandOptionRows(optionRows, subcommandRow);
467
+ const hasRequiredWithDefaults = subcommandOptionRows.some((row) => row.required && row.defaultValue);
468
+
469
+ if (json) {
470
+ io.stdout.write(`${JSON.stringify({
471
+ command: "generate",
472
+ targetType: "generator-subcommand-help",
473
+ packageId,
474
+ resolvedFrom: resolvedFromLabel,
475
+ generatorDescription: summary,
476
+ subcommand: {
477
+ name: subcommandRow.name,
478
+ primary: subcommandRow.primary,
479
+ description: effectiveDescription,
480
+ positionalArgs,
481
+ options: subcommandOptionRows
482
+ },
483
+ usage
484
+ }, null, 2)}\n`);
485
+ return true;
486
+ }
487
+
488
+ const lines = [];
489
+ lines.push(`Generator subcommand help: ${packageId} ${subcommandRow.name}`);
490
+ if (resolvedFromLabel) {
491
+ lines.push(`Resolved from: ${resolvedFromLabel}`);
492
+ }
493
+ if (summary) {
494
+ lines.push(`Generator: ${summary}`);
495
+ }
496
+ lines.push(`Description: ${effectiveDescription}`);
497
+ if (subcommandRow.primary) {
498
+ lines.push("Note: this is the primary generator command (same behavior as running without a subcommand).");
499
+ }
500
+ lines.push("");
501
+ lines.push("Use:");
502
+ for (const usageLine of usage) {
503
+ lines.push(`- ${usageLine}`);
504
+ }
505
+ lines.push("");
506
+ lines.push(`Positional args (${positionalArgs.length}):`);
507
+ if (positionalArgs.length > 0) {
508
+ for (const positionalArg of positionalArgs) {
509
+ lines.push(formatPositionalArgSummary(positionalArg));
510
+ }
511
+ } else {
512
+ lines.push("- No positional arguments.");
513
+ }
514
+ lines.push("");
515
+ lines.push(`Options (${subcommandOptionRows.length}):`);
516
+ if (subcommandOptionRows.length > 0) {
517
+ for (const optionRow of subcommandOptionRows) {
518
+ lines.push(formatOptionSummary(optionRow));
519
+ }
520
+ } else {
521
+ lines.push("- No inline options.");
522
+ }
523
+ if (hasRequiredWithDefaults) {
524
+ lines.push("");
525
+ lines.push("Note: required options with defaults are auto-filled when omitted.");
526
+ }
527
+ io.stdout.write(`${lines.join("\n")}\n`);
528
+ return true;
529
+ }
530
+
531
+ function renderAddPackageHelp({
532
+ io,
533
+ packageEntry,
534
+ packageIdInput = "",
535
+ json = false
536
+ } = {}) {
537
+ const packageId = String(packageEntry?.packageId || "").trim();
538
+ const summary = resolvePackageSummary(packageEntry);
539
+ const optionRows = buildPackageOptionRows(packageEntry);
540
+ const preferredId = toShortPackageId(packageId) || packageId;
541
+ const usage = Object.freeze([
542
+ `jskit add package ${preferredId} help`,
543
+ `jskit add package ${preferredId} [--<option> <value>...]`
544
+ ]);
545
+ const resolvedFrom = String(packageIdInput || "").trim();
546
+ const resolvedFromLabel = resolvedFrom && resolvedFrom !== packageId ? resolvedFrom : "";
547
+ const hasRequiredWithDefaults = optionRows.some((row) => row.required && row.defaultValue);
548
+
549
+ if (json) {
550
+ io.stdout.write(`${JSON.stringify({
551
+ command: "add",
552
+ targetType: "package",
553
+ packageId,
554
+ resolvedFrom: resolvedFromLabel,
555
+ description: summary,
556
+ usage,
557
+ options: optionRows
558
+ }, null, 2)}\n`);
559
+ return;
560
+ }
561
+
562
+ const lines = [];
563
+ lines.push(`Package help: ${packageId}`);
564
+ if (resolvedFromLabel) {
565
+ lines.push(`Resolved from: ${resolvedFromLabel}`);
566
+ }
567
+ if (summary) {
568
+ lines.push(`Description: ${summary}`);
569
+ }
570
+ lines.push("");
571
+ lines.push("Use:");
572
+ for (const usageLine of usage) {
573
+ lines.push(`- ${usageLine}`);
574
+ }
575
+ lines.push("");
576
+ lines.push(`Options (${optionRows.length}):`);
577
+ if (optionRows.length > 0) {
578
+ for (const optionRow of optionRows) {
579
+ lines.push(formatOptionSummary(optionRow));
580
+ }
581
+ } else {
582
+ lines.push("- No inline options.");
583
+ }
584
+ if (hasRequiredWithDefaults) {
585
+ lines.push("");
586
+ lines.push("Note: required options with defaults are auto-filled when omitted.");
587
+ }
588
+ io.stdout.write(`${lines.join("\n")}\n`);
589
+ }
590
+
591
+ function renderAddBundleHelp({
592
+ io,
593
+ bundleId = "",
594
+ bundle = {},
595
+ json = false
596
+ } = {}) {
597
+ const normalizedBundle = ensureObject(bundle);
598
+ const packageIds = ensureArray(normalizedBundle.packages).map((value) => String(value || "").trim()).filter(Boolean);
599
+ const description = String(normalizedBundle.description || "").trim();
600
+ const usage = Object.freeze([
601
+ `jskit add bundle ${bundleId} help`,
602
+ `jskit add bundle ${bundleId} [--<option> <value>...]`,
603
+ "jskit add package <packageId> help"
604
+ ]);
605
+
606
+ if (json) {
607
+ io.stdout.write(`${JSON.stringify({
608
+ command: "add",
609
+ targetType: "bundle",
610
+ bundleId,
611
+ description,
612
+ packages: packageIds,
613
+ usage
614
+ }, null, 2)}\n`);
615
+ return;
616
+ }
617
+
618
+ const lines = [];
619
+ lines.push(`Bundle help: ${bundleId}`);
620
+ if (description) {
621
+ lines.push(`Description: ${description}`);
622
+ }
623
+ lines.push("");
624
+ lines.push(`Included packages (${packageIds.length}):`);
625
+ for (const packageId of packageIds) {
626
+ lines.push(`- ${packageId}`);
627
+ }
628
+ lines.push("");
629
+ lines.push("Inline options:");
630
+ lines.push("- Bundles do not define direct options.");
631
+ lines.push("- Pass package options through using --<option> <value>.");
632
+ lines.push("- Use package help to inspect option contracts for included packages.");
633
+ lines.push("");
634
+ lines.push("Use:");
635
+ for (const usageLine of usage) {
636
+ lines.push(`- ${usageLine}`);
637
+ }
638
+ io.stdout.write(`${lines.join("\n")}\n`);
639
+ }
640
+
641
+ export {
642
+ isHelpToken,
643
+ renderGenerateCatalogHelp,
644
+ renderAddCatalogHelp,
645
+ renderGeneratePackageHelp,
646
+ renderGenerateSubcommandHelp,
647
+ renderAddPackageHelp,
648
+ renderAddBundleHelp
649
+ };