@backstage/cli-node 0.3.5-next.0 → 0.3.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # @backstage/cli-node
2
2
 
3
+ ## 0.3.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 3d6721d: Add intent-based CLI modules for catalog, scaffolder, and search.
8
+
9
+ New modules provide domain-oriented subcommands that talk directly to the corresponding plugins' REST APIs:
10
+
11
+ - `@backstage/cli-module-catalog`: `catalog list`, `catalog get`, `catalog validate`, `catalog register`, `catalog unregister`
12
+ - `@backstage/cli-module-scaffolder`: `template list`, `template execute`, `template dry-run`
13
+ - `@backstage/cli-module-search`: `search`, `docs search`
14
+
15
+ The commands take their main input as a positional argument: an entity or template reference, a file path (or `-` for standard input) for `catalog validate` and `template dry-run`, a URL for `catalog register`, and the search terms for `search` and `docs search`. Repeatable `key=value` flags are used for filters, template values, and secrets, and comma-separated lists for `--fields`. Unknown flags are rejected.
16
+
17
+ The commands that list results, `catalog list`, `template list`, `search`, and `docs search`, support `--output human` (the default) or `--output json`, along with `--limit` and `--cursor` for paging. Each command prints a single page. Human output shows how many results were shown and, when more are available, prints the `--cursor` value to continue from. All other commands always print JSON. `catalog validate` checks every entity in a multi-document YAML file and prints a JSON array with one result per entity. `catalog validate` and `template dry-run` exit with a non-zero code when the input is invalid, while connection and authentication problems are reported as errors.
18
+
19
+ `@backstage/cli-node` now provides shared parsers for repeatable `key=value` inputs and comma-separated lists.
20
+
21
+ - 7c6ab3d: Commands run through `runCli` now wait for `stdout` and `stderr` to be flushed before exiting, so large command output is no longer truncated on platforms where writes to `stdout` and `stderr` are asynchronous.
22
+ - 6dc6bc6: Fixed CLIs created with `runCli` silently exiting when the root or a command group is invoked without a subcommand. They now display help for that level and complete successfully.
23
+
24
+ Help output now lists groups and commands in separate, alphabetically sorted sections. Group previews adapt to the terminal width, using 80 columns when unavailable. Nested groups expand breadth-first when space permits; unexpanded groups retain a trailing slash and truncated previews end in an ellipsis.
25
+
26
+ - Updated dependencies
27
+ - @backstage/errors@1.3.2
28
+ - @backstage/cli-common@0.3.2
29
+
30
+ ## 0.3.5-next.1
31
+
32
+ ### Patch Changes
33
+
34
+ - 7c6ab3d: Commands run through `runCli` now wait for `stdout` and `stderr` to be flushed before exiting, so large command output is no longer truncated on platforms where writes to `stdout` and `stderr` are asynchronous.
35
+ - 6dc6bc6: Fixed CLIs created with `runCli` silently exiting when the root or a command group is invoked without a subcommand. They now display help for that level and complete successfully.
36
+
37
+ Help output now lists groups and commands in separate, alphabetically sorted sections. Group previews adapt to the terminal width, using 80 columns when unavailable. Nested groups expand breadth-first when space permits; unexpanded groups retain a trailing slash and truncated previews end in an ellipsis.
38
+
3
39
  ## 0.3.5-next.0
4
40
 
5
41
  ### Patch Changes
