@ucdjs/cli 0.2.2 → 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.
@@ -0,0 +1,63 @@
1
+ import { _ as output, p as green, t as printHelp, v as red } from "./cli.mjs";
2
+ import { writeFile } from "node:fs/promises";
3
+ import { createUCDClient } from "@ucdjs/client";
4
+ import { UCDJS_API_BASE_URL } from "@ucdjs/env";
5
+
6
+ //#region src/cmd/files/get.ts
7
+ async function runFilesGet({ path, flags }) {
8
+ if (flags?.help || flags?.h) {
9
+ printHelp({
10
+ headline: "Get a specific file from the UCD API",
11
+ commandName: "ucd files get",
12
+ usage: "<path> [...flags]",
13
+ description: "Download a specific file from the Unicode Character Database API. The file content will be written to stdout by default, or to a file if --output is specified.",
14
+ tables: { Flags: [
15
+ ["--base-url", "Base URL for the UCD API (defaults to api.ucdjs.dev)."],
16
+ ["--output (-o)", "Write file content to the specified file path instead of stdout."],
17
+ ["--help (-h)", "See all available flags."]
18
+ ] }
19
+ });
20
+ return;
21
+ }
22
+ if (!path) {
23
+ output.error(red(`\n❌ Error: Path is required.`));
24
+ output.error(` Usage: ucd files get <path>`);
25
+ output.error(` Example: ucd files get 16.0.0/ucd/UnicodeData.txt`);
26
+ return;
27
+ }
28
+ const { baseUrl, output: outputFlag } = flags;
29
+ try {
30
+ const result = await (await createUCDClient(baseUrl || UCDJS_API_BASE_URL)).files.get(path);
31
+ if (result.error) {
32
+ output.error(red(`\n❌ Error fetching file:`));
33
+ output.error(` ${result.error.message}`);
34
+ if (result.error.status === 404) output.error(` File "${path}" not found.`);
35
+ return;
36
+ }
37
+ if (!result.data) {
38
+ output.error(red(`\n❌ Error: No data returned from API.`));
39
+ return;
40
+ }
41
+ if (Array.isArray(result.data)) {
42
+ output.error(red(`\n❌ Error: Path "${path}" is a directory, not a file.`));
43
+ output.error(` Use "ucd files list" to view directory contents.`);
44
+ return;
45
+ }
46
+ const content = result.data;
47
+ if (outputFlag) {
48
+ await writeFile(outputFlag, content, "utf-8");
49
+ output.log(green(`\n✓ File written to: ${outputFlag}\n`));
50
+ } else output.log(content);
51
+ } catch (err) {
52
+ let message = "Unknown error";
53
+ if (err instanceof Error) message = err.message;
54
+ else if (typeof err === "string") message = err;
55
+ output.error(red(`\n❌ Error getting file:`));
56
+ output.error(` ${message}`);
57
+ output.error("Please check the API configuration and try again.");
58
+ output.error("If you believe this is a bug, please report it at https://github.com/ucdjs/ucd/issues");
59
+ }
60
+ }
61
+
62
+ //#endregion
63
+ export { runFilesGet };
@@ -0,0 +1,116 @@
1
+ import { _ as output, c as bold, d as formatBytes, p as green, t as printHelp, u as dim, v as red, y as yellow } from "./cli.mjs";
2
+ import process from "node:process";
3
+ import { readFile } from "node:fs/promises";
4
+ import { resolve } from "node:path";
5
+ import { Buffer } from "node:buffer";
6
+ import { computeFileHash, computeFileHashWithoutUCDHeader, stripUnicodeHeader } from "@ucdjs/lockfile";
7
+
8
+ //#region src/cmd/lockfile/hash.ts
9
+ async function runLockfileHash({ filePath, flags }) {
10
+ if (flags?.help || flags?.h) {
11
+ printHelp({
12
+ headline: "Compute content hash for a file",
13
+ commandName: "ucd lockfile hash",
14
+ usage: "<file-path> [...flags]",
15
+ description: "Compute the SHA-256 hash of a file, optionally stripping the Unicode header first. Useful for debugging and verifying file integrity.",
16
+ tables: { Flags: [
17
+ ["--strip-header", "Strip the Unicode header before computing hash (default: false)."],
18
+ ["--compare <hash>", "Compare computed hash with the provided hash."],
19
+ ["--json", "Output hash information as JSON."],
20
+ ["--help (-h)", "See all available flags."]
21
+ ] }
22
+ });
23
+ return;
24
+ }
25
+ if (!filePath) {
26
+ output.error(red(`\n❌ Error: File path is required.`));
27
+ output.error(`\n Usage: ${green("ucd lockfile hash <file-path>")}`);
28
+ return;
29
+ }
30
+ const { json, stripHeader, compare } = flags;
31
+ const resolvedPath = resolve(process.cwd(), filePath);
32
+ try {
33
+ let content;
34
+ try {
35
+ content = await readFile(resolvedPath, "utf-8");
36
+ } catch {
37
+ if (json) output.json({
38
+ success: false,
39
+ filePath: resolvedPath,
40
+ error: "FILE_NOT_FOUND",
41
+ message: `File not found at "${resolvedPath}"`
42
+ });
43
+ else output.error(red(`\n❌ Error: File not found at "${resolvedPath}".`));
44
+ return;
45
+ }
46
+ const fileSize = Buffer.byteLength(content, "utf-8");
47
+ const fileHash = await computeFileHash(content);
48
+ const contentHash = await computeFileHashWithoutUCDHeader(content);
49
+ const primaryHash = stripHeader ? contentHash : fileHash;
50
+ const strippedContent = stripUnicodeHeader(content);
51
+ const hasUnicodeHeader = strippedContent !== content;
52
+ const headerLinesRemoved = hasUnicodeHeader ? content.split("\n").length - strippedContent.split("\n").length : 0;
53
+ let compareResult;
54
+ if (compare) {
55
+ const normalizedCompare = compare.startsWith("sha256:") ? compare : `sha256:${compare}`;
56
+ compareResult = {
57
+ matches: primaryHash === normalizedCompare,
58
+ providedHash: normalizedCompare
59
+ };
60
+ }
61
+ if (json) {
62
+ output.json({
63
+ success: true,
64
+ filePath: resolvedPath,
65
+ fileSize,
66
+ formattedSize: formatBytes(fileSize),
67
+ fileHash,
68
+ contentHash,
69
+ hasUnicodeHeader,
70
+ headerLinesRemoved,
71
+ usedHash: stripHeader ? "contentHash" : "fileHash",
72
+ primaryHash,
73
+ comparison: compareResult ? {
74
+ providedHash: compareResult.providedHash,
75
+ matches: compareResult.matches
76
+ } : void 0
77
+ });
78
+ return;
79
+ }
80
+ output.log(`\n ${bold("File Hash Information")}`);
81
+ output.log(` ${dim("─".repeat(50))}\n`);
82
+ output.log(` ${bold("File:")} ${green(resolvedPath)}`);
83
+ output.log(` ${bold("Size:")} ${formatBytes(fileSize)}`);
84
+ output.log(` ${bold("Unicode Header:")} ${hasUnicodeHeader ? yellow("Present") : dim("Not detected")}`);
85
+ if (hasUnicodeHeader) output.log(` ${bold("Header Lines:")} ${headerLinesRemoved}`);
86
+ output.log(`\n ${bold("Hashes:")}`);
87
+ output.log(` ${dim("─".repeat(50))}`);
88
+ output.log(` ${bold("File Hash:")} ${dim(fileHash)}`);
89
+ output.log(` ${dim("(hash of complete file content)")}`);
90
+ output.log(` ${bold("Content Hash:")} ${dim(contentHash)}`);
91
+ output.log(` ${dim("(hash with Unicode header stripped)")}`);
92
+ if (compareResult) {
93
+ output.log(`\n ${bold("Comparison:")}`);
94
+ output.log(` ${dim("─".repeat(50))}`);
95
+ output.log(` ${bold("Provided Hash:")} ${dim(compareResult.providedHash)}`);
96
+ output.log(` ${bold("Computed Hash:")} ${dim(primaryHash)}`);
97
+ if (compareResult.matches) output.log(` ${bold("Result:")} ${green("✅ Hashes match")}`);
98
+ else output.log(` ${bold("Result:")} ${red("❌ Hashes do not match")}`);
99
+ }
100
+ output.log("");
101
+ } catch (err) {
102
+ if (json) output.json({
103
+ success: false,
104
+ filePath: resolvedPath,
105
+ error: "HASH_COMPUTATION_FAILED",
106
+ message: err instanceof Error ? err.message : String(err)
107
+ });
108
+ else {
109
+ output.error(red(`\n❌ Error computing hash:`));
110
+ if (err instanceof Error) output.error(` ${err.message}`);
111
+ }
112
+ }
113
+ }
114
+
115
+ //#endregion
116
+ export { runLockfileHash };
@@ -0,0 +1,100 @@
1
+ import { _ as output, c as bold, d as formatBytes, p as green, t as printHelp, u as dim, v as red, y as yellow } from "./cli.mjs";
2
+ import process from "node:process";
3
+ import { resolve } from "node:path";
4
+ import { getLockfilePath, readLockfile } from "@ucdjs/lockfile";
5
+ import NodeFileSystemBridge from "@ucdjs/fs-bridge/bridges/node";
6
+
7
+ //#region src/cmd/lockfile/info.ts
8
+ async function runLockfileInfo({ flags }) {
9
+ if (flags?.help || flags?.h) {
10
+ printHelp({
11
+ headline: "Display lockfile information and summary",
12
+ commandName: "ucd lockfile info",
13
+ usage: "[...flags]",
14
+ description: "Read and display information from the UCD store lockfile including versions, file counts, and sizes.",
15
+ tables: { Flags: [
16
+ ["--store-dir", "Directory where the UCD store is located (defaults to current directory)."],
17
+ ["--json", "Output lockfile information as JSON."],
18
+ ["--help (-h)", "See all available flags."]
19
+ ] }
20
+ });
21
+ return;
22
+ }
23
+ const { storeDir, json } = flags;
24
+ const storePath = storeDir ? resolve(storeDir) : process.cwd();
25
+ try {
26
+ const fs = NodeFileSystemBridge({ basePath: storePath });
27
+ const lockfilePath = getLockfilePath();
28
+ let lockfile;
29
+ try {
30
+ lockfile = await readLockfile(fs, lockfilePath);
31
+ } catch (err) {
32
+ output.error(red(`\n❌ Error: Could not read lockfile at "${lockfilePath}".`));
33
+ if (err instanceof Error) output.error(` ${err.message}`);
34
+ output.error(`\n Run ${green("ucd store init")} to create a new store.`);
35
+ return;
36
+ }
37
+ const versions = Object.keys(lockfile.versions);
38
+ const totalFiles = Object.values(lockfile.versions).reduce((sum, v) => sum + v.fileCount, 0);
39
+ const totalSize = Object.values(lockfile.versions).reduce((sum, v) => sum + v.totalSize, 0);
40
+ if (json) {
41
+ output.json({
42
+ storePath,
43
+ lockfilePath,
44
+ lockfileVersion: lockfile.lockfileVersion,
45
+ totalVersions: versions.length,
46
+ totalFiles,
47
+ totalSize,
48
+ formattedTotalSize: formatBytes(totalSize),
49
+ filters: lockfile.filters,
50
+ versions: Object.entries(lockfile.versions).map(([version, entry]) => ({
51
+ version,
52
+ ...entry,
53
+ formattedSize: formatBytes(entry.totalSize)
54
+ }))
55
+ });
56
+ return;
57
+ }
58
+ output.log(`\n ${bold("UCD Store Lockfile Information")}`);
59
+ output.log(` ${dim("─".repeat(40))}\n`);
60
+ output.log(` ${bold("Store Path:")} ${green(storePath)}`);
61
+ output.log(` ${bold("Lockfile Path:")} ${dim(lockfilePath)}`);
62
+ output.log(` ${bold("Lockfile Version:")} ${lockfile.lockfileVersion}`);
63
+ output.log(` ${bold("Total Versions:")} ${versions.length}`);
64
+ output.log(` ${bold("Total Files:")} ${totalFiles.toLocaleString()}`);
65
+ output.log(` ${bold("Total Size:")} ${formatBytes(totalSize)}`);
66
+ const includeFilters = lockfile.filters?.include ?? [];
67
+ const excludeFilters = lockfile.filters?.exclude ?? [];
68
+ const disableDefaultExclusions = lockfile.filters?.disableDefaultExclusions ?? false;
69
+ if (includeFilters.length > 0 || excludeFilters.length > 0 || disableDefaultExclusions) {
70
+ output.log(`\n ${bold("Filters:")}`);
71
+ if (includeFilters.length > 0) output.log(` ${bold("Include:")} ${includeFilters.join(", ")}`);
72
+ if (excludeFilters.length > 0) output.log(` ${bold("Exclude:")} ${excludeFilters.join(", ")}`);
73
+ if (disableDefaultExclusions) output.log(` ${yellow("Default exclusions disabled")}`);
74
+ }
75
+ output.log(`\n ${bold("Versions:")}`);
76
+ output.log(` ${dim("─".repeat(40))}`);
77
+ const sortedVersions = versions.sort((a, b) => {
78
+ const [aMajor = 0, aMinor = 0, aPatch = 0] = a.split(".").map(Number);
79
+ const [bMajor = 0, bMinor = 0, bPatch = 0] = b.split(".").map(Number);
80
+ if (bMajor !== aMajor) return bMajor - aMajor;
81
+ if (bMinor !== aMinor) return bMinor - aMinor;
82
+ return bPatch - aPatch;
83
+ });
84
+ for (const version of sortedVersions) {
85
+ const entry = lockfile.versions[version];
86
+ if (!entry) continue;
87
+ output.log(`\n ${green(version)}`);
88
+ output.log(` ${bold("Files:")} ${entry.fileCount.toLocaleString()}`);
89
+ output.log(` ${bold("Size:")} ${formatBytes(entry.totalSize)}`);
90
+ output.log(` ${bold("Snapshot:")} ${dim(entry.path)}`);
91
+ }
92
+ output.log("");
93
+ } catch (err) {
94
+ output.error(red(`\n❌ Error reading lockfile:`));
95
+ if (err instanceof Error) output.error(` ${err.message}`);
96
+ }
97
+ }
98
+
99
+ //#endregion
100
+ export { runLockfileInfo };
@@ -0,0 +1,75 @@
1
+ import { _ as output, c as bold, d as formatBytes, p as green, t as printHelp, u as dim, v as red } from "./cli.mjs";
2
+ import { UCDJS_API_BASE_URL, UCD_STAT_TYPE_HEADER } from "@ucdjs/env";
3
+ import { customFetch } from "@ucdjs-internal/shared";
4
+
5
+ //#region src/cmd/files/info.ts
6
+ function formatMetadata(metadata) {
7
+ const lines = [];
8
+ lines.push(` ${bold("Path:")} ${green(metadata.path || "(root)")}`);
9
+ lines.push(` ${bold("Type:")} ${metadata.type === "directory" ? green("directory") : dim("file")}`);
10
+ if (metadata.contentType) lines.push(` ${bold("Content-Type:")} ${dim(metadata.contentType)}`);
11
+ if (metadata.lastModified) lines.push(` ${bold("Last Modified:")} ${dim(metadata.lastModified)}`);
12
+ if (metadata.contentLength) {
13
+ const formatted = formatBytes(Number.parseInt(metadata.contentLength, 10));
14
+ lines.push(` ${bold("Size:")} ${dim(formatted)}`);
15
+ }
16
+ return lines.join("\n");
17
+ }
18
+ async function runFilesInfo({ path, flags }) {
19
+ if (flags?.help || flags?.h) {
20
+ printHelp({
21
+ headline: "Get metadata about a file or directory from the UCD API",
22
+ commandName: "ucd files info",
23
+ usage: "<path> [...flags]",
24
+ description: "Retrieve metadata (type, size, last modified date) about a file or directory without downloading its content.",
25
+ tables: { Flags: [
26
+ ["--base-url", "Base URL for the UCD API (defaults to api.ucdjs.dev)."],
27
+ ["--json", "Output metadata as raw JSON."],
28
+ ["--help (-h)", "See all available flags."]
29
+ ] }
30
+ });
31
+ return;
32
+ }
33
+ const { baseUrl, json } = flags;
34
+ const apiBaseUrl = baseUrl || UCDJS_API_BASE_URL;
35
+ try {
36
+ const url = new URL(`/api/v1/files/${path || ""}`, apiBaseUrl);
37
+ const result = await customFetch.safe(url.toString(), { method: "HEAD" });
38
+ if (result.error) {
39
+ output.error(red(`\n❌ Error fetching file info:`));
40
+ output.error(` ${result.error.message}`);
41
+ if (result.error.status === 404) output.error(` Path "${path || "(root)"}" not found.`);
42
+ return;
43
+ }
44
+ if (!result.response) {
45
+ output.error(red(`\n❌ Error: No response from API.`));
46
+ return;
47
+ }
48
+ const headers = result.response.headers;
49
+ const metadata = {
50
+ path: path || "",
51
+ type: headers.get(UCD_STAT_TYPE_HEADER) || "file",
52
+ contentType: headers.get("Content-Type") || void 0,
53
+ lastModified: headers.get("Last-Modified") || void 0,
54
+ contentLength: headers.get("Content-Length") || void 0
55
+ };
56
+ if (json) {
57
+ output.json(metadata);
58
+ return;
59
+ }
60
+ output.log(`\nFile info: ${green(path || "(root)")}\n`);
61
+ output.log(formatMetadata(metadata));
62
+ output.log("");
63
+ } catch (err) {
64
+ let message = "Unknown error";
65
+ if (err instanceof Error) message = err.message;
66
+ else if (typeof err === "string") message = err;
67
+ output.error(red(`\n❌ Error getting file info:`));
68
+ output.error(` ${message}`);
69
+ output.error("Please check the API configuration and try again.");
70
+ output.error("If you believe this is a bug, please report it at https://github.com/ucdjs/ucd/issues");
71
+ }
72
+ }
73
+
74
+ //#endregion
75
+ export { runFilesInfo };
@@ -0,0 +1,126 @@
1
+ import { _ as output, f as formatDuration, h as keyValue, l as cyan, m as header, p as green, s as blankLine, t as printHelp, v as red, y as yellow } from "./cli.mjs";
2
+ import { i as assertLocalStore, o as createStoreFromFlags, r as SHARED_FLAGS, s as runVersionPrompt, t as LOCAL_STORE_FLAGS } from "./_shared-CgcKJJFf.mjs";
3
+ import { tryOr } from "@ucdjs-internal/shared";
4
+
5
+ //#region src/cmd/store/init.ts
6
+ async function runInitStore({ flags, versions }) {
7
+ if (flags?.help || flags?.h) {
8
+ printHelp({
9
+ headline: "Initialize an UCD Store",
10
+ commandName: "ucd store init",
11
+ usage: "[...versions] [...flags]",
12
+ tables: { Flags: [
13
+ ...LOCAL_STORE_FLAGS,
14
+ ...SHARED_FLAGS,
15
+ ["--json", "Output initialization results in JSON format."],
16
+ ["--help (-h)", "See all available flags."]
17
+ ] }
18
+ });
19
+ return;
20
+ }
21
+ assertLocalStore(flags);
22
+ const { storeDir, baseUrl, include: patterns, exclude: excludePatterns, force, json } = flags;
23
+ let selectedVersions = versions;
24
+ if (!selectedVersions || selectedVersions.length === 0) {
25
+ const pickedVersions = await tryOr({
26
+ try: runVersionPrompt(),
27
+ err: () => {
28
+ if (json) output.errorJson({
29
+ type: "PROMPT_CANCELLED",
30
+ message: "Version selection cancelled"
31
+ });
32
+ else output.warning("Version selection cancelled.");
33
+ return [];
34
+ }
35
+ });
36
+ if (pickedVersions.length === 0) {
37
+ if (json) output.errorJson({
38
+ type: "NO_VERSIONS_SELECTED",
39
+ message: "No versions selected"
40
+ });
41
+ else output.warning("No versions selected. Operation cancelled.");
42
+ return;
43
+ }
44
+ selectedVersions = pickedVersions;
45
+ }
46
+ const store = await createStoreFromFlags({
47
+ baseUrl,
48
+ storeDir,
49
+ remote: false,
50
+ include: patterns,
51
+ exclude: excludePatterns,
52
+ versions: selectedVersions,
53
+ force,
54
+ requireExistingStore: false
55
+ });
56
+ if (!json) {
57
+ header("UCD Store Initialization");
58
+ keyValue("Store Path", storeDir, { valueColor: cyan });
59
+ keyValue("Versions", selectedVersions.map((v) => cyan(v)).join(", "));
60
+ blankLine();
61
+ output.success("Lockfile created");
62
+ }
63
+ const [mirrorResult, mirrorError] = await store.mirror({
64
+ versions: selectedVersions,
65
+ force,
66
+ filters: {
67
+ include: patterns,
68
+ exclude: excludePatterns
69
+ }
70
+ });
71
+ if (mirrorError) {
72
+ if (json) output.errorJson({
73
+ type: "MIRROR_FAILED",
74
+ message: "Mirror failed",
75
+ details: {
76
+ reason: mirrorError.message,
77
+ lockfileCreated: true
78
+ }
79
+ });
80
+ else output.fail("Mirror failed", { details: [mirrorError.message, "Lockfile was created, but files were not downloaded."] });
81
+ return;
82
+ }
83
+ if (!mirrorResult) {
84
+ if (json) output.errorJson({
85
+ type: "NO_MIRROR_RESULT",
86
+ message: "Mirror returned no result",
87
+ details: { lockfileCreated: true }
88
+ });
89
+ else output.fail("Mirror returned no result", { details: ["Lockfile was created, but files were not downloaded."] });
90
+ return;
91
+ }
92
+ if (json) {
93
+ const jsonOutput = {
94
+ success: true,
95
+ storeDir,
96
+ versions: selectedVersions,
97
+ summary: mirrorResult.summary ? {
98
+ versionsCount: mirrorResult.versions.size,
99
+ downloaded: mirrorResult.summary.counts.success,
100
+ skipped: mirrorResult.summary.counts.skipped,
101
+ failed: mirrorResult.summary.counts.failed,
102
+ totalSize: mirrorResult.summary.storage.totalSize,
103
+ duration: mirrorResult.summary.duration
104
+ } : void 0
105
+ };
106
+ output.json(jsonOutput);
107
+ return;
108
+ }
109
+ output.success("Files downloaded");
110
+ if (mirrorResult.summary) {
111
+ const { counts, duration, storage } = mirrorResult.summary;
112
+ header("Summary");
113
+ keyValue("Versions", String(mirrorResult.versions.size));
114
+ keyValue("Downloaded", `${green(String(counts.success))} files`);
115
+ if (counts.skipped > 0) keyValue("Skipped", `${yellow(String(counts.skipped))} files`);
116
+ if (counts.failed > 0) keyValue("Failed", `${red(String(counts.failed))} files`);
117
+ keyValue("Total Size", storage.totalSize);
118
+ keyValue("Duration", formatDuration(duration));
119
+ }
120
+ blankLine();
121
+ output.success("Store initialized successfully");
122
+ blankLine();
123
+ }
124
+
125
+ //#endregion
126
+ export { runInitStore };
@@ -0,0 +1,89 @@
1
+ import { _ as output, p as green, t as printHelp, u as dim, v as red } from "./cli.mjs";
2
+ import { createUCDClient } from "@ucdjs/client";
3
+ import { UCDJS_API_BASE_URL } from "@ucdjs/env";
4
+ import { createDebugger } from "@ucdjs-internal/shared";
5
+
6
+ //#region src/cmd/files/list.ts
7
+ const debug = createDebugger("ucdjs:cli:files:list");
8
+ function formatDate(timestamp) {
9
+ return new Date(timestamp).toLocaleString();
10
+ }
11
+ function formatDirectoryListing(entries) {
12
+ if (entries.length === 0) return " (empty directory)";
13
+ const sorted = [...entries].sort((a, b) => {
14
+ if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
15
+ return a.name.localeCompare(b.name);
16
+ });
17
+ const lines = [];
18
+ const maxNameLength = Math.max(...sorted.map((e) => e.name.length), 20);
19
+ const padding = Math.min(maxNameLength + 2, 40);
20
+ for (const entry of sorted) {
21
+ const typeIcon = entry.type === "directory" ? green("▸") : "·";
22
+ const typeLabel = entry.type === "directory" ? green("dir") : dim("file");
23
+ const name = entry.name.padEnd(padding);
24
+ const date = entry.lastModified ? formatDate(entry.lastModified) : dim("no date");
25
+ lines.push(` ${typeIcon} ${name} ${typeLabel} ${date}`);
26
+ }
27
+ return lines.join("\n");
28
+ }
29
+ async function runFilesList({ path, flags }) {
30
+ if (flags?.help || flags?.h) {
31
+ printHelp({
32
+ headline: "List files and directories from the UCD API",
33
+ commandName: "ucd files list",
34
+ usage: "[path] [...flags]",
35
+ description: "List files and directories from the Unicode Character Database API. If no path is provided, lists the root directory.",
36
+ tables: { Flags: [
37
+ ["--base-url", "Base URL for the UCD API (defaults to api.ucdjs.dev)."],
38
+ ["--json", "Output directory listing as raw JSON."],
39
+ ["--help (-h)", "See all available flags."]
40
+ ] }
41
+ });
42
+ return;
43
+ }
44
+ const { baseUrl, json } = flags;
45
+ try {
46
+ const client = await createUCDClient(baseUrl || UCDJS_API_BASE_URL);
47
+ debug?.(`Fetching directory listing for path "${path || "(root)"}"`);
48
+ const result = await client.files.get(path || "");
49
+ if (result.error) {
50
+ output.error(red(`\n❌ Error fetching directory listing:`));
51
+ output.error(` ${result.error.message}`);
52
+ if (result.error.status === 404) output.error(` Path "${path || "(root)"}" not found.`);
53
+ return;
54
+ }
55
+ if (!result.data) {
56
+ output.error(red(`\n❌ Error: No data returned from API.`));
57
+ return;
58
+ }
59
+ if (typeof result.data === "string") {
60
+ output.error(red(`\n❌ Error: Path "${path || "(root)"}" is a file, not a directory.`));
61
+ output.error(` Use "ucd files get" to retrieve file content.`);
62
+ return;
63
+ }
64
+ if (!Array.isArray(result.data)) {
65
+ output.error(red(`\n❌ Error: Unexpected response format from API.`));
66
+ return;
67
+ }
68
+ const entries = result.data;
69
+ if (json) {
70
+ output.json(entries);
71
+ return;
72
+ }
73
+ const pathDisplay = path || "(root)";
74
+ output.log(`\nDirectory listing: ${green(pathDisplay)}\n`);
75
+ output.log(formatDirectoryListing(entries));
76
+ output.log("");
77
+ } catch (err) {
78
+ let message = "Unknown error";
79
+ if (err instanceof Error) message = err.message;
80
+ else if (typeof err === "string") message = err;
81
+ output.error(red(`\n❌ Error listing files:`));
82
+ output.error(` ${message}`);
83
+ output.error("Please check the API configuration and try again.");
84
+ output.error("If you believe this is a bug, please report it at https://github.com/ucdjs/ucd/issues");
85
+ }
86
+ }
87
+
88
+ //#endregion
89
+ export { runFilesList };