@ucdjs/cli 0.1.2 → 0.2.0
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/dist/{cli-utils-DyOk9RI0.js → cli-utils-CSnE3roj.js} +22 -13
- package/dist/cli.js +1 -1
- package/dist/download-x471YfwO.js +196 -0
- package/dist/fields-BCwNC6Tk.js +122 -0
- package/dist/{root-BT7DL0Ms.js → root-CK5_bOyd.js} +2 -2
- package/package.json +9 -6
- package/dist/fields-BqikCPbE.js +0 -98
- package/dist/generate-Z7bYnm_v.js +0 -103
|
@@ -4,14 +4,14 @@ import yargs from "yargs-parser";
|
|
|
4
4
|
|
|
5
5
|
//#region package.json
|
|
6
6
|
var name = "@ucdjs/cli";
|
|
7
|
-
var version = "0.
|
|
7
|
+
var version = "0.2.0";
|
|
8
8
|
var type = "module";
|
|
9
9
|
var author = {
|
|
10
10
|
"name": "Lucas Nørgård",
|
|
11
11
|
"email": "lucasnrgaard@gmail.com",
|
|
12
12
|
"url": "https://luxass.dev"
|
|
13
13
|
};
|
|
14
|
-
var packageManager = "pnpm@10.
|
|
14
|
+
var packageManager = "pnpm@10.11.0";
|
|
15
15
|
var license = "MIT";
|
|
16
16
|
var homepage = "https://github.com/ucdjs/ucd";
|
|
17
17
|
var repository = {
|
|
@@ -33,10 +33,13 @@ var dependencies = {
|
|
|
33
33
|
"@luxass/utils": "catalog:prod",
|
|
34
34
|
"@ucdjs/schema-gen": "workspace:*",
|
|
35
35
|
"farver": "catalog:prod",
|
|
36
|
+
"micromatch": "catalog:prod",
|
|
37
|
+
"p-limit": "catalog:prod",
|
|
36
38
|
"yargs-parser": "catalog:prod"
|
|
37
39
|
};
|
|
38
40
|
var devDependencies = {
|
|
39
41
|
"@luxass/eslint-config": "catalog:dev",
|
|
42
|
+
"@types/micromatch": "catalog:dev",
|
|
40
43
|
"@types/yargs-parser": "catalog:dev",
|
|
41
44
|
"eslint": "catalog:dev",
|
|
42
45
|
"publint": "catalog:dev",
|
|
@@ -65,7 +68,7 @@ var package_default = {
|
|
|
65
68
|
|
|
66
69
|
//#endregion
|
|
67
70
|
//#region src/cli-utils.ts
|
|
68
|
-
const SUPPORTED_COMMANDS = new Set(["
|
|
71
|
+
const SUPPORTED_COMMANDS = new Set(["codegen", "download"]);
|
|
69
72
|
/**
|
|
70
73
|
* Resolves the CLI command based on the provided arguments.
|
|
71
74
|
*
|
|
@@ -131,7 +134,7 @@ async function runCommand(cmd, flags) {
|
|
|
131
134
|
headline: "A CLI for working with the Unicode Character Database (UCD).",
|
|
132
135
|
usage: "[command] [...flags]",
|
|
133
136
|
tables: {
|
|
134
|
-
"Commands": [["
|
|
137
|
+
"Commands": [["download", "Download Unicode data files."], ["codegen", "Generate TypeScript code from UCD data."]],
|
|
135
138
|
"Global Flags": [
|
|
136
139
|
["--force", "Force the operation to run, even if it's not needed."],
|
|
137
140
|
["--version", "Show the version number and exit."],
|
|
@@ -143,27 +146,33 @@ async function runCommand(cmd, flags) {
|
|
|
143
146
|
case "version":
|
|
144
147
|
console.log(` ${bgGreen(black(` ucd `))} ${green(`v${package_default.version ?? "x.y.z"}`)}`);
|
|
145
148
|
break;
|
|
146
|
-
case "
|
|
147
|
-
const {
|
|
149
|
+
case "codegen": {
|
|
150
|
+
const { runCodegenRoot } = await import("./root-CK5_bOyd.js");
|
|
151
|
+
const subcommand = flags._[3]?.toString() ?? "";
|
|
152
|
+
await runCodegenRoot(subcommand, { flags });
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case "download": {
|
|
156
|
+
const { runDownload } = await import("./download-x471YfwO.js");
|
|
148
157
|
const versions = flags._.slice(3);
|
|
149
|
-
await
|
|
158
|
+
await runDownload({
|
|
150
159
|
versions,
|
|
151
160
|
flags
|
|
152
161
|
});
|
|
153
162
|
break;
|
|
154
163
|
}
|
|
155
|
-
case "codegen": {
|
|
156
|
-
const { runCodegenRoot } = await import("./root-BT7DL0Ms.js");
|
|
157
|
-
const subcommand = flags._[3]?.toString() ?? "";
|
|
158
|
-
await runCodegenRoot(subcommand, { flags });
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
164
|
default: throw new Error(`Error running ${cmd} -- no command found.`);
|
|
162
165
|
}
|
|
163
166
|
}
|
|
164
167
|
function parseFlags(args) {
|
|
165
168
|
return yargs(args, {
|
|
166
169
|
configuration: { "parse-positional-numbers": false },
|
|
170
|
+
default: { force: false },
|
|
171
|
+
boolean: [
|
|
172
|
+
"force",
|
|
173
|
+
"help",
|
|
174
|
+
"h"
|
|
175
|
+
],
|
|
167
176
|
string: [
|
|
168
177
|
"output-dir",
|
|
169
178
|
"input-dir",
|
package/dist/cli.js
CHANGED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { printHelp } from "./cli-utils-CSnE3roj.js";
|
|
2
|
+
import { gray, green, red, yellow } from "farver/fast";
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import path, { dirname } from "node:path";
|
|
5
|
+
import { UNICODE_VERSION_METADATA, hasUCDPath, mapToUCDPathVersion } from "@luxass/unicode-utils";
|
|
6
|
+
import micromatch from "micromatch";
|
|
7
|
+
|
|
8
|
+
//#region src/cmd/download.ts
|
|
9
|
+
const CONCURRENCY_LIMIT = 3;
|
|
10
|
+
const API_URL = "https://unicode-api.luxass.dev/api/v1";
|
|
11
|
+
const UNICODE_PROXY_URL = "https://unicode-proxy.ucdjs.dev";
|
|
12
|
+
async function runDownload({ versions: providedVersions, flags }) {
|
|
13
|
+
if (flags?.help || flags?.h) {
|
|
14
|
+
printHelp({
|
|
15
|
+
headline: "Download Unicode Data Files",
|
|
16
|
+
commandName: "ucd download",
|
|
17
|
+
usage: "<...versions> [...flags]",
|
|
18
|
+
tables: { Flags: [
|
|
19
|
+
["--output-dir", "Specify the output directory."],
|
|
20
|
+
["--exclude", "Exclude files matching glob patterns (e.g., '*Test*,ReadMe.txt,*.draft')."],
|
|
21
|
+
["--exclude-test", "Exclude all test files (ending with Test.txt)."],
|
|
22
|
+
["--force", "Force the download, even if the files already exist."],
|
|
23
|
+
["--debug", "Enable debug output."],
|
|
24
|
+
["--help (-h)", "See all available flags."]
|
|
25
|
+
] }
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (providedVersions.length === 0) {
|
|
30
|
+
console.error(red("No versions provided. Please provide at least one version."));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
let versions = [];
|
|
34
|
+
if (providedVersions[0] === "all") versions = UNICODE_VERSION_METADATA.map((v) => {
|
|
35
|
+
const mappedVersion = mapToUCDPathVersion(v.version);
|
|
36
|
+
return {
|
|
37
|
+
version: v.version,
|
|
38
|
+
mappedVersion: mappedVersion === v.version ? void 0 : mappedVersion
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
else versions = providedVersions.map((v) => {
|
|
42
|
+
const mappedVersion = mapToUCDPathVersion(v);
|
|
43
|
+
return {
|
|
44
|
+
version: v,
|
|
45
|
+
mappedVersion: mappedVersion === v ? void 0 : mappedVersion
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
const invalidVersions = versions.filter(({ version }) => !UNICODE_VERSION_METADATA.find((v) => v.version === version));
|
|
49
|
+
if (invalidVersions.length > 0) {
|
|
50
|
+
console.error(red(`Invalid version(s): ${invalidVersions.map((v) => v.version).join(", ")}. Please provide valid Unicode versions.`));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const outputDir = flags.outputDir ?? path.join(process.cwd(), "data");
|
|
54
|
+
await mkdir(outputDir, { recursive: true });
|
|
55
|
+
const excludePatterns = flags.exclude?.split(",").map((p) => p.trim()).filter(Boolean) || [];
|
|
56
|
+
if (flags.excludeTest) excludePatterns.push("**/*Test*");
|
|
57
|
+
function getAllFilePaths(entries) {
|
|
58
|
+
const filePaths = [];
|
|
59
|
+
function collectPaths(entryList, currentPath = "") {
|
|
60
|
+
for (const entry of entryList) {
|
|
61
|
+
const fullPath = currentPath ? `${currentPath}/${entry.path}` : entry.path;
|
|
62
|
+
if (!entry.children) filePaths.push(fullPath);
|
|
63
|
+
else if (entry.children.length > 0) collectPaths(entry.children, fullPath);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
collectPaths(entries);
|
|
67
|
+
return filePaths;
|
|
68
|
+
}
|
|
69
|
+
function filterEntriesRecursive(entries) {
|
|
70
|
+
if (excludePatterns.length === 0) return entries;
|
|
71
|
+
const allPaths = getAllFilePaths(entries);
|
|
72
|
+
const patterns = ["**", ...excludePatterns.map((pattern) => `!${pattern}`)];
|
|
73
|
+
const matchedPaths = new Set(micromatch(allPaths, patterns, {
|
|
74
|
+
dot: true,
|
|
75
|
+
nocase: true,
|
|
76
|
+
debug: flags.debug
|
|
77
|
+
}));
|
|
78
|
+
function filterEntries(entryList, prefix = "") {
|
|
79
|
+
const result = [];
|
|
80
|
+
for (const entry of entryList) {
|
|
81
|
+
const fullPath = prefix ? `${prefix}/${entry.path}` : entry.path;
|
|
82
|
+
if (!entry.children) {
|
|
83
|
+
if (matchedPaths.has(fullPath)) result.push(entry);
|
|
84
|
+
} else {
|
|
85
|
+
const filteredChildren = filterEntries(entry.children, fullPath);
|
|
86
|
+
if (filteredChildren.length > 0) result.push({
|
|
87
|
+
...entry,
|
|
88
|
+
children: filteredChildren
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
return filterEntries(entries);
|
|
95
|
+
}
|
|
96
|
+
async function processFileEntries(entries, basePath, versionOutputDir, downloadedFiles, currentDirPath = "", errors) {
|
|
97
|
+
const dirPromises = [];
|
|
98
|
+
const filePromises = [];
|
|
99
|
+
for (const entry of entries) {
|
|
100
|
+
const entryOutputPath = currentDirPath ? path.join(currentDirPath, entry.path) : entry.path;
|
|
101
|
+
const outputPath = path.join(versionOutputDir, entryOutputPath);
|
|
102
|
+
if (entry.children) dirPromises.push((async () => {
|
|
103
|
+
await mkdir(outputPath, { recursive: true });
|
|
104
|
+
await processFileEntries(entry.children || [], `${basePath}/${entry.path}`, versionOutputDir, downloadedFiles, entryOutputPath, errors);
|
|
105
|
+
})());
|
|
106
|
+
else filePromises.push((async () => {
|
|
107
|
+
try {
|
|
108
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
109
|
+
const url = `${UNICODE_PROXY_URL}${basePath}/${entry.path}`;
|
|
110
|
+
const response = await fetch(url);
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
errors.push(`Failed to fetch ${entry.path}: ${response.status} ${response.statusText}`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const content = await response.text();
|
|
116
|
+
await writeFile(outputPath, content);
|
|
117
|
+
downloadedFiles.push(outputPath);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
errors.push(`Error downloading ${entry.path}: ${err.message}`);
|
|
120
|
+
}
|
|
121
|
+
})());
|
|
122
|
+
}
|
|
123
|
+
await Promise.all([...dirPromises, ...filePromises]);
|
|
124
|
+
}
|
|
125
|
+
async function processVersion(version) {
|
|
126
|
+
console.info(`Starting download for Unicode ${green(version.version)}${version.mappedVersion ? gray(` (${green(version.mappedVersion)})`) : ""}`);
|
|
127
|
+
const downloadedFiles = [];
|
|
128
|
+
const errors = [];
|
|
129
|
+
const versionOutputDir = path.join(outputDir, `v${version.version}`);
|
|
130
|
+
await mkdir(versionOutputDir, { recursive: true });
|
|
131
|
+
try {
|
|
132
|
+
const filesResponse = await fetch(`${API_URL}/unicode-files/${version.version}`);
|
|
133
|
+
if (!filesResponse.ok) {
|
|
134
|
+
console.error(red(`Failed to fetch file list for version ${version.version}: ${filesResponse.status} ${filesResponse.statusText}`));
|
|
135
|
+
return {
|
|
136
|
+
version: version.version,
|
|
137
|
+
downloadedFiles,
|
|
138
|
+
errors: [`Failed to fetch file list: ${filesResponse.statusText}`]
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const fileEntries = await filesResponse.json();
|
|
142
|
+
if (!Array.isArray(fileEntries)) {
|
|
143
|
+
console.error(red(`Invalid response format for version ${version.version}`));
|
|
144
|
+
return {
|
|
145
|
+
version: version.version,
|
|
146
|
+
downloadedFiles,
|
|
147
|
+
errors: ["Invalid response format"]
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const filteredEntries = filterEntriesRecursive(fileEntries);
|
|
151
|
+
const correctVersion = version.mappedVersion ?? version.version;
|
|
152
|
+
const basePath = `/${correctVersion}${hasUCDPath(correctVersion) ? "/ucd" : ""}`;
|
|
153
|
+
await processFileEntries(filteredEntries, basePath, versionOutputDir, downloadedFiles, "", errors);
|
|
154
|
+
if (downloadedFiles.length === 0 && errors.length === 0) console.warn(yellow(`No files were downloaded for Unicode ${version.version}`));
|
|
155
|
+
else if (downloadedFiles.length > 0) {
|
|
156
|
+
console.info(green(`✓ Downloaded ${downloadedFiles.length} files for Unicode ${version.version}`));
|
|
157
|
+
if (flags.debug) downloadedFiles.forEach((file) => {
|
|
158
|
+
const relativePath = path.relative(versionOutputDir, file);
|
|
159
|
+
console.info(green(` - ${relativePath}`));
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (errors.length > 0) {
|
|
163
|
+
console.warn(yellow(`${errors.length} errors occurred during download of Unicode ${version.version}`));
|
|
164
|
+
if (flags.debug) errors.forEach((error) => console.error(red(` - ${error}`)));
|
|
165
|
+
}
|
|
166
|
+
} catch (err) {
|
|
167
|
+
const errorMessage = `Error processing version ${version.version}: ${err.message}`;
|
|
168
|
+
errors.push(errorMessage);
|
|
169
|
+
console.error(red(errorMessage));
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
version: version.version,
|
|
173
|
+
downloadedFiles,
|
|
174
|
+
errors
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const results = [];
|
|
178
|
+
const versionGroups = [];
|
|
179
|
+
for (let i = 0; i < versions.length; i += CONCURRENCY_LIMIT) versionGroups.push(versions.slice(i, i + CONCURRENCY_LIMIT));
|
|
180
|
+
for (const versionGroup of versionGroups) {
|
|
181
|
+
const batchResults = await Promise.all(versionGroup.map(processVersion));
|
|
182
|
+
results.push(...batchResults);
|
|
183
|
+
}
|
|
184
|
+
const totalFiles = results.reduce((sum, r) => sum + r.downloadedFiles.length, 0);
|
|
185
|
+
const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);
|
|
186
|
+
const successfulVersions = results.filter((r) => r.downloadedFiles.length > 0).length;
|
|
187
|
+
console.info(`\n${"=".repeat(50)}`);
|
|
188
|
+
console.info("Download Summary:");
|
|
189
|
+
console.info(green(`✓ ${successfulVersions}/${results.length} versions processed successfully`));
|
|
190
|
+
console.info(green(`✓ ${totalFiles} total files downloaded`));
|
|
191
|
+
if (totalErrors > 0) console.warn(yellow(`⚠ ${totalErrors} total errors encountered`));
|
|
192
|
+
console.info("=".repeat(50));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
//#endregion
|
|
196
|
+
export { runDownload };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { printHelp } from "./cli-utils-CSnE3roj.js";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { mkdir, readdir, stat, writeFile } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { runSchemagen } from "@ucdjs/schema-gen";
|
|
7
|
+
|
|
8
|
+
//#region src/cmd/codegen/fields.ts
|
|
9
|
+
function flattenVersion(version) {
|
|
10
|
+
const parts = version.split(".");
|
|
11
|
+
while (parts.length > 1 && parts[parts.length - 1] === "0") parts.pop();
|
|
12
|
+
return parts.join(".");
|
|
13
|
+
}
|
|
14
|
+
async function scanFiles(inputPath) {
|
|
15
|
+
const resolvedInputPath = path.resolve(inputPath);
|
|
16
|
+
const isDirectory = (await stat(resolvedInputPath)).isDirectory();
|
|
17
|
+
const filesWithVersion = [];
|
|
18
|
+
if (!isDirectory) {
|
|
19
|
+
const parentDir = path.dirname(resolvedInputPath);
|
|
20
|
+
const versionMatch = parentDir.match(/v(\d+\.\d+\.\d+)/);
|
|
21
|
+
const version = versionMatch ? flattenVersion(versionMatch[1]) : "unknown";
|
|
22
|
+
filesWithVersion.push({
|
|
23
|
+
filePath: resolvedInputPath,
|
|
24
|
+
version
|
|
25
|
+
});
|
|
26
|
+
} else {
|
|
27
|
+
const dir = await readdir(resolvedInputPath, {
|
|
28
|
+
withFileTypes: true,
|
|
29
|
+
recursive: true
|
|
30
|
+
});
|
|
31
|
+
for (const file of dir) {
|
|
32
|
+
if (!file.isFile() || !file.name.endsWith(".txt") || file.name.endsWith("Test.txt")) continue;
|
|
33
|
+
const filePath = path.join(file.parentPath, file.name);
|
|
34
|
+
const versionMatch = filePath.match(/v(\d+\.\d+\.\d+)/);
|
|
35
|
+
const version = versionMatch ? flattenVersion(versionMatch[1]) : "unknown";
|
|
36
|
+
filesWithVersion.push({
|
|
37
|
+
filePath,
|
|
38
|
+
version
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return filesWithVersion;
|
|
43
|
+
}
|
|
44
|
+
async function writeBundledFile(version, results, bundleTemplate, outputDir) {
|
|
45
|
+
let bundledCode = `// This file is generated by ucd codegen. Do not edit this file directly.\n`;
|
|
46
|
+
bundledCode += `// Unicode Version: ${version}\n\n`;
|
|
47
|
+
for (const result of results) {
|
|
48
|
+
const relativePath = path.relative(process.cwd(), result.fileName);
|
|
49
|
+
bundledCode += `\n// #region ${relativePath}\n`;
|
|
50
|
+
bundledCode += result.code;
|
|
51
|
+
bundledCode += `\n// #endregion\n`;
|
|
52
|
+
}
|
|
53
|
+
const bundleFileName = bundleTemplate.replace("{version}", version);
|
|
54
|
+
let bundlePath;
|
|
55
|
+
if (path.isAbsolute(bundleFileName) || bundleFileName.startsWith("./") || bundleFileName.startsWith("../")) bundlePath = path.resolve(bundleFileName);
|
|
56
|
+
else if (outputDir) bundlePath = path.resolve(path.join(outputDir, bundleFileName));
|
|
57
|
+
else bundlePath = path.resolve(bundleFileName);
|
|
58
|
+
if (!bundlePath.endsWith(".ts")) bundlePath += ".ts";
|
|
59
|
+
await mkdir(path.dirname(bundlePath), { recursive: true });
|
|
60
|
+
await writeFile(bundlePath, bundledCode, "utf-8");
|
|
61
|
+
console.log(`Generated bundled fields for Unicode ${version} in ${bundlePath}`);
|
|
62
|
+
}
|
|
63
|
+
async function runFieldCodegen({ inputPath, flags }) {
|
|
64
|
+
if (flags?.help || flags?.h) {
|
|
65
|
+
printHelp({
|
|
66
|
+
headline: "Generate Unicode Data Files",
|
|
67
|
+
commandName: "ucd codegen fields",
|
|
68
|
+
usage: "<input> [...flags]",
|
|
69
|
+
tables: { Flags: [
|
|
70
|
+
["--openai-key (-k)", "The OpenAI API key to use. (can also be set using OPENAI_API_KEY env var)"],
|
|
71
|
+
["--output-dir", "Specify the output directory for generated files (defaults to .codegen)"],
|
|
72
|
+
["--bundle <filename>", "Combine generated files into a single file per version. Use {version} placeholder for version-specific naming"],
|
|
73
|
+
["--help (-h)", "See all available flags."]
|
|
74
|
+
] }
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const openaiKey = flags.openaiKey || process.env.OPENAI_API_KEY;
|
|
79
|
+
if (!openaiKey) {
|
|
80
|
+
console.error("No OpenAI API key provided. Please provide an OpenAI API key.");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (inputPath == null) {
|
|
84
|
+
console.error("No input path provided. Please provide an input path.");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const resolvedInputPath = path.resolve(inputPath);
|
|
88
|
+
if (!existsSync(resolvedInputPath)) {
|
|
89
|
+
console.error(`invalid input path: ${inputPath}. Please provide a valid input path.`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const shouldBundle = typeof flags.bundle === "string" || flags.bundle === true;
|
|
93
|
+
const bundleTemplate = typeof flags.bundle === "string" ? flags.bundle : "index.ts";
|
|
94
|
+
let outputDir = flags.outputDir;
|
|
95
|
+
if (!outputDir) if (shouldBundle && (path.isAbsolute(bundleTemplate) || bundleTemplate.startsWith("./") || bundleTemplate.startsWith("../"))) outputDir = path.dirname(bundleTemplate);
|
|
96
|
+
else outputDir = path.join(path.dirname(resolvedInputPath), ".codegen");
|
|
97
|
+
if (outputDir) await mkdir(outputDir, { recursive: true });
|
|
98
|
+
const filesWithVersion = await scanFiles(inputPath);
|
|
99
|
+
console.log(`Found ${filesWithVersion.length} files to process.`);
|
|
100
|
+
const results = await runSchemagen({
|
|
101
|
+
files: filesWithVersion,
|
|
102
|
+
openaiKey
|
|
103
|
+
});
|
|
104
|
+
const writePromises = [];
|
|
105
|
+
if (!shouldBundle) {
|
|
106
|
+
writePromises.push(...results.map((result) => writeFile(path.join(outputDir, `${result.fileName}.ts`), result.code, "utf-8")));
|
|
107
|
+
await Promise.all(writePromises);
|
|
108
|
+
console.log(`Generated fields for ${results.length} files in ${outputDir}`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const resultsByVersion = new Map();
|
|
112
|
+
for (const result of results) {
|
|
113
|
+
const versionResults = resultsByVersion.get(result.version) ?? [];
|
|
114
|
+
versionResults.push(result);
|
|
115
|
+
resultsByVersion.set(result.version, versionResults);
|
|
116
|
+
}
|
|
117
|
+
const bundlePromises = Array.from(resultsByVersion.entries()).map(([version, versionResults]) => writeBundledFile(version, versionResults, bundleTemplate, outputDir));
|
|
118
|
+
await Promise.all(bundlePromises);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
export { runFieldCodegen };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { printHelp } from "./cli-utils-
|
|
1
|
+
import { printHelp } from "./cli-utils-CSnE3roj.js";
|
|
2
2
|
|
|
3
3
|
//#region src/cmd/codegen/root.ts
|
|
4
4
|
const CODEGEN_SUBCOMMANDS = ["fields"];
|
|
@@ -18,7 +18,7 @@ async function runCodegenRoot(subcommand, { flags }) {
|
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
20
|
if (subcommand === "fields") {
|
|
21
|
-
const { runFieldCodegen } = await import("./fields-
|
|
21
|
+
const { runFieldCodegen } = await import("./fields-BCwNC6Tk.js");
|
|
22
22
|
const inputPath = flags._.slice(4)?.toString() ?? "";
|
|
23
23
|
await runFieldCodegen({
|
|
24
24
|
inputPath,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ucdjs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Lucas Nørgård",
|
|
@@ -25,20 +25,23 @@
|
|
|
25
25
|
"dist"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@luxass/unicode-utils": "^0.
|
|
29
|
-
"@luxass/utils": "^
|
|
28
|
+
"@luxass/unicode-utils": "^0.9.0",
|
|
29
|
+
"@luxass/utils": "^2.2.1",
|
|
30
30
|
"farver": "^0.4.2",
|
|
31
|
+
"micromatch": "^4.0.8",
|
|
32
|
+
"p-limit": "^6.2.0",
|
|
31
33
|
"yargs-parser": "^21.1.1",
|
|
32
|
-
"@ucdjs/schema-gen": "0.1
|
|
34
|
+
"@ucdjs/schema-gen": "0.2.1"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"@luxass/eslint-config": "^4.18.1",
|
|
38
|
+
"@types/micromatch": "^4.0.9",
|
|
36
39
|
"@types/yargs-parser": "^21.0.3",
|
|
37
|
-
"eslint": "^9.
|
|
40
|
+
"eslint": "^9.27.0",
|
|
38
41
|
"publint": "^0.3.12",
|
|
39
42
|
"tsdown": "v0.9.3",
|
|
40
43
|
"typescript": "^5.8.3",
|
|
41
|
-
"vitest-testdirs": "^
|
|
44
|
+
"vitest-testdirs": "^4.0.0"
|
|
42
45
|
},
|
|
43
46
|
"publishConfig": {
|
|
44
47
|
"access": "public"
|
package/dist/fields-BqikCPbE.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { printHelp } from "./cli-utils-DyOk9RI0.js";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { RawDataFile } from "@luxass/unicode-utils/data-files";
|
|
6
|
-
import { toKebabCase } from "@luxass/utils";
|
|
7
|
-
import { generateFields } from "@ucdjs/schema-gen";
|
|
8
|
-
|
|
9
|
-
//#region src/cmd/codegen/fields.ts
|
|
10
|
-
async function runFieldCodegen({ inputPath, flags }) {
|
|
11
|
-
if (flags?.help || flags?.h) {
|
|
12
|
-
printHelp({
|
|
13
|
-
headline: "Generate Unicode Data Files",
|
|
14
|
-
commandName: "ucd codegen fields",
|
|
15
|
-
usage: "<input> [...flags]",
|
|
16
|
-
tables: { Flags: [
|
|
17
|
-
["--openai-key (-k)", "The OpenAI API key to use. (can also be set using OPENAI_API_KEY env var)"],
|
|
18
|
-
["--output-dir", "Specify the output directory for generated files (defaults to .codegen)"],
|
|
19
|
-
["--bundle <filename>", "Combine all generated files into a single file, if no filename is provided, the default is index.ts"],
|
|
20
|
-
["--help (-h)", "See all available flags."]
|
|
21
|
-
] }
|
|
22
|
-
});
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
const openaiKey = flags.openaiKey || process.env.OPENAI_API_KEY;
|
|
26
|
-
if (!openaiKey) {
|
|
27
|
-
console.error("No OpenAI API key provided. Please provide an OpenAI API key.");
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
if (inputPath == null) {
|
|
31
|
-
console.error("No input path provided. Please provide an input path.");
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
const resolvedInputPath = path.resolve(inputPath);
|
|
35
|
-
if (!existsSync(resolvedInputPath)) {
|
|
36
|
-
console.error(`invalid input path: ${inputPath}. Please provide a valid input path.`);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
const outputDir = flags.outputDir || process.cwd();
|
|
40
|
-
await mkdir(outputDir, { recursive: true });
|
|
41
|
-
const shouldBundle = typeof flags.bundle === "string" || flags.bundle === true;
|
|
42
|
-
const bundleFileName = typeof flags.bundle === "string" ? flags.bundle : "index.ts";
|
|
43
|
-
const isDirectory = (await stat(resolvedInputPath)).isDirectory();
|
|
44
|
-
const files = [];
|
|
45
|
-
if (isDirectory) {
|
|
46
|
-
const dir = await readdir(resolvedInputPath, {
|
|
47
|
-
withFileTypes: true,
|
|
48
|
-
recursive: true
|
|
49
|
-
});
|
|
50
|
-
for (const file of dir) if (file.isFile()) {
|
|
51
|
-
if (!file.name.endsWith(".txt")) continue;
|
|
52
|
-
files.push(path.join(file.parentPath, file.name));
|
|
53
|
-
}
|
|
54
|
-
} else files.push(resolvedInputPath);
|
|
55
|
-
const promises = files.map(async (filePath) => {
|
|
56
|
-
const content = await readFile(filePath, "utf-8");
|
|
57
|
-
const datafile = new RawDataFile(content);
|
|
58
|
-
if (datafile.heading == null) {
|
|
59
|
-
console.error(`heading for file ${filePath} is null. Skipping file.`);
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
const code = await generateFields({
|
|
63
|
-
datafile,
|
|
64
|
-
apiKey: openaiKey
|
|
65
|
-
});
|
|
66
|
-
if (code == null) {
|
|
67
|
-
console.error(`Error generating fields for file: ${filePath}`);
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
if (!shouldBundle) {
|
|
71
|
-
const fileName = toKebabCase(path.basename(filePath).replace(/\.txt$/, "")).toLowerCase();
|
|
72
|
-
await writeFile(path.join(outputDir, `${fileName}.ts`), code, "utf-8");
|
|
73
|
-
}
|
|
74
|
-
return {
|
|
75
|
-
code,
|
|
76
|
-
fileName: filePath.replace(`${process.cwd()}/`, "")
|
|
77
|
-
};
|
|
78
|
-
});
|
|
79
|
-
const generatedCode = await Promise.all(promises);
|
|
80
|
-
if (!shouldBundle) {
|
|
81
|
-
console.log(`Generated fields for ${files.length} files in ${outputDir}`);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
let bundledCode = `// This file is generated by ucd codegen. Do not edit this file directly.\n\n`;
|
|
85
|
-
for (const { fileName, code } of generatedCode.filter((obj) => obj != null)) {
|
|
86
|
-
bundledCode += `//#region ${fileName}\n`;
|
|
87
|
-
bundledCode += code;
|
|
88
|
-
bundledCode += `\n//#endregion\n`;
|
|
89
|
-
bundledCode += "\n\n";
|
|
90
|
-
}
|
|
91
|
-
let bundlePath = path.resolve(path.join(outputDir, bundleFileName));
|
|
92
|
-
if (path.extname(bundleFileName) === "") bundlePath = path.join(outputDir, `${bundleFileName}.ts`);
|
|
93
|
-
await writeFile(bundlePath, bundledCode, "utf-8");
|
|
94
|
-
console.log(`Generated bundled fields in ${bundlePath}`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
//#endregion
|
|
98
|
-
export { runFieldCodegen };
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { printHelp } from "./cli-utils-DyOk9RI0.js";
|
|
2
|
-
import { green, yellow } from "farver/fast";
|
|
3
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
-
import path, { dirname, join } from "node:path";
|
|
5
|
-
import { UNICODE_VERSIONS_WITH_UCD } from "@luxass/unicode-utils";
|
|
6
|
-
|
|
7
|
-
//#region src/cmd/generate.ts
|
|
8
|
-
const BASE_URL = "https://unicode-proxy.ucdjs.dev/proxy";
|
|
9
|
-
async function runGenerate({ versions: providedVersions, flags }) {
|
|
10
|
-
if (flags?.help || flags?.h) {
|
|
11
|
-
printHelp({
|
|
12
|
-
headline: "Generate Unicode Data Files",
|
|
13
|
-
commandName: "ucd generate",
|
|
14
|
-
usage: "<...versions> [...flags]",
|
|
15
|
-
tables: { Flags: [["--output-dir", "Specify the output directory."], ["--help (-h)", "See all available flags."]] }
|
|
16
|
-
});
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
if (providedVersions.length === 0) {
|
|
20
|
-
console.error("No versions provided. Please provide at least one version.");
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
if (providedVersions[0] === "all") providedVersions = UNICODE_VERSIONS_WITH_UCD.map((v) => v.version);
|
|
24
|
-
const invalidVersions = providedVersions.filter((version) => !UNICODE_VERSIONS_WITH_UCD.find((v) => v.version === version));
|
|
25
|
-
if (invalidVersions.length > 0) {
|
|
26
|
-
console.error(`Invalid version(s) provided: ${invalidVersions.join(", ")}. Please provide valid Unicode versions.`);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const outputDir = flags.outputDir ?? path.join(process.cwd(), "data");
|
|
30
|
-
await mkdir(outputDir, { recursive: true });
|
|
31
|
-
async function processDirectory(entry, basePath, version, baseOutputDir, downloadedFiles) {
|
|
32
|
-
const dirPath = `${BASE_URL}/${version}/ucd/${entry.path}`;
|
|
33
|
-
const dirResponse = await fetch(dirPath);
|
|
34
|
-
if (!dirResponse.ok) {
|
|
35
|
-
console.error(`failed to fetch directory: ${entry.path}`);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const dirEntries = await dirResponse.json();
|
|
39
|
-
if (!Array.isArray(dirEntries)) {
|
|
40
|
-
console.error(`invalid response format for directory: ${entry.path}`);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
const fileEntries = dirEntries.filter((e) => e.type === "file" && e.name !== "ReadMe.txt" && !e.path.includes("latest") && !e.path.includes("draft"));
|
|
44
|
-
await Promise.all(fileEntries.map(async (fileEntry) => {
|
|
45
|
-
const fullPath = `${basePath}/${entry.path}/${fileEntry.path}`;
|
|
46
|
-
const pathParts = fullPath.replace(/^\//, "").split("/");
|
|
47
|
-
const filePath = join(baseOutputDir, pathParts.slice(2).join("/"));
|
|
48
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
49
|
-
try {
|
|
50
|
-
const content = await fetch(`${BASE_URL}${fullPath}`).then((res) => res.text());
|
|
51
|
-
await writeFile(filePath, content);
|
|
52
|
-
downloadedFiles.push(filePath);
|
|
53
|
-
} catch {
|
|
54
|
-
console.error(`failed to process file: ${fullPath}`);
|
|
55
|
-
}
|
|
56
|
-
}));
|
|
57
|
-
const dirEntriesToProcess = dirEntries.filter((e) => e.type === "directory");
|
|
58
|
-
await Promise.all(dirEntriesToProcess.map((dir) => processDirectory(dir, `${basePath}/${entry.path}`, version, baseOutputDir, downloadedFiles)));
|
|
59
|
-
}
|
|
60
|
-
for (const version of providedVersions) {
|
|
61
|
-
console.info(`Starting the generation process for data files for ${green(`Unicode ${version}`)}!`);
|
|
62
|
-
const downloadedFilesForVersion = [];
|
|
63
|
-
const versionPath = `/${version}/ucd`;
|
|
64
|
-
const rootResponse = await fetch(`${BASE_URL}${versionPath}`);
|
|
65
|
-
if (!rootResponse.ok) {
|
|
66
|
-
console.error(`failed to fetch data for version ${version}`);
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
const rootEntries = await rootResponse.json();
|
|
70
|
-
if (!Array.isArray(rootEntries)) {
|
|
71
|
-
console.error(`invalid response format for version ${version}`);
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
const versionOutputDir = path.join(outputDir, `v${version}`);
|
|
75
|
-
await mkdir(versionOutputDir, { recursive: true });
|
|
76
|
-
const rootFiles = rootEntries.filter((e) => e.type === "file" && e.name !== "ReadMe.txt" && !e.path.includes("latest") && !e.path.includes("draft"));
|
|
77
|
-
await Promise.all(rootFiles.map(async (fileEntry) => {
|
|
78
|
-
const fileUrl = `${versionPath}/${fileEntry.path}`;
|
|
79
|
-
const outputFilePath = path.join(versionOutputDir, fileEntry.name);
|
|
80
|
-
try {
|
|
81
|
-
const content = await fetch(`${BASE_URL}${fileUrl}`).then((res) => res.text());
|
|
82
|
-
await writeFile(outputFilePath, content);
|
|
83
|
-
downloadedFilesForVersion.push(outputFilePath);
|
|
84
|
-
} catch {
|
|
85
|
-
console.error(`failed to process file: ${fileUrl}`);
|
|
86
|
-
}
|
|
87
|
-
}));
|
|
88
|
-
const directories = rootEntries.filter((e) => e.type === "directory");
|
|
89
|
-
await Promise.all(directories.map((dir) => processDirectory(dir, versionPath, version, versionOutputDir, downloadedFilesForVersion)));
|
|
90
|
-
if (downloadedFilesForVersion.length === 0) {
|
|
91
|
-
console.warn(yellow(`No files were downloaded for Unicode ${version}.`));
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
console.info(green(`✓ Generated ${downloadedFilesForVersion.length} for Unicode ${version}`));
|
|
95
|
-
for (const file of downloadedFilesForVersion) {
|
|
96
|
-
const relativePath = path.relative(versionOutputDir, file);
|
|
97
|
-
console.info(green(` - ${relativePath}`));
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
//#endregion
|
|
103
|
-
export { runGenerate };
|