@ucdjs/cli 0.3.0 → 0.3.1-beta.1

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/bin/ucd.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import("../dist/cli.js");
3
+ import("../dist/cli.mjs");
@@ -0,0 +1,113 @@
1
+ import { a as StoreConfigurationError, i as RemoteNotSupportedError, n as CLIError, o as StoreDirIsRequiredError, r as CLIStoreError } from "./cli.mjs";
2
+ import { createUCDClient } from "@ucdjs/client";
3
+ import { UCDJS_API_BASE_URL } from "@ucdjs/env";
4
+ import { isCancel, multiselect } from "@clack/prompts";
5
+ import { UCDStoreBaseError, createHTTPUCDStore, createNodeUCDStore, validateVersions } from "@ucdjs/ucd-store";
6
+ import { UNICODE_VERSION_METADATA } from "@unicode-utils/core";
7
+
8
+ //#region src/cmd/store/_shared.ts
9
+ /**
10
+ * Shared flags for all store commands (filtering, API config)
11
+ */
12
+ const SHARED_FLAGS = [
13
+ ["--include", "Patterns to include files in the store."],
14
+ ["--exclude", "Patterns to exclude files from the store."],
15
+ ["--base-url", "Base URL for the UCD API."],
16
+ ["--force", "Force operation (command-specific behavior)."]
17
+ ];
18
+ /**
19
+ * Flags specific to local store commands (init, sync, mirror)
20
+ */
21
+ const LOCAL_STORE_FLAGS = [["--store-dir", "Directory where the UCD files are stored. (required)"]];
22
+ /**
23
+ * Flags for commands that work with both local and remote stores
24
+ */
25
+ const REMOTE_CAPABLE_FLAGS = [["--store-dir", "Directory where the UCD files are stored."], ["--remote", "Use a Remote UCD Store (read-only)."]];
26
+ /**
27
+ * Asserts that storeDir is provided. Used for local-only commands (init, sync, mirror).
28
+ */
29
+ function assertLocalStore(flags) {
30
+ if (flags.remote) throw new RemoteNotSupportedError();
31
+ if (!flags.storeDir) throw new StoreDirIsRequiredError();
32
+ }
33
+ function assertRemoteOrStoreDir(flags) {
34
+ if (!flags.remote && !flags.storeDir) throw new StoreConfigurationError("Either --remote or --store-dir must be specified.");
35
+ }
36
+ /**
37
+ * Validates that the provided versions exist in the API.
38
+ * Throws a CLIError if any versions are invalid.
39
+ *
40
+ * @param versions - Versions to validate
41
+ * @param baseUrl - Base URL for the UCD API
42
+ * @throws {CLIError} If any versions are invalid
43
+ */
44
+ async function validateVersionsOrThrow(versions, baseUrl) {
45
+ if (!versions || versions.length === 0) return;
46
+ const client = await createUCDClient(baseUrl || UCDJS_API_BASE_URL);
47
+ try {
48
+ const result = await validateVersions({
49
+ client,
50
+ versions
51
+ });
52
+ if (!result.valid) throw new CLIError(`Invalid Unicode version(s): ${result.invalidVersions.join(", ")}`, {
53
+ title: "Version Validation Error",
54
+ details: [`Available versions: ${result.availableVersions.join(", ")}`]
55
+ });
56
+ } catch (err) {
57
+ if (err instanceof CLIError) throw err;
58
+ if (err instanceof Error) throw new CLIStoreError(err);
59
+ throw err;
60
+ }
61
+ }
62
+ /**
63
+ * Creates a UCD store instance based on the provided CLI flags.
64
+ * Validates versions before creating the store.
65
+ *
66
+ * @param {CLIStoreCmdSharedFlags} flags - Configuration flags for creating the store
67
+ * @returns {Promise<UCDStore>} A promise that resolves to a UCDStore instance
68
+ * @throws {CLIStoreError} When store creation fails
69
+ * @throws {StoreDirIsRequiredError} When store directory is not specified for local stores
70
+ * @throws {CLIError} When version validation fails
71
+ */
72
+ async function createStoreFromFlags(flags) {
73
+ const { storeDir, remote, baseUrl, include, exclude, force, requireExistingStore, versionStrategy } = flags;
74
+ if (flags.versions && flags.versions.length > 0) await validateVersionsOrThrow(flags.versions, baseUrl);
75
+ const options = {
76
+ baseUrl,
77
+ globalFilters: {
78
+ include,
79
+ exclude
80
+ },
81
+ versionStrategy: force ? "overwrite" : versionStrategy || "strict",
82
+ requireExistingStore
83
+ };
84
+ try {
85
+ if (remote) return await createHTTPUCDStore(options);
86
+ if (!storeDir) throw new StoreDirIsRequiredError();
87
+ return await createNodeUCDStore({
88
+ ...options,
89
+ basePath: storeDir,
90
+ versions: flags.versions || []
91
+ });
92
+ } catch (err) {
93
+ if (err instanceof UCDStoreBaseError) throw new CLIStoreError(err);
94
+ throw err;
95
+ }
96
+ }
97
+ async function runVersionPrompt({ input, output } = {}) {
98
+ const selectedVersions = await multiselect({
99
+ options: UNICODE_VERSION_METADATA.map(({ version }) => ({
100
+ value: version,
101
+ label: version
102
+ })),
103
+ message: "Select Unicode versions to initialize the store with:",
104
+ required: true,
105
+ input,
106
+ output
107
+ });
108
+ if (isCancel(selectedVersions)) return [];
109
+ return selectedVersions;
110
+ }
111
+
112
+ //#endregion
113
+ export { assertRemoteOrStoreDir as a, assertLocalStore as i, REMOTE_CAPABLE_FLAGS as n, createStoreFromFlags as o, SHARED_FLAGS as r, runVersionPrompt as s, LOCAL_STORE_FLAGS as t };
@@ -0,0 +1,75 @@
1
+ import { _ as output, p as green, t as printHelp, v as red } from "./cli.mjs";
2
+ import { a as assertRemoteOrStoreDir, n as REMOTE_CAPABLE_FLAGS, o as createStoreFromFlags, r as SHARED_FLAGS } from "./_shared-CgcKJJFf.mjs";
3
+
4
+ //#region src/cmd/store/analyze.ts
5
+ async function runAnalyzeStore({ flags, versions }) {
6
+ if (flags?.help || flags?.h) {
7
+ printHelp({
8
+ headline: "Analyze UCD Store",
9
+ commandName: "ucd store analyze",
10
+ usage: "[...versions] [...flags]",
11
+ tables: { Flags: [
12
+ ...REMOTE_CAPABLE_FLAGS,
13
+ ...SHARED_FLAGS,
14
+ ["--check-orphaned", "Check for orphaned files in the store."],
15
+ ["--json", "Output analyze information in JSON format."],
16
+ ["--help (-h)", "See all available flags."]
17
+ ] }
18
+ });
19
+ return;
20
+ }
21
+ if (!versions || versions.length === 0) output.log("No specific versions provided. Analyzing all versions in the store.");
22
+ assertRemoteOrStoreDir(flags);
23
+ const { storeDir, json, remote, baseUrl, include: patterns, exclude: excludePatterns } = flags;
24
+ const [analyzeData, analyzeError] = await (await createStoreFromFlags({
25
+ baseUrl,
26
+ storeDir,
27
+ remote,
28
+ include: patterns,
29
+ exclude: excludePatterns,
30
+ requireExistingStore: true
31
+ })).analyze({
32
+ versions: versions.length > 0 ? versions : void 0,
33
+ filters: {
34
+ include: patterns,
35
+ exclude: excludePatterns
36
+ }
37
+ });
38
+ if (analyzeError != null) {
39
+ output.error(red(`\n❌ Error analyzing store:`));
40
+ output.error(` ${analyzeError.message}`);
41
+ return;
42
+ }
43
+ if (!analyzeData) {
44
+ output.error(red(`\n❌ Error: Analyze operation returned no result.`));
45
+ return;
46
+ }
47
+ if (json) {
48
+ const analyzeDataObj = {
49
+ ...analyzeData,
50
+ versions: Object.fromEntries(Array.from(analyzeData.versions.entries()).map(([version, report]) => [version, {
51
+ ...report,
52
+ files: {
53
+ ...report.files,
54
+ missing: report.files.missing ?? [],
55
+ orphaned: report.files.orphaned ?? [],
56
+ present: report.files.present ?? []
57
+ }
58
+ }]))
59
+ };
60
+ output.json(analyzeDataObj);
61
+ return;
62
+ }
63
+ for (const [version, report] of analyzeData.versions.entries()) {
64
+ output.log(`Version: ${version}`);
65
+ if (report.isComplete) output.log(` Status: ${green("complete")}`);
66
+ else output.warning(` Status: ${red("incomplete")}`);
67
+ output.log(` Files: ${report.counts.success}`);
68
+ if (report.files.missing && report.files.missing.length > 0) output.warning(` Missing files: ${report.files.missing.length}`);
69
+ if (report.files.orphaned && report.files.orphaned.length > 0) output.warning(` Orphaned files: ${report.files.orphaned.length}`);
70
+ if (report.counts.total) output.log(` Total files expected: ${report.counts.total}`);
71
+ }
72
+ }
73
+
74
+ //#endregion
75
+ export { runAnalyzeStore };
package/dist/cli.mjs ADDED
@@ -0,0 +1,403 @@
1
+ import process$1 from "node:process";
2
+ import farver, { bgGreen, black, bold, dim, green } from "farver/fast";
3
+ import yargs from "yargs-parser";
4
+
5
+ //#region package.json
6
+ var version = "0.3.1-beta.1";
7
+
8
+ //#endregion
9
+ //#region src/output.ts
10
+ const green$1 = farver.green;
11
+ const red = farver.red;
12
+ const yellow = farver.yellow;
13
+ const cyan = farver.cyan;
14
+ const bold$1 = farver.bold;
15
+ const dim$1 = farver.dim;
16
+ const BUG_REPORT_URL = "https://github.com/ucdjs/ucd/issues";
17
+ const DEFAULT_INDENT = 2;
18
+ const DEFAULT_DIVIDER_LENGTH = 40;
19
+ const DEFAULT_LABEL_WIDTH = 16;
20
+ const DEFAULT_MAX_LIST_ITEMS = 10;
21
+ let jsonMode = false;
22
+ /**
23
+ * Centralized output utility for CLI commands.
24
+ *
25
+ * In JSON mode:
26
+ * - `log` is redirected to stderr so only JSON goes to stdout
27
+ * - `warn` and `error` always go to stderr
28
+ * - `json` always writes to stdout
29
+ * - `success`, `fail`, `warning`, `info` are silent (no output)
30
+ *
31
+ * This ensures that when `--json` is used, stdout contains only valid JSON
32
+ * that can be piped to other tools.
33
+ */
34
+ const output = {
35
+ log: (...args) => {
36
+ if (jsonMode) console.error(...args);
37
+ else console.log(...args);
38
+ },
39
+ warn: (...args) => {
40
+ console.warn(...args);
41
+ },
42
+ error: (...args) => {
43
+ console.error(...args);
44
+ },
45
+ json: (data) => {
46
+ console.log(`${JSON.stringify(data, null, 2)}\n`);
47
+ },
48
+ success: (message) => {
49
+ if (jsonMode) return;
50
+ console.log(green$1(`✓ ${message}`));
51
+ },
52
+ fail: (message, options) => {
53
+ if (jsonMode) return;
54
+ console.error(red(`\n❌ Error: ${message}`));
55
+ if (options?.details) for (const detail of options.details) console.error(` ${detail}`);
56
+ if (options?.bugReport) console.error(`\n If you believe this is a bug, please report it at ${BUG_REPORT_URL}`);
57
+ },
58
+ warning: (message) => {
59
+ if (jsonMode) return;
60
+ console.warn(yellow(`⚠ ${message}`));
61
+ },
62
+ info: (message) => {
63
+ if (jsonMode) return;
64
+ console.log(message);
65
+ },
66
+ errorJson: (error) => {
67
+ if (jsonMode) console.log(JSON.stringify({ error }, null, 2));
68
+ else {
69
+ console.error(red(`\n❌ Error: ${error.message}`));
70
+ if (error.details) console.error(` Details: ${typeof error.details === "string" ? error.details : JSON.stringify(error.details)}`);
71
+ }
72
+ },
73
+ failWithJson: (error) => {
74
+ if (jsonMode) console.log(JSON.stringify({ error }, null, 2));
75
+ else {
76
+ console.error(red(`\n❌ Error: ${error.message}`));
77
+ if (error.details) console.error(` Details: ${typeof error.details === "string" ? error.details : JSON.stringify(error.details)}`);
78
+ }
79
+ process.exit(1);
80
+ }
81
+ };
82
+ /**
83
+ * Enable or disable JSON mode.
84
+ * When enabled, `output.log` is redirected to stderr.
85
+ *
86
+ * @param enabled - Whether to enable JSON mode
87
+ */
88
+ function setJsonMode(enabled) {
89
+ jsonMode = enabled;
90
+ }
91
+ /**
92
+ * Create indentation string.
93
+ *
94
+ * @param spaces - Number of spaces (default: 2)
95
+ * @returns Indentation string
96
+ *
97
+ * @example indent() // " "
98
+ * @example indent(4) // " "
99
+ */
100
+ function indent(spaces = DEFAULT_INDENT) {
101
+ return " ".repeat(spaces);
102
+ }
103
+ /**
104
+ * Print a section header with title and divider.
105
+ * Silent in JSON mode.
106
+ *
107
+ * @param title - The header title
108
+ *
109
+ * @example header("UCD Store Information")
110
+ * // Output:
111
+ * // UCD Store Information
112
+ * // ────────────────────────────────────────
113
+ */
114
+ function header(title) {
115
+ if (jsonMode) return;
116
+ const ind = indent();
117
+ console.log(`\n${ind}${bold$1(title)}`);
118
+ console.log(`${ind}${dim$1("─".repeat(DEFAULT_DIVIDER_LENGTH))}\n`);
119
+ }
120
+ /**
121
+ * Print a key-value pair with aligned formatting.
122
+ * Silent in JSON mode.
123
+ *
124
+ * @param key - The label/key
125
+ * @param value - The value to display
126
+ * @param options - Formatting options
127
+ *
128
+ * @example keyValue("Store Path", "/path/to/store", { valueColor: green })
129
+ * // Output: Store Path: /path/to/store
130
+ */
131
+ function keyValue(key, value, options = {}) {
132
+ if (jsonMode) return;
133
+ const { indent: indentSpaces = DEFAULT_INDENT, labelWidth = DEFAULT_LABEL_WIDTH, valueColor } = options;
134
+ const label = `${key}:`.padEnd(labelWidth);
135
+ const displayValue = valueColor ? valueColor(value) : value;
136
+ console.log(`${indent(indentSpaces)}${bold$1(label)} ${displayValue}`);
137
+ }
138
+ /**
139
+ * Print a list of items with optional truncation.
140
+ * Silent in JSON mode.
141
+ *
142
+ * @param items - Array of items to display
143
+ * @param options - List formatting options
144
+ *
145
+ * @example list(["file1.txt", "file2.txt"], { prefix: "+", itemColor: green })
146
+ * // Output:
147
+ * // + file1.txt
148
+ * // + file2.txt
149
+ *
150
+ * @example list(manyFiles, { maxItems: 5 })
151
+ * // Output:
152
+ * // • file1.txt
153
+ * // • file2.txt
154
+ * // ... and 20 more
155
+ */
156
+ function list(items, options = {}) {
157
+ if (jsonMode) return;
158
+ const { prefix = "•", maxItems = DEFAULT_MAX_LIST_ITEMS, indent: indentSpaces = DEFAULT_INDENT, itemColor } = options;
159
+ const ind = indent(indentSpaces);
160
+ const displayItems = items.slice(0, maxItems);
161
+ for (const item of displayItems) {
162
+ const displayItem = itemColor ? itemColor(typeof item === "string" ? item : item.filePath) : typeof item === "string" ? item : item.filePath;
163
+ console.log(`${ind}${prefix} ${displayItem}`);
164
+ }
165
+ if (items.length > maxItems) console.log(`${ind}${dim$1(`... and ${items.length - maxItems} more`)}`);
166
+ }
167
+ /**
168
+ * Print a blank line.
169
+ * Silent in JSON mode.
170
+ */
171
+ function blankLine() {
172
+ if (jsonMode) return;
173
+ console.log("");
174
+ }
175
+ /**
176
+ * Format a duration in milliseconds to a human-readable string.
177
+ *
178
+ * @param ms - Duration in milliseconds
179
+ * @returns Formatted duration string (e.g., "2.35s", "150ms")
180
+ *
181
+ * @example formatDuration(2350) // "2.35s"
182
+ * @example formatDuration(150) // "150ms"
183
+ */
184
+ function formatDuration(ms) {
185
+ if (ms < 1e3) return `${ms}ms`;
186
+ return `${(ms / 1e3).toFixed(2)}s`;
187
+ }
188
+ /**
189
+ * Format bytes to human-readable size.
190
+ *
191
+ * @param bytes - Size in bytes
192
+ * @returns Formatted size string (e.g., "1.5 MB", "256 KB")
193
+ */
194
+ function formatBytes(bytes) {
195
+ if (bytes === 0) return "0 B";
196
+ const units = [
197
+ "B",
198
+ "KB",
199
+ "MB",
200
+ "GB",
201
+ "TB"
202
+ ];
203
+ const k = 1024;
204
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
205
+ return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${units[i]}`;
206
+ }
207
+
208
+ //#endregion
209
+ //#region src/errors.ts
210
+ var CLIError = class extends Error {
211
+ title;
212
+ details;
213
+ constructor(message, options) {
214
+ super(message);
215
+ this.name = "CLIError";
216
+ this.title = options?.title ?? "Error";
217
+ this.details = options?.details ?? [];
218
+ }
219
+ toPrettyMessage() {
220
+ output.fail(this.message, { details: this.details });
221
+ }
222
+ };
223
+ var StoreDirIsRequiredError = class extends CLIError {
224
+ constructor() {
225
+ super("--store-dir is required. Please specify the directory where the UCD files should be stored.", { title: "Store Error" });
226
+ this.name = "StoreDirIsRequiredError";
227
+ }
228
+ };
229
+ var RemoteNotSupportedError = class extends CLIError {
230
+ constructor() {
231
+ super("The --remote flag is not supported for this command.", { title: "Store Error" });
232
+ this.name = "RemoteNotSupportedError";
233
+ }
234
+ };
235
+ var StoreConfigurationError = class extends CLIError {
236
+ constructor(message) {
237
+ super(message, { title: "Store Error" });
238
+ this.name = "StoreConfigurationError";
239
+ }
240
+ };
241
+ var CLIStoreError = class extends CLIError {
242
+ constructor(error) {
243
+ super(error.message, { title: "Store Error" });
244
+ this.name = "CLIStoreError";
245
+ }
246
+ };
247
+
248
+ //#endregion
249
+ //#region src/cli-utils.ts
250
+ const SUPPORTED_COMMANDS = new Set([
251
+ "codegen",
252
+ "store",
253
+ "files",
254
+ "lockfile"
255
+ ]);
256
+ /**
257
+ * Resolves the CLI command based on the provided arguments.
258
+ *
259
+ * If the `version` flag is present, it returns the "version" command.
260
+ * Otherwise, it checks if the first positional argument (`flags._[0]`)
261
+ * is a supported command. If it is, it returns that command.
262
+ * If no supported command is found, it defaults to the "help" command.
263
+ *
264
+ * @param {Arguments} flags - The parsed arguments from the command line.
265
+ * @returns {CLICommand} The resolved CLI command.
266
+ */
267
+ function resolveCommand(flags) {
268
+ if (flags.version) return "version";
269
+ const cmd = flags._[0];
270
+ if (SUPPORTED_COMMANDS.has(cmd)) return cmd;
271
+ return "help";
272
+ }
273
+ function printHelp({ commandName, headline, usage, tables, description }) {
274
+ const isTinyTerminal = (process$1.stdout.columns || 80) < 60;
275
+ const indent = " ";
276
+ const linebreak = () => "";
277
+ const table = (rows, { padding }) => {
278
+ let raw = "";
279
+ for (const [command, help] of rows) if (isTinyTerminal) raw += `${indent}${indent}${bold(command)}\n${indent}${indent}${indent}${dim(help)}\n`;
280
+ else {
281
+ const paddedCommand = command.padEnd(padding);
282
+ raw += `${indent}${indent}${bold(paddedCommand)} ${dim(help)}\n`;
283
+ }
284
+ return raw.slice(0, -1);
285
+ };
286
+ const message = [];
287
+ if (headline) message.push(`\n${indent}${bgGreen(black(` ${commandName} `))} ${green(`v${version ?? "0.0.0"}`)}`, `${indent}${dim(headline)}`);
288
+ if (usage) message.push(linebreak(), `${indent}${bold("USAGE")}`, `${indent}${indent}${green(commandName)} ${usage}`);
289
+ if (description) message.push(linebreak(), `${indent}${bold("DESCRIPTION")}`, `${indent}${indent}${description}`);
290
+ if (tables) {
291
+ function calculateTablePadding(rows) {
292
+ const maxLength = rows.reduce((val, [first]) => Math.max(val, first.length), 0);
293
+ return Math.min(maxLength, 30) + 2;
294
+ }
295
+ const tableEntries = Object.entries(tables);
296
+ for (const [tableTitle, tableRows] of tableEntries) {
297
+ const padding = calculateTablePadding(tableRows);
298
+ message.push(linebreak(), `${indent}${bold(tableTitle.toUpperCase())}`, table(tableRows, { padding }));
299
+ }
300
+ }
301
+ message.push(linebreak(), `${indent}${dim(`Run with --help for more information on specific commands.`)}`);
302
+ console.log(`${message.join("\n")}\n`);
303
+ }
304
+ /**
305
+ * Runs a command based on the provided CLI command and flags.
306
+ *
307
+ * @param {CLICommand} cmd - The CLI command to execute.
308
+ * @param {Arguments} flags - The flags passed to the command.
309
+ * @returns {Promise<void>} A promise that resolves when the command has finished executing.
310
+ * @throws An error if the command is not found.
311
+ */
312
+ async function runCommand(cmd, flags) {
313
+ switch (cmd) {
314
+ case "help":
315
+ printHelp({
316
+ commandName: "ucd",
317
+ headline: "A CLI for working with the Unicode Character Database (UCD).",
318
+ usage: "[command] [...flags]",
319
+ tables: {
320
+ "Commands": [
321
+ ["store", "Manage UCD stores (init, mirror, sync, etc.)."],
322
+ ["codegen", "Generate TypeScript code from UCD data."],
323
+ ["files", "List and get files from the UCD API."],
324
+ ["lockfile", "Inspect and validate UCD store lockfiles."]
325
+ ],
326
+ "Global Flags": [["--version", "Show the version number and exit."], ["--help", "Show this help message."]]
327
+ }
328
+ });
329
+ break;
330
+ case "version":
331
+ console.log(` ${bgGreen(black(` ucd `))} ${green(`v${version ?? "x.y.z"}`)}`);
332
+ break;
333
+ case "codegen": {
334
+ const { runCodegenRoot } = await import("./root-CfALAyOQ.mjs");
335
+ await runCodegenRoot(flags._[1]?.toString() ?? "", { flags });
336
+ break;
337
+ }
338
+ case "store": {
339
+ const { runStoreRoot } = await import("./root-C_Ycy7kI.mjs");
340
+ await runStoreRoot(flags._[1]?.toString() ?? "", { flags });
341
+ break;
342
+ }
343
+ case "files": {
344
+ const { runFilesRoot } = await import("./root-DXVHyPa6.mjs");
345
+ await runFilesRoot(flags._[1]?.toString() ?? "", { flags });
346
+ break;
347
+ }
348
+ case "lockfile": {
349
+ const { runLockfileRoot } = await import("./root-BnOeYqGP.mjs");
350
+ await runLockfileRoot(flags._[1]?.toString() ?? "", { flags });
351
+ break;
352
+ }
353
+ default: throw new Error(`Error running ${cmd} -- no command found.`);
354
+ }
355
+ }
356
+ function parseFlags(args) {
357
+ return yargs(args, {
358
+ configuration: { "parse-positional-numbers": false },
359
+ default: { force: false },
360
+ boolean: [
361
+ "force",
362
+ "help",
363
+ "h",
364
+ "dry-run",
365
+ "json"
366
+ ],
367
+ string: [
368
+ "output-dir",
369
+ "base-url",
370
+ "output",
371
+ "store-dir"
372
+ ],
373
+ array: ["include", "exclude"]
374
+ });
375
+ }
376
+ async function runCLI(args) {
377
+ const flags = parseFlags(args);
378
+ process$1.title = "ucd-cli";
379
+ setJsonMode(!!flags.json);
380
+ try {
381
+ await runCommand(resolveCommand(flags), flags);
382
+ } catch (err) {
383
+ if (err instanceof CLIError) {
384
+ err.toPrettyMessage();
385
+ if (process$1.env.NODE_ENV !== "test" && !process$1.env.VITEST) process$1.exit(1);
386
+ return;
387
+ }
388
+ let message = "Unknown error";
389
+ if (err instanceof Error) message = err.message;
390
+ else if (typeof err === "string") message = err;
391
+ output.fail(message, { bugReport: true });
392
+ if (process$1.env.NODE_ENV !== "test" && !process$1.env.VITEST) process$1.exit(1);
393
+ } finally {
394
+ setJsonMode(false);
395
+ }
396
+ }
397
+
398
+ //#endregion
399
+ //#region src/cli.ts
400
+ runCLI(process$1.argv.slice(2));
401
+
402
+ //#endregion
403
+ export { output as _, StoreConfigurationError as a, bold$1 as c, formatBytes as d, formatDuration as f, list as g, keyValue as h, RemoteNotSupportedError as i, cyan as l, header as m, CLIError as n, StoreDirIsRequiredError as o, green$1 as p, CLIStoreError as r, blankLine as s, printHelp as t, dim$1 as u, red as v, yellow as y };
@@ -1,4 +1,4 @@
1
- import { t as printHelp } from "./cli-utils-dAbyq59_.js";
1
+ import { _ as output, n as CLIError, t as printHelp } from "./cli.mjs";
2
2
  import process from "node:process";
3
3
  import { existsSync } from "node:fs";
4
4
  import { mkdir, readdir, stat, writeFile } from "node:fs/promises";
@@ -57,7 +57,7 @@ async function writeBundledFile(version, results, bundleTemplate, outputDir) {
57
57
  if (!bundlePath.endsWith(".ts")) bundlePath += ".ts";
58
58
  await mkdir(path.dirname(bundlePath), { recursive: true });
59
59
  await writeFile(bundlePath, bundledCode, "utf-8");
60
- console.log(`Generated bundled fields for Unicode ${version} in ${bundlePath}`);
60
+ output.success(`Generated bundled fields for Unicode ${version} in ${bundlePath}`);
61
61
  }
62
62
  async function runFieldCodegen({ inputPath, flags }) {
63
63
  if (flags?.help || flags?.h) {
@@ -75,19 +75,10 @@ async function runFieldCodegen({ inputPath, flags }) {
75
75
  return;
76
76
  }
77
77
  const openaiKey = flags.openaiKey || process.env.OPENAI_API_KEY;
78
- if (!openaiKey) {
79
- console.error("No OpenAI API key provided. Please provide an OpenAI API key.");
80
- return;
81
- }
82
- if (inputPath == null) {
83
- console.error("No input path provided. Please provide an input path.");
84
- return;
85
- }
78
+ if (!openaiKey) throw new CLIError("No OpenAI API key provided.", { details: ["Please provide an OpenAI API key via --openai-key flag or OPENAI_API_KEY environment variable."] });
79
+ if (inputPath == null) throw new CLIError("No input path provided.", { details: ["Please provide an input path as the first argument."] });
86
80
  const resolvedInputPath = path.resolve(inputPath);
87
- if (!existsSync(resolvedInputPath)) {
88
- console.error(`invalid input path: ${inputPath}. Please provide a valid input path.`);
89
- return;
90
- }
81
+ if (!existsSync(resolvedInputPath)) throw new CLIError(`Invalid input path: ${inputPath}`, { details: ["The specified input path does not exist. Please provide a valid input path."] });
91
82
  const shouldBundle = typeof flags.bundle === "string" || flags.bundle === true;
92
83
  const bundleTemplate = typeof flags.bundle === "string" ? flags.bundle : "index.ts";
93
84
  let outputDir = flags.outputDir;
@@ -95,7 +86,7 @@ async function runFieldCodegen({ inputPath, flags }) {
95
86
  else outputDir = path.join(path.dirname(resolvedInputPath), ".codegen");
96
87
  if (outputDir) await mkdir(outputDir, { recursive: true });
97
88
  const filesWithVersion = await scanFiles(inputPath);
98
- console.log(`Found ${filesWithVersion.length} files to process.`);
89
+ output.info(`Found ${filesWithVersion.length} files to process.`);
99
90
  const results = await runSchemagen({
100
91
  files: filesWithVersion,
101
92
  openaiKey
@@ -104,7 +95,7 @@ async function runFieldCodegen({ inputPath, flags }) {
104
95
  if (!shouldBundle) {
105
96
  writePromises.push(...results.map((result) => writeFile(path.join(outputDir, `${result.fileName}.ts`), result.code, "utf-8")));
106
97
  await Promise.all(writePromises);
107
- console.log(`Generated fields for ${results.length} files in ${outputDir}`);
98
+ output.success(`Generated fields for ${results.length} files in ${outputDir}`);
108
99
  return;
109
100
  }
110
101
  const resultsByVersion = /* @__PURE__ */ new Map();