@jskit-ai/jskit-cli 0.2.39 → 0.2.41

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.
@@ -2,6 +2,8 @@ function createRunCli({
2
2
  parseArgs,
3
3
  printUsage,
4
4
  shouldShowCommandHelpOnBareInvocation,
5
+ validateCommandOptions,
6
+ resolveCommandDescriptor,
5
7
  commandHandlers,
6
8
  cleanupMaterializedPackageRoots,
7
9
  createCliError
@@ -15,6 +17,12 @@ function createRunCli({
15
17
  if (typeof shouldShowCommandHelpOnBareInvocation !== "function") {
16
18
  throw new TypeError("createRunCli requires shouldShowCommandHelpOnBareInvocation.");
17
19
  }
20
+ if (typeof validateCommandOptions !== "function") {
21
+ throw new TypeError("createRunCli requires validateCommandOptions.");
22
+ }
23
+ if (typeof resolveCommandDescriptor !== "function") {
24
+ throw new TypeError("createRunCli requires resolveCommandDescriptor.");
25
+ }
18
26
  if (!commandHandlers || typeof commandHandlers !== "object") {
19
27
  throw new TypeError("createRunCli requires commandHandlers.");
20
28
  }
@@ -33,6 +41,16 @@ function createRunCli({
33
41
 
34
42
  try {
35
43
  const { command, options, positional } = parseArgs(argv, { createCliError });
44
+ validateCommandOptions(
45
+ { command, positional, options },
46
+ {
47
+ createCliError,
48
+ renderUsage: () => {
49
+ const helpCommand = command === "help" ? String(positional[0] || "").trim() : command;
50
+ printUsage(stderr, { command: helpCommand });
51
+ }
52
+ }
53
+ );
36
54
  if (options.help || command === "help") {
37
55
  const helpCommand = command === "help" ? String(positional[0] || "").trim() : command;
38
56
  printUsage(stdout, { command: helpCommand });
@@ -44,85 +62,29 @@ function createRunCli({
44
62
  return 0;
45
63
  }
46
64
 
47
- if (command === "create") {
48
- return await commandHandlers.commandCreate({
49
- positional,
50
- options,
51
- cwd,
52
- io: { stdin, stdout, stderr }
53
- });
54
- }
55
- if (command === "list") {
56
- return await commandHandlers.commandList({ positional, options, cwd, stdout });
57
- }
58
- if (command === "list-placements") {
59
- return await commandHandlers.commandListPlacements({ options, cwd, stdout });
60
- }
61
- if (command === "list-link-items") {
62
- return await commandHandlers.commandListLinkItems({ options, cwd, stdout });
63
- }
64
- if (command === "show") {
65
- return await commandHandlers.commandShow({ positional, options, stdout });
66
- }
67
- if (command === "migrations") {
68
- return await commandHandlers.commandMigrations({
69
- positional,
70
- options,
71
- cwd,
72
- io: { stdin, stdout, stderr }
73
- });
74
- }
75
- if (command === "add") {
76
- return await commandHandlers.commandAdd({
77
- positional,
78
- options,
79
- cwd,
80
- io: { stdin, stdout, stderr }
81
- });
82
- }
83
- if (command === "generate") {
84
- return await commandHandlers.commandGenerate({
85
- positional,
86
- options,
87
- cwd,
88
- io: { stdin, stdout, stderr }
89
- });
90
- }
91
- if (command === "position") {
92
- return await commandHandlers.commandPosition({
93
- positional,
94
- options,
95
- cwd,
96
- io: { stdin, stdout, stderr }
97
- });
98
- }
99
- if (command === "update") {
100
- return await commandHandlers.commandUpdate({
65
+ const commandDescriptor = resolveCommandDescriptor(command);
66
+ const handlerName = String(commandDescriptor?.handlerName || "").trim();
67
+ if (handlerName) {
68
+ const commandHandler = commandHandlers[handlerName];
69
+ if (typeof commandHandler !== "function") {
70
+ throw createCliError(`Unhandled command: ${command}`, { showUsage: true });
71
+ }
72
+ return await commandHandler({
101
73
  positional,
102
74
  options,
103
75
  cwd,
76
+ stdout,
77
+ stderr,
104
78
  io: { stdin, stdout, stderr }
105
79
  });
106
80
  }
107
- if (command === "remove") {
108
- return await commandHandlers.commandRemove({
109
- positional,
110
- options,
111
- cwd,
112
- io: { stdin, stdout, stderr }
113
- });
114
- }
115
- if (command === "doctor") {
116
- return await commandHandlers.commandDoctor({ cwd, options, stdout });
117
- }
118
- if (command === "lint-descriptors") {
119
- return await commandHandlers.commandLintDescriptors({ options, stdout });
120
- }
121
81
 
122
82
  throw createCliError(`Unhandled command: ${command}`, { showUsage: true });
123
83
  } catch (error) {
124
84
  stderr.write(`jskit: ${error?.message || String(error)}\n`);
125
- if (error?.showUsage) {
85
+ if (typeof error?.renderUsage === "function") {
86
+ error.renderUsage();
87
+ } else if (error?.showUsage) {
126
88
  printUsage(stderr);
127
89
  }
128
90
  return 1;
@@ -1,349 +1,102 @@
1
- import { resolveCommandAlias } from "./commandCatalog.js";
2
-
3
- const COMMAND_OVERVIEW = Object.freeze([
4
- Object.freeze({
5
- command: "create",
6
- summary: "Scaffold an app-local runtime package."
7
- }),
8
- Object.freeze({
9
- command: "add",
10
- summary: "Install a runtime bundle or package into the current app."
11
- }),
12
- Object.freeze({
13
- command: "generate",
14
- summary: "Run a generator package (or generator subcommand)."
15
- }),
16
- Object.freeze({
17
- command: "list",
18
- summary: "List bundles, runtime packages, or generator packages."
19
- }),
20
- Object.freeze({
21
- command: "list-placements",
22
- summary: "List discovered UI placement targets."
23
- }),
24
- Object.freeze({
25
- command: "list-link-items",
26
- summary: "List available placement link-item component tokens."
27
- }),
28
- Object.freeze({
29
- command: "show",
30
- summary: "Show detailed metadata for a bundle or package."
31
- }),
32
- Object.freeze({
33
- command: "migrations",
34
- summary: "Generate managed migration files only."
35
- }),
36
- Object.freeze({
37
- command: "position",
38
- summary: "Re-apply positioning-only mutations for an installed package."
39
- }),
40
- Object.freeze({
41
- command: "update",
42
- summary: "Re-apply one installed package."
43
- }),
44
- Object.freeze({
45
- command: "remove",
46
- summary: "Remove one installed package."
47
- }),
48
- Object.freeze({
49
- command: "doctor",
50
- summary: "Validate lockfile and managed-file integrity."
51
- }),
52
- Object.freeze({
53
- command: "lint-descriptors",
54
- summary: "Validate bundle and package descriptor contracts."
55
- })
56
- ]);
57
-
58
- const COMMAND_HELP = Object.freeze({
59
- create: Object.freeze({
60
- title: "create",
61
- minimalUse: "jskit create package <name>",
62
- parameters: Object.freeze([
63
- Object.freeze({
64
- name: "<name>",
65
- description: "Local package slug used to scaffold packages/<name>."
66
- })
67
- ]),
68
- defaults: Object.freeze([
69
- "No npm install runs unless --run-npm-install is passed.",
70
- "If --scope is omitted, scope is inferred from app name.",
71
- "If --package-id is omitted, it is derived from scope + name."
72
- ]),
73
- fullUse: "jskit create package <name> [--scope <scope>] [--package-id <id>] [--description <text>] [--dry-run] [--run-npm-install] [--json]"
74
- }),
75
- add: Object.freeze({
76
- title: "add",
77
- minimalUse: "jskit add package <packageId>",
78
- parameters: Object.freeze([
79
- Object.freeze({
80
- name: "package | bundle",
81
- description: "Target type. Use package for one runtime package, bundle for a bundle id."
82
- }),
83
- Object.freeze({
84
- name: "<packageId|bundleId>",
85
- description: "Catalog id or installed node_modules package id."
86
- })
87
- ]),
88
- defaults: Object.freeze([
89
- "No npm install runs unless --run-npm-install is passed.",
90
- "Short ids resolve to @jskit-ai/<id> when available.",
91
- "Running without args lists bundles and runtime packages.",
92
- "Existing matching version is skipped unless options force reapply."
93
- ]),
94
- fullUse: "jskit add <package|bundle> <id> [--<option> <value>...] [--dry-run] [--run-npm-install] [--json] [--verbose]"
95
- }),
96
- generate: Object.freeze({
97
- title: "generate",
98
- minimalUse: "jskit generate <generatorId>",
99
- parameters: Object.freeze([
100
- Object.freeze({
101
- name: "<generatorId>",
102
- description: "Generator package id (for example: crud-ui-generator)."
103
- }),
104
- Object.freeze({
105
- name: "[subcommand]",
106
- description: "Optional generator subcommand (for example: scaffold or scaffold-field)."
107
- }),
108
- Object.freeze({
109
- name: "[subcommand args...]",
110
- description: "Optional positional args consumed by the chosen subcommand."
111
- })
112
- ]),
113
- defaults: Object.freeze([
114
- "No npm install runs unless --run-npm-install is passed.",
115
- "Short ids resolve to @jskit-ai/<id> when available.",
116
- "Running without args lists available generators.",
117
- "If no subcommand is provided, the generator primary command runs.",
118
- "Use jskit generate <generatorId> <subcommand> help for subcommand-specific usage."
119
- ]),
120
- fullUse: "jskit generate <generatorId> [subcommand] [subcommand args...] [--<option> <value>...] [--dry-run] [--run-npm-install] [--json] [--verbose]"
121
- }),
122
- list: Object.freeze({
123
- title: "list",
124
- minimalUse: "jskit list",
125
- parameters: Object.freeze([
126
- Object.freeze({
127
- name: "[mode]",
128
- description: "Optional mode: bundles, packages, or generators."
129
- })
130
- ]),
131
- defaults: Object.freeze([
132
- "Without mode, list prints bundles + runtime packages + generators.",
133
- "placements are listed by the dedicated list-placements command.",
134
- "--full and --expanded only affect bundle/package listing views."
135
- ]),
136
- fullUse: "jskit list [bundles|packages|generators] [--full] [--expanded] [--json]"
137
- }),
138
- "list-placements": Object.freeze({
139
- title: "list-placements",
140
- minimalUse: "jskit list-placements",
141
- parameters: Object.freeze([]),
142
- defaults: Object.freeze([
143
- "Discovers placement outlets from app Vue ShellOutlet tags and route meta.",
144
- "Includes placement outlets contributed by installed package metadata.",
145
- "Shows plain text by default; use --json for structured output."
146
- ]),
147
- fullUse: "jskit list-placements [--json]"
148
- }),
149
- "list-link-items": Object.freeze({
150
- title: "list-link-items",
151
- minimalUse: "jskit list-link-items",
152
- parameters: Object.freeze([
153
- Object.freeze({
154
- name: "[--prefix <value>]",
155
- description: "Optional token prefix filter (example: local.main. or users.web.shell.)."
156
- }),
157
- Object.freeze({
158
- name: "[--all]",
159
- description: "Include all discovered tokens (including non-link-item and client container/runtime tokens)."
160
- })
161
- ]),
162
- defaults: Object.freeze([
163
- "Default output shows link-item tokens only (token names ending with link-item).",
164
- "Default includes app and installed-package placement-linked token sources.",
165
- "Use --prefix to narrow quickly (recommended: --prefix local.main.).",
166
- "Use --all when you want the full discovered token set.",
167
- "Shows plain text by default; use --json for structured output."
168
- ]),
169
- fullUse: "jskit list-link-items [--prefix <value>] [--all] [--json]"
170
- }),
171
- show: Object.freeze({
172
- title: "show",
173
- minimalUse: "jskit show <id>",
174
- parameters: Object.freeze([
175
- Object.freeze({
176
- name: "<id>",
177
- description: "Bundle id or package id to inspect."
178
- })
179
- ]),
180
- defaults: Object.freeze([
181
- "view is an alias of show.",
182
- "Basic output is compact; --details expands capability and runtime sections.",
183
- "--debug-exports implies --details."
184
- ]),
185
- fullUse: "jskit show <id> [--details] [--debug-exports] [--json]"
186
- }),
187
- migrations: Object.freeze({
188
- title: "migrations",
189
- minimalUse: "jskit migrations changed",
190
- parameters: Object.freeze([
191
- Object.freeze({
192
- name: "<scope>",
193
- description: "all | changed | package."
194
- }),
195
- Object.freeze({
196
- name: "[packageId]",
197
- description: "Required only when scope is package."
198
- })
199
- ]),
200
- defaults: Object.freeze([
201
- "No npm install runs unless --run-npm-install is passed.",
202
- "Inline options are accepted only for 'migrations package <packageId>'.",
203
- "Without --json, output lists touched migration files."
204
- ]),
205
- fullUse: "jskit migrations <all|changed|package> [packageId] [--<option> <value>...] [--dry-run] [--json] [--verbose]"
206
- }),
207
- position: Object.freeze({
208
- title: "position",
209
- minimalUse: "jskit position element <packageId>",
210
- parameters: Object.freeze([
211
- Object.freeze({
212
- name: "element",
213
- description: "Target type for positioning command."
214
- }),
215
- Object.freeze({
216
- name: "<packageId>",
217
- description: "Installed package id to re-position."
218
- })
219
- ]),
220
- defaults: Object.freeze([
221
- "Only positioning mutations are applied.",
222
- "No npm install runs unless --run-npm-install is passed.",
223
- "Reads current options from lock unless overridden inline."
224
- ]),
225
- fullUse: "jskit position element <packageId> [--<option> <value>...] [--dry-run] [--json]"
226
- }),
227
- update: Object.freeze({
228
- title: "update",
229
- minimalUse: "jskit update package <packageId>",
230
- parameters: Object.freeze([
231
- Object.freeze({
232
- name: "package",
233
- description: "Target type for update command."
234
- }),
235
- Object.freeze({
236
- name: "<packageId>",
237
- description: "Installed package id to re-apply."
238
- })
239
- ]),
240
- defaults: Object.freeze([
241
- "No npm install runs unless --run-npm-install is passed.",
242
- "Existing lock options are reused unless overridden inline.",
243
- "update reuses add package flow with forced reapply."
244
- ]),
245
- fullUse: "jskit update package <packageId> [--<option> <value>...] [--dry-run] [--run-npm-install] [--json]"
246
- }),
247
- remove: Object.freeze({
248
- title: "remove",
249
- minimalUse: "jskit remove package <packageId>",
250
- parameters: Object.freeze([
251
- Object.freeze({
252
- name: "package",
253
- description: "Target type for remove command."
254
- }),
255
- Object.freeze({
256
- name: "<packageId>",
257
- description: "Installed package id to remove."
258
- })
259
- ]),
260
- defaults: Object.freeze([
261
- "No npm install runs unless --run-npm-install is passed.",
262
- "Managed files and lock entries are removed for the package.",
263
- "Local package source directories are not deleted."
264
- ]),
265
- fullUse: "jskit remove package <packageId> [--dry-run] [--run-npm-install] [--json]"
266
- }),
267
- doctor: Object.freeze({
268
- title: "doctor",
269
- minimalUse: "jskit doctor",
270
- parameters: Object.freeze([]),
271
- defaults: Object.freeze([
272
- "Validates lock entries, managed files, and registry visibility.",
273
- "Reports issues as plain text by default.",
274
- "Use --json for machine-readable diagnostics."
275
- ]),
276
- fullUse: "jskit doctor [--json]"
277
- }),
278
- "lint-descriptors": Object.freeze({
279
- title: "lint-descriptors",
280
- minimalUse: "jskit lint-descriptors",
281
- parameters: Object.freeze([]),
282
- defaults: Object.freeze([
283
- "Runs descriptor consistency checks.",
284
- "check-di-labels is optional and adds stricter DI token label checks.",
285
- "Outputs plain text by default and supports --json."
286
- ]),
287
- fullUse: "jskit lint-descriptors [--check-di-labels] [--json]"
288
- })
289
- });
1
+ import {
2
+ listOverviewCommandDescriptors,
3
+ resolveCommandDescriptor,
4
+ shouldShowCommandHelpOnBareInvocation
5
+ } from "./commandCatalog.js";
6
+ import {
7
+ createColorFormatter,
8
+ writeWrappedLines
9
+ } from "../shared/outputFormatting.js";
290
10
 
291
- const BARE_COMMAND_HELP = new Set([
292
- "create",
293
- "show",
294
- "migrations",
295
- "position",
296
- "update",
297
- "remove"
298
- ]);
11
+ function appendSeparatedBlocks(lines = [], blocks = []) {
12
+ const normalizedBlocks = Array.isArray(blocks) ? blocks : [];
13
+ for (const [index, block] of normalizedBlocks.entries()) {
14
+ if (index > 0) {
15
+ lines.push("");
16
+ }
17
+ const lineList = Array.isArray(block) ? block : [block];
18
+ for (const line of lineList) {
19
+ lines.push(line);
20
+ }
21
+ }
22
+ }
299
23
 
300
- function writeLine(stream, line = "") {
301
- stream.write(`${line}\n`);
24
+ function writeHelpLines(stream, lines = []) {
25
+ writeWrappedLines({
26
+ stdout: stream,
27
+ lines
28
+ });
302
29
  }
303
30
 
304
31
  function printTopLevelHelp(stream = process.stderr) {
305
- writeLine(stream, "JSKit CLI");
306
- writeLine(stream, "");
307
- writeLine(stream, "Use: jskit help <command> for command-specific usage.");
308
- writeLine(stream, "");
309
- writeLine(stream, "Available commands:");
310
- for (const entry of COMMAND_OVERVIEW) {
311
- writeLine(stream, ` ${entry.command.padEnd(16, " ")} ${entry.summary}`);
32
+ const color = createColorFormatter(stream);
33
+ const lines = [];
34
+ lines.push(color.heading("JSKit CLI"));
35
+ lines.push("");
36
+ lines.push("Use: jskit help <command> for command-specific usage.");
37
+ lines.push("");
38
+ lines.push(color.heading("Available commands:"));
39
+ for (const entry of listOverviewCommandDescriptors()) {
40
+ lines.push(` ${color.item(entry.command.padEnd(16, " "))} ${entry.summary}`);
312
41
  }
313
- writeLine(stream, "");
314
- writeLine(stream, "Global flags:");
315
- writeLine(stream, " --dry-run --run-npm-install --json --verbose --help");
42
+ writeHelpLines(stream, lines);
316
43
  }
317
44
 
318
45
  function printCommandHelp(stream = process.stderr, command = "") {
319
- const resolvedCommand = resolveCommandAlias(command);
320
- const entry = COMMAND_HELP[resolvedCommand];
46
+ const entry = resolveCommandDescriptor(command);
321
47
  if (!entry) {
322
48
  printTopLevelHelp(stream);
323
49
  return;
324
50
  }
325
51
 
326
- writeLine(stream, `Command: ${entry.title}`);
327
- writeLine(stream, "");
52
+ const color = createColorFormatter(stream);
53
+ const lines = [];
54
+ lines.push(`Command: ${color.emphasis(entry.command)}`);
55
+ lines.push("");
56
+
57
+ let sectionNumber = 1;
328
58
 
329
- writeLine(stream, "1) Minimal use");
330
- writeLine(stream, ` ${entry.minimalUse}`);
59
+ lines.push(color.heading(`${sectionNumber++}) Minimal use`));
60
+ lines.push(` ${entry.minimalUse}`);
331
61
  if (entry.parameters.length > 0) {
332
- writeLine(stream, " Parameters:");
333
- for (const parameter of entry.parameters) {
334
- writeLine(stream, ` - ${parameter.name}: ${parameter.description}`);
335
- }
62
+ lines.push(color.heading(" Parameters:"));
63
+ appendSeparatedBlocks(
64
+ lines,
65
+ entry.parameters.map((parameter) => ` - ${parameter.name}: ${parameter.description}`)
66
+ );
336
67
  }
337
- writeLine(stream, "");
68
+ lines.push("");
69
+
70
+ lines.push(color.heading(`${sectionNumber++}) Defaults`));
71
+ appendSeparatedBlocks(
72
+ lines,
73
+ entry.defaults.map((defaultLine) => ` - ${defaultLine}`)
74
+ );
75
+ lines.push("");
338
76
 
339
- writeLine(stream, "2) Defaults");
340
- for (const defaultLine of entry.defaults) {
341
- writeLine(stream, ` - ${defaultLine}`);
77
+ const examples = Array.isArray(entry.examples) ? entry.examples : [];
78
+ if (examples.length > 0) {
79
+ lines.push(color.heading(`${sectionNumber++}) Examples`));
80
+ appendSeparatedBlocks(
81
+ lines,
82
+ examples.map((example) => {
83
+ const block = [];
84
+ const label = String(example?.label || "").trim();
85
+ if (label) {
86
+ block.push(` - ${color.item(label)}`);
87
+ }
88
+ for (const line of Array.isArray(example?.lines) ? example.lines : []) {
89
+ block.push(` ${line}`);
90
+ }
91
+ return block;
92
+ })
93
+ );
94
+ lines.push("");
342
95
  }
343
- writeLine(stream, "");
344
96
 
345
- writeLine(stream, "3) Full use");
346
- writeLine(stream, ` ${entry.fullUse}`);
97
+ lines.push(color.heading(`${sectionNumber++}) Full use`));
98
+ lines.push(` ${entry.fullUse}`);
99
+ writeHelpLines(stream, lines);
347
100
  }
348
101
 
349
102
  function printUsage(stream = process.stderr, { command = "" } = {}) {
@@ -356,15 +109,6 @@ function printUsage(stream = process.stderr, { command = "" } = {}) {
356
109
  printCommandHelp(stream, normalizedCommand);
357
110
  }
358
111
 
359
- function shouldShowCommandHelpOnBareInvocation(command = "", positional = []) {
360
- const resolvedCommand = resolveCommandAlias(command);
361
- const argumentList = Array.isArray(positional) ? positional : [];
362
- if (!BARE_COMMAND_HELP.has(resolvedCommand)) {
363
- return false;
364
- }
365
- return argumentList.length < 1;
366
- }
367
-
368
112
  export {
369
113
  printUsage,
370
114
  shouldShowCommandHelpOnBareInvocation
@@ -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