@@ -22,6 +22,13 @@ ${chalk__default.default.red(message)}
22
22
  `);
23
23
  process.exit(code);
24
24
  }
25
+ async function flushStdio() {
26
+ await Promise.all(
27
+ [process.stdout, process.stderr].map(
28
+ (stream) => new Promise((resolve) => stream.write("", () => resolve()))
29
+ )
30
+ );
31
+ }
25
32
  function exitWithError(error) {
26
33
  if (!errors.isError(error)) {
27
34
  return exit(errors.stringifyError(error));
@@ -70,30 +77,97 @@ function getNodeName(node) {
70
77
  }
71
78
  return InternalCommandNode.OpaqueCommandLeafNode.toInternal(node).name;
72
79
  }
73
- function getNodeDescription(node) {
74
- if (InternalCommandNode.OpaqueCommandTreeNode.isType(node)) {
75
- return InternalCommandNode.OpaqueCommandTreeNode.toInternal(node).name;
80
+ function createGroupPreview(children, width) {
81
+ const entries = children.filter((node) => !InternalCommandNode.isCommandNodeHidden(node)).sort((a, b) => getNodeName(a).localeCompare(getNodeName(b), "en")).map((node) => ({ node, path: getNodeName(node) }));
82
+ const format = (items) => items.map(
83
+ ({ node, path }) => path + (InternalCommandNode.OpaqueCommandTreeNode.isType(node) ? "/" : "")
84
+ ).join(", ");
85
+ if (format(entries).length > width) {
86
+ while (entries.length > 0) {
87
+ entries.pop();
88
+ const preview = format(entries);
89
+ if (preview.length + 3 <= width) {
90
+ return preview ? `${preview}, \u2026` : "\u2026";
91
+ }
92
+ }
93
+ return "\u2026";
94
+ }
95
+ const queue = [...entries];
96
+ for (const entry of queue) {
97
+ if (!InternalCommandNode.OpaqueCommandTreeNode.isType(entry.node)) {
98
+ continue;
99
+ }
100
+ const expanded = InternalCommandNode.OpaqueCommandTreeNode.toInternal(entry.node).children.filter((node) => !InternalCommandNode.isCommandNodeHidden(node)).sort((a, b) => getNodeName(a).localeCompare(getNodeName(b), "en")).map((node) => ({ node, path: `${entry.path} ${getNodeName(node)}` }));
101
+ const index = entries.indexOf(entry);
102
+ const candidate = [...entries];
103
+ candidate.splice(index, 1, ...expanded);
104
+ if (format(candidate).length <= width) {
105
+ entries.splice(index, 1, ...expanded);
106
+ queue.push(...expanded);
107
+ }
76
108
  }
77
- return InternalCommandNode.OpaqueCommandLeafNode.toInternal(node).command.description;
109
+ return format(entries);
78
110
  }
79
111
  function createHelpOptions(options) {
80
112
  const { nodes, includeHelpCommand, version } = options;
81
113
  const visibleNodes = nodes.filter((node) => !InternalCommandNode.isCommandNodeHidden(node));
114
+ const groups = visibleNodes.filter(InternalCommandNode.OpaqueCommandTreeNode.isType);
115
+ const previewWidth = Math.max(
116
+ 1,
117
+ (process.stdout.columns || 80) - Math.max(0, ...groups.map((node) => getNodeName(node).length)) - 10
118
+ );
119
+ const groupRows = [];
120
+ const commandRows = [];
121
+ for (const node of visibleNodes) {
122
+ if (InternalCommandNode.OpaqueCommandTreeNode.isType(node)) {
123
+ const { name, children } = InternalCommandNode.OpaqueCommandTreeNode.toInternal(node);
124
+ groupRows.push([name, createGroupPreview(children, previewWidth)]);
125
+ } else {
126
+ const { name, command: cmd } = InternalCommandNode.OpaqueCommandLeafNode.toInternal(node);
127
+ commandRows.push([name, cmd.description]);
128
+ }
129
+ }
130
+ if (includeHelpCommand) {
131
+ commandRows.push(["help", "Display help for command"]);
132
+ }
133
+ for (const rows of [groupRows, commandRows]) {
134
+ rows.sort(([a], [b]) => a.localeCompare(b, "en"));
135
+ }
82
136
  return {
83
137
  version,
84
138
  render(nodesToRender, renderers) {
85
- const commandsNode = nodesToRender.find((node) => node.id === "commands");
86
- if (commandsNode) {
87
- const commandRows = visibleNodes.map((node) => [
88
- getNodeName(node),
89
- getNodeDescription(node)
90
- ]);
91
- if (includeHelpCommand) {
92
- commandRows.push(["help", "Display help for command"]);
93
- }
94
- commandsNode.data.body.data.tableData = commandRows;
95
- }
96
- return renderers.render(nodesToRender);
139
+ return renderers.render(
140
+ nodesToRender.flatMap((node) => {
141
+ if (node.id !== "commands") {
142
+ return [node];
143
+ }
144
+ return [
145
+ { title: "Groups:", rows: groupRows },
146
+ { title: "Commands:", rows: commandRows }
147
+ ].filter((section) => section.rows.length > 0).map(
148
+ ({ title, rows }) => ({
149
+ type: "section",
150
+ data: {
151
+ title,
152
+ indentBody: 0,
153
+ body: {
154
+ type: "table",
155
+ data: {
156
+ tableData: rows,
157
+ tableOptions: [
158
+ {
159
+ width: "content-width",
160
+ paddingLeft: 2,
161
+ paddingRight: 8
162
+ }
163
+ ]
164
+ }
165
+ }
166
+ }
167
+ })
168
+ );
169
+ })
170
+ );
97
171
  }
98
172
  };
99
173
  }
@@ -139,6 +213,7 @@ async function runCommandLevel(options) {
139
213
  commandArgs,
140
214
  programName
141
215
  );
216
+ await flushStdio();
142
217
  process.exit(0);
143
218
  }
144
219
  } catch (error) {
@@ -192,6 +267,7 @@ async function runCommandLevel(options) {
192
267
  return;
193
268
  }
194
269
  if (argv.length === 0) {
270
+ parsed.showHelp();
195
271
  return;
196
272
  }
197
273
  console.log();
@@ -1 +1 @@
1
- {"version":3,"file":"runCli.cjs.js","sources":["../../src/cli-module/runCli.ts"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n OpaqueCliModule,\n OpaqueCommandLeafNode,\n OpaqueCommandTreeNode,\n isCommandNodeHidden,\n} from '@internal/cli';\nimport type { CommandNode } from '@internal/cli';\nimport { ForwardedError, isError, stringifyError } from '@backstage/errors';\nimport chalk from 'chalk';\nimport { cli, command } from 'cleye';\nimport type { Renderers } from 'cleye';\nimport { CommandGraph } from './CommandGraph';\nimport type { CliCommand, CliModule } from './types';\n\ninterface HelpNode {\n id?: string;\n type: keyof Renderers;\n data: unknown;\n}\n\ninterface CommandsHelpData {\n body: {\n data: {\n tableData: string[][];\n };\n };\n}\n\nfunction exit(message: string, code: number = 1): never {\n process.stderr.write(`\\n${chalk.red(message)}\\n\\n`);\n process.exit(code);\n}\n\nfunction exitWithError(error: unknown): never {\n if (!isError(error)) {\n return exit(stringifyError(error));\n }\n\n switch (error.name) {\n case 'InputError':\n return exit(error.message, 74 /* input/output error */);\n case 'NotFoundError':\n return exit(error.message, 127 /* command not found */);\n case 'NotImplementedError':\n return exit(error.message, 64 /* command line usage error */);\n case 'AuthenticationError':\n case 'NotAllowedError':\n return exit(error.message, 77 /* permission denied */);\n case 'ExitCodeError':\n return exit(\n error.message,\n 'code' in error && typeof error.code === 'number' ? error.code : 1,\n );\n default:\n return exit(\n stringifyError(error),\n 'code' in error && typeof error.code === 'number' ? error.code : 1,\n );\n }\n}\n\nfunction getNodeName(node: CommandNode): string {\n if (OpaqueCommandTreeNode.isType(node)) {\n return OpaqueCommandTreeNode.toInternal(node).name;\n }\n return OpaqueCommandLeafNode.toInternal(node).name;\n}\n\nfunction getNodeDescription(node: CommandNode): string {\n if (OpaqueCommandTreeNode.isType(node)) {\n return OpaqueCommandTreeNode.toInternal(node).name;\n }\n return OpaqueCommandLeafNode.toInternal(node).command.description;\n}\n\nfunction createHelpOptions(options: {\n nodes: ReadonlyArray<CommandNode>;\n includeHelpCommand: boolean;\n version?: string;\n}) {\n const { nodes, includeHelpCommand, version } = options;\n const visibleNodes = nodes.filter(node => !isCommandNodeHidden(node));\n\n return {\n version,\n render(nodesToRender: HelpNode[], renderers: Renderers) {\n const commandsNode = nodesToRender.find(node => node.id === 'commands');\n if (commandsNode) {\n const commandRows = visibleNodes.map(node => [\n getNodeName(node),\n getNodeDescription(node),\n ]);\n if (includeHelpCommand) {\n commandRows.push(['help', 'Display help for command']);\n }\n (commandsNode.data as CommandsHelpData).body.data.tableData =\n commandRows;\n }\n return renderers.render(nodesToRender);\n },\n };\n}\n\nasync function executeCommand(\n commandToExecute: CliCommand,\n args: string[],\n programName: string,\n): Promise<void> {\n const context = {\n args,\n info: {\n usage: [programName, ...commandToExecute.path].join(' '),\n name: commandToExecute.path.join(' '),\n },\n };\n\n if (typeof commandToExecute.execute === 'function') {\n await commandToExecute.execute(context);\n } else {\n const mod = await commandToExecute.execute.loader();\n const fn =\n typeof mod.default === 'function'\n ? mod.default\n : (mod.default as any).default;\n await fn(context);\n }\n}\n\nasync function runCommandLevel(options: {\n nodes: ReadonlyArray<CommandNode>;\n argv: string[];\n programName: string;\n commandPath?: string[];\n version?: string;\n}): Promise<void> {\n const { nodes, argv, programName, commandPath = [], version } = options;\n const name = [programName, ...commandPath].join(' ');\n const commandArgs = argv.slice(1);\n const commands = nodes.map(node =>\n command(\n {\n name: getNodeName(node),\n help: false,\n },\n async () => {\n try {\n if (OpaqueCommandTreeNode.isType(node)) {\n await runCommandLevel({\n nodes: OpaqueCommandTreeNode.toInternal(node).children,\n argv: commandArgs,\n programName,\n commandPath: [...commandPath, getNodeName(node)],\n version,\n });\n } else {\n await executeCommand(\n OpaqueCommandLeafNode.toInternal(node).command,\n commandArgs,\n programName,\n );\n process.exit(0);\n }\n } catch (error: unknown) {\n exitWithError(error);\n }\n },\n ),\n );\n const includeHelpCommand = !nodes.some(node => getNodeName(node) === 'help');\n if (includeHelpCommand && nodes.length > 0) {\n commands.push(\n command(\n {\n name: 'help',\n parameters: ['[command...]'],\n help: false,\n },\n async () => {\n await runCommandLevel({\n nodes,\n argv: [...commandArgs, '--help'],\n programName,\n commandPath,\n version,\n });\n },\n ),\n );\n }\n\n await cli(\n {\n name,\n flags: version\n ? {\n version: {\n type: Boolean,\n alias: 'V',\n description: 'Show version',\n },\n }\n : undefined,\n commands,\n help: createHelpOptions({\n nodes,\n includeHelpCommand: includeHelpCommand && nodes.length > 0,\n version,\n }),\n },\n parsed => {\n if (version && parsed.flags.version) {\n console.log(version);\n process.exit(0);\n return;\n }\n\n if (argv.length === 0) {\n return;\n }\n\n console.log();\n console.log(\n chalk.red(`Invalid command: ${[...commandPath, ...argv].join(' ')}`),\n );\n console.log();\n parsed.showHelp();\n process.exit(1);\n },\n [...argv],\n );\n}\n\nfunction handleUnhandledRejection(rejection: unknown): void {\n exitWithError(new ForwardedError('Unhandled rejection', rejection));\n}\n\nfunction hasVersionFlag(args: string[]): boolean {\n const separatorIndex = args.indexOf('--');\n const options = separatorIndex === -1 ? args : args.slice(0, separatorIndex);\n return options.includes('-V') || options.includes('--version');\n}\n\n/**\n * Runs a collection of CLI modules as an executable program.\n *\n * This is intended for creating custom CLI packages from a fixed set of\n * directly imported modules. Module discovery and override behavior are left\n * to the caller.\n *\n * @example\n * ```ts\n * import { runCli } from '@backstage/cli-node';\n * import buildModule from '@backstage/cli-module-build';\n * import testModule from '@backstage/cli-module-test-jest';\n * import packageJson from '../package.json';\n *\n * runCli({\n * modules: [buildModule, testModule],\n * name: packageJson.name,\n * version: packageJson.version,\n * });\n * ```\n *\n * @public\n */\nexport async function runCli(options: {\n /** The CLI modules whose commands are included in the program. */\n modules: ReadonlyArray<CliModule>;\n /** The program name shown in help output and usage strings. */\n name: string;\n /** The version string shown when `--version` is passed. */\n version?: string;\n}): Promise<void> {\n const { modules, name, version } = options;\n const graph = new CommandGraph();\n\n for (const module of modules) {\n if (!OpaqueCliModule.isType(module)) {\n throw new Error(\n `Invalid CLI module: expected a module created with createCliModule`,\n );\n }\n\n for (const commandToAdd of await OpaqueCliModule.toInternal(module)\n .commands) {\n graph.add(commandToAdd, module);\n }\n }\n\n if (\n !process.listeners('unhandledRejection').includes(handleUnhandledRejection)\n ) {\n process.on('unhandledRejection', handleUnhandledRejection);\n }\n\n const args = process.argv.slice(2);\n if (version && hasVersionFlag(args)) {\n console.log(version);\n process.exit(0);\n return;\n }\n\n await runCommandLevel({\n nodes: graph.roots,\n argv: args,\n programName: name,\n version,\n });\n}\n"],"names":["chalk","isError","stringifyError","OpaqueCommandTreeNode","OpaqueCommandLeafNode","isCommandNodeHidden","command","cli","ForwardedError","CommandGraph","OpaqueCliModule"],"mappings":"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"}
1
+ {"version":3,"file":"runCli.cjs.js","sources":["../../src/cli-module/runCli.ts"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n OpaqueCliModule,\n OpaqueCommandLeafNode,\n OpaqueCommandTreeNode,\n isCommandNodeHidden,\n} from '@internal/cli';\nimport type { CommandNode } from '@internal/cli';\nimport { ForwardedError, isError, stringifyError } from '@backstage/errors';\nimport chalk from 'chalk';\nimport { cli, command } from 'cleye';\nimport type { Renderers } from 'cleye';\nimport { CommandGraph } from './CommandGraph';\nimport type { CliCommand, CliModule } from './types';\n\ninterface HelpNode {\n id?: string;\n type: keyof Renderers;\n data: unknown;\n}\n\nfunction exit(message: string, code: number = 1): never {\n process.stderr.write(`\\n${chalk.red(message)}\\n\\n`);\n process.exit(code);\n}\n\n/**\n * Waits for any output queued on stdout and stderr to be handed off to the OS.\n * Depending on the platform and stream type (e.g. pipes on macOS), these writes\n * can be asynchronous, so exiting immediately after a large write can truncate\n * the output.\n */\nasync function flushStdio(): Promise<void> {\n await Promise.all(\n [process.stdout, process.stderr].map(\n stream => new Promise<void>(resolve => stream.write('', () => resolve())),\n ),\n );\n}\n\nfunction exitWithError(error: unknown): never {\n if (!isError(error)) {\n return exit(stringifyError(error));\n }\n\n switch (error.name) {\n case 'InputError':\n return exit(error.message, 74 /* input/output error */);\n case 'NotFoundError':\n return exit(error.message, 127 /* command not found */);\n case 'NotImplementedError':\n return exit(error.message, 64 /* command line usage error */);\n case 'AuthenticationError':\n case 'NotAllowedError':\n return exit(error.message, 77 /* permission denied */);\n case 'ExitCodeError':\n return exit(\n error.message,\n 'code' in error && typeof error.code === 'number' ? error.code : 1,\n );\n default:\n return exit(\n stringifyError(error),\n 'code' in error && typeof error.code === 'number' ? error.code : 1,\n );\n }\n}\n\nfunction getNodeName(node: CommandNode): string {\n if (OpaqueCommandTreeNode.isType(node)) {\n return OpaqueCommandTreeNode.toInternal(node).name;\n }\n return OpaqueCommandLeafNode.toInternal(node).name;\n}\n\nfunction createGroupPreview(\n children: ReadonlyArray<CommandNode>,\n width: number,\n): string {\n const entries = children\n .filter(node => !isCommandNodeHidden(node))\n .sort((a, b) => getNodeName(a).localeCompare(getNodeName(b), 'en'))\n .map(node => ({ node, path: getNodeName(node) }));\n const format = (items: typeof entries) =>\n items\n .map(\n ({ node, path }) =>\n path + (OpaqueCommandTreeNode.isType(node) ? '/' : ''),\n )\n .join(', ');\n\n if (format(entries).length > width) {\n while (entries.length > 0) {\n entries.pop();\n const preview = format(entries);\n if (preview.length + 3 <= width) {\n return preview ? `${preview}, …` : '…';\n }\n }\n return '…';\n }\n\n // Expand breadth-first, preserving every sibling before adding more detail.\n const queue = [...entries];\n for (const entry of queue) {\n if (!OpaqueCommandTreeNode.isType(entry.node)) {\n continue;\n }\n const expanded = OpaqueCommandTreeNode.toInternal(entry.node)\n .children.filter(node => !isCommandNodeHidden(node))\n .sort((a, b) => getNodeName(a).localeCompare(getNodeName(b), 'en'))\n .map(node => ({ node, path: `${entry.path} ${getNodeName(node)}` }));\n const index = entries.indexOf(entry);\n const candidate = [...entries];\n candidate.splice(index, 1, ...expanded);\n if (format(candidate).length <= width) {\n entries.splice(index, 1, ...expanded);\n queue.push(...expanded);\n }\n }\n return format(entries);\n}\n\nfunction createHelpOptions(options: {\n nodes: ReadonlyArray<CommandNode>;\n includeHelpCommand: boolean;\n version?: string;\n}) {\n const { nodes, includeHelpCommand, version } = options;\n const visibleNodes = nodes.filter(node => !isCommandNodeHidden(node));\n const groups = visibleNodes.filter(OpaqueCommandTreeNode.isType);\n // Match the table's name column and padding, with 80 columns for piped output.\n const previewWidth = Math.max(\n 1,\n (process.stdout.columns || 80) -\n Math.max(0, ...groups.map(node => getNodeName(node).length)) -\n 10,\n );\n const groupRows: string[][] = [];\n const commandRows: string[][] = [];\n\n for (const node of visibleNodes) {\n if (OpaqueCommandTreeNode.isType(node)) {\n const { name, children } = OpaqueCommandTreeNode.toInternal(node);\n groupRows.push([name, createGroupPreview(children, previewWidth)]);\n } else {\n const { name, command: cmd } = OpaqueCommandLeafNode.toInternal(node);\n commandRows.push([name, cmd.description]);\n }\n }\n if (includeHelpCommand) {\n commandRows.push(['help', 'Display help for command']);\n }\n for (const rows of [groupRows, commandRows]) {\n rows.sort(([a], [b]) => a.localeCompare(b, 'en'));\n }\n\n return {\n version,\n render(nodesToRender: HelpNode[], renderers: Renderers) {\n return renderers.render(\n nodesToRender.flatMap(node => {\n if (node.id !== 'commands') {\n return [node];\n }\n return [\n { title: 'Groups:', rows: groupRows },\n { title: 'Commands:', rows: commandRows },\n ]\n .filter(section => section.rows.length > 0)\n .map(\n ({ title, rows }): HelpNode => ({\n type: 'section',\n data: {\n title,\n indentBody: 0,\n body: {\n type: 'table',\n data: {\n tableData: rows,\n tableOptions: [\n {\n width: 'content-width',\n paddingLeft: 2,\n paddingRight: 8,\n },\n ],\n },\n },\n },\n }),\n );\n }),\n );\n },\n };\n}\n\nasync function executeCommand(\n commandToExecute: CliCommand,\n args: string[],\n programName: string,\n): Promise<void> {\n const context = {\n args,\n info: {\n usage: [programName, ...commandToExecute.path].join(' '),\n name: commandToExecute.path.join(' '),\n },\n };\n\n if (typeof commandToExecute.execute === 'function') {\n await commandToExecute.execute(context);\n } else {\n const mod = await commandToExecute.execute.loader();\n const fn =\n typeof mod.default === 'function'\n ? mod.default\n : (mod.default as any).default;\n await fn(context);\n }\n}\n\nasync function runCommandLevel(options: {\n nodes: ReadonlyArray<CommandNode>;\n argv: string[];\n programName: string;\n commandPath?: string[];\n version?: string;\n}): Promise<void> {\n const { nodes, argv, programName, commandPath = [], version } = options;\n const name = [programName, ...commandPath].join(' ');\n const commandArgs = argv.slice(1);\n const commands = nodes.map(node =>\n command(\n {\n name: getNodeName(node),\n help: false,\n },\n async () => {\n try {\n if (OpaqueCommandTreeNode.isType(node)) {\n await runCommandLevel({\n nodes: OpaqueCommandTreeNode.toInternal(node).children,\n argv: commandArgs,\n programName,\n commandPath: [...commandPath, getNodeName(node)],\n version,\n });\n } else {\n await executeCommand(\n OpaqueCommandLeafNode.toInternal(node).command,\n commandArgs,\n programName,\n );\n await flushStdio();\n process.exit(0);\n }\n } catch (error: unknown) {\n exitWithError(error);\n }\n },\n ),\n );\n const includeHelpCommand = !nodes.some(node => getNodeName(node) === 'help');\n if (includeHelpCommand && nodes.length > 0) {\n commands.push(\n command(\n {\n name: 'help',\n parameters: ['[command...]'],\n help: false,\n },\n async () => {\n await runCommandLevel({\n nodes,\n argv: [...commandArgs, '--help'],\n programName,\n commandPath,\n version,\n });\n },\n ),\n );\n }\n\n await cli(\n {\n name,\n flags: version\n ? {\n version: {\n type: Boolean,\n alias: 'V',\n description: 'Show version',\n },\n }\n : undefined,\n commands,\n help: createHelpOptions({\n nodes,\n includeHelpCommand: includeHelpCommand && nodes.length > 0,\n version,\n }),\n },\n parsed => {\n if (version && parsed.flags.version) {\n console.log(version);\n process.exit(0);\n return;\n }\n\n if (argv.length === 0) {\n parsed.showHelp();\n return;\n }\n\n console.log();\n console.log(\n chalk.red(`Invalid command: ${[...commandPath, ...argv].join(' ')}`),\n );\n console.log();\n parsed.showHelp();\n process.exit(1);\n },\n [...argv],\n );\n}\n\nfunction handleUnhandledRejection(rejection: unknown): void {\n exitWithError(new ForwardedError('Unhandled rejection', rejection));\n}\n\nfunction hasVersionFlag(args: string[]): boolean {\n const separatorIndex = args.indexOf('--');\n const options = separatorIndex === -1 ? args : args.slice(0, separatorIndex);\n return options.includes('-V') || options.includes('--version');\n}\n\n/**\n * Runs a collection of CLI modules as an executable program.\n *\n * This is intended for creating custom CLI packages from a fixed set of\n * directly imported modules. Module discovery and override behavior are left\n * to the caller.\n *\n * Invoking the program or a command group without a subcommand displays\n * help for that level and completes successfully. Command groups and commands\n * are listed separately in alphabetical order. Group previews fit the terminal width\n * (80 columns when unavailable), expanding nested groups breadth-first when space\n * permits. Unexpanded groups have a trailing slash; truncated previews end in an ellipsis.\n *\n * @example\n * ```ts\n * import { runCli } from '@backstage/cli-node';\n * import buildModule from '@backstage/cli-module-build';\n * import testModule from '@backstage/cli-module-test-jest';\n * import packageJson from '../package.json';\n *\n * runCli({\n * modules: [buildModule, testModule],\n * name: packageJson.name,\n * version: packageJson.version,\n * });\n * ```\n *\n * @public\n */\nexport async function runCli(options: {\n /** The CLI modules whose commands are included in the program. */\n modules: ReadonlyArray<CliModule>;\n /** The program name shown in help output and usage strings. */\n name: string;\n /** The version string shown when `--version` is passed. */\n version?: string;\n}): Promise<void> {\n const { modules, name, version } = options;\n const graph = new CommandGraph();\n\n for (const module of modules) {\n if (!OpaqueCliModule.isType(module)) {\n throw new Error(\n `Invalid CLI module: expected a module created with createCliModule`,\n );\n }\n\n for (const commandToAdd of await OpaqueCliModule.toInternal(module)\n .commands) {\n graph.add(commandToAdd, module);\n }\n }\n\n if (\n !process.listeners('unhandledRejection').includes(handleUnhandledRejection)\n ) {\n process.on('unhandledRejection', handleUnhandledRejection);\n }\n\n const args = process.argv.slice(2);\n if (version && hasVersionFlag(args)) {\n console.log(version);\n process.exit(0);\n return;\n }\n\n await runCommandLevel({\n nodes: graph.roots,\n argv: args,\n programName: name,\n version,\n });\n}\n"],"names":["chalk","isError","stringifyError","OpaqueCommandTreeNode","OpaqueCommandLeafNode","isCommandNodeHidden","command","cli","ForwardedError","CommandGraph","OpaqueCliModule"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -221,6 +221,12 @@ declare function createCliModule(options: {
221
221
  * directly imported modules. Module discovery and override behavior are left
222
222
  * to the caller.
223
223
  *
224
+ * Invoking the program or a command group without a subcommand displays
225
+ * help for that level and completes successfully. Command groups and commands
226
+ * are listed separately in alphabetical order. Group previews fit the terminal width
227
+ * (80 columns when unavailable), expanding nested groups breadth-first when space
228
+ * permits. Unexpanded groups have a trailing slash; truncated previews end in an ellipsis.
229
+ *
224
230
  * @example
225
231
  * ```ts
226
232
  * import { runCli } from '@backstage/cli-node';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/cli-node",
3
- "version": "0.3.5-next.0",
3
+ "version": "0.3.5",
4
4
  "description": "Node.js library for Backstage CLIs",
5
5
  "backstage": {
6
6
  "role": "node-library"
@@ -32,9 +32,9 @@
32
32
  "test": "backstage-cli package test"
33
33
  },
34
34
  "dependencies": {
35
- "@backstage/cli-common": "0.3.2-next.0",
36
- "@backstage/errors": "1.3.2-next.0",
37
- "@backstage/types": "1.2.2",
35
+ "@backstage/cli-common": "^0.3.2",
36
+ "@backstage/errors": "^1.3.2",
37
+ "@backstage/types": "^1.2.2",
38
38
  "@manypkg/get-packages": "^1.1.3",
39
39
  "@yarnpkg/lockfile": "^1.1.0",
40
40
  "@yarnpkg/parsers": "^3.0.0",
@@ -48,9 +48,9 @@
48
48
  "zod": "^3.25.76 || ^4.0.0"
49
49
  },
50
50
  "devDependencies": {
51
- "@backstage/backend-test-utils": "1.11.8-next.0",
52
- "@backstage/cli": "0.36.7-next.0",
53
- "@backstage/test-utils": "1.7.23-next.0",
51
+ "@backstage/backend-test-utils": "^1.11.8",
52
+ "@backstage/cli": "^0.36.7",
53
+ "@backstage/test-utils": "^1.7.23",
54
54
  "@types/proper-lockfile": "^4",
55
55
  "@types/yarnpkg__lockfile": "^1.1.4"
56
56
  },