hanzio 2.2.0 → 3.1.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/README.md +133 -7
- package/dist/api-wrapper/connection-auth.d.ts +61 -0
- package/dist/cli/fields.d.ts +30 -0
- package/dist/cli/index.d.ts +122 -0
- package/dist/format/index.d.ts +91 -0
- package/dist/format/phone.d.ts +19 -0
- package/dist/http/index.d.ts +14 -0
- package/dist/i18n/catalog.d.ts +47 -0
- package/dist/i18n/check.d.ts +45 -0
- package/dist/i18n/cli.d.ts +2 -0
- package/dist/i18n/content-rules.d.ts +55 -0
- package/dist/i18n/i18next.d.ts +39 -0
- package/dist/i18n/index.d.ts +16 -1
- package/dist/i18n/locales.d.ts +62 -0
- package/dist/i18n/vite.d.ts +16 -0
- package/dist/i18n/vue.d.ts +93 -0
- package/dist/{index-kj1phgrx.js → index-29mmh343.js} +24 -1
- package/dist/{index-zbj9s3s3.js → index-2kz392vw.js} +1 -1
- package/dist/index-5n2e326q.js +409 -0
- package/dist/{index-hy7kbb99.js → index-5tzqfmnj.js} +1 -1
- package/dist/{index-0zvtcbzy.js → index-6f2w90pp.js} +69 -1
- package/dist/index-6ragggbs.js +473 -0
- package/dist/index-mq2qgc20.js +116 -0
- package/dist/index-najbyzpr.js +99 -0
- package/dist/index-ww09bm49.js +250 -0
- package/dist/iso/countries.d.ts +262 -0
- package/dist/iso/currencies.d.ts +165 -0
- package/dist/iso/index.d.ts +4 -0
- package/dist/iso/languages.d.ts +195 -0
- package/dist/iso/phone-language.d.ts +5 -0
- package/dist/promise/index.d.ts +31 -0
- package/dist/redis/client.d.ts +49 -0
- package/dist/redis/concurrency-limit.d.ts +63 -0
- package/dist/redis/index.d.ts +3 -0
- package/dist/redis/lua-script.d.ts +13 -0
- package/dist/secrets/infisical-identity-file.d.ts +12 -0
- package/dist/src/api-wrapper/connection-auth.js +82 -0
- package/dist/src/api-wrapper/index.js +1 -1
- package/dist/src/cli/index.js +10 -0
- package/dist/src/format/index.js +79 -2
- package/dist/src/format/phone.js +21 -0
- package/dist/src/http/index.js +69 -0
- package/dist/src/i18n/check.js +15 -0
- package/dist/src/i18n/cli.js +71 -0
- package/dist/src/i18n/i18next.js +48 -0
- package/dist/src/i18n/index.js +16 -34
- package/dist/src/i18n/vite.js +25 -0
- package/dist/src/i18n/vue.js +51 -0
- package/dist/src/index.js +22 -6
- package/dist/src/iso/index.js +655 -0
- package/dist/src/redis/index.js +577 -0
- package/dist/src/secrets/index.js +78 -35
- package/dist/src/sitemap/index.js +1 -1
- package/dist/src/storage/index.js +8 -117
- package/dist/src/testing/index.js +348 -0
- package/dist/src/zod/index.js +20 -253
- package/dist/string/index.d.ts +21 -0
- package/dist/testing/connection-auth-contract.d.ts +43 -0
- package/dist/testing/helpers.d.ts +26 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/module-mocker.d.ts +30 -0
- package/dist/testing/prisma.d.ts +30 -0
- package/dist/testing/window.d.ts +18 -0
- package/dist/typedswitch/index.d.ts +55 -34
- package/dist/zod/index.d.ts +10 -0
- package/dist/zod/introspect.d.ts +2 -1
- package/package.json +97 -2
- package/src/api-wrapper/README.md +57 -2
- package/src/api-wrapper/SKILL.md +256 -0
- package/src/api-wrapper/connection-auth.ts +142 -0
- package/src/cli/README.md +220 -0
- package/src/cli/fields.ts +213 -0
- package/src/cli/index.ts +532 -0
- package/src/format/README.md +82 -2
- package/src/format/index.ts +148 -0
- package/src/format/phone.ts +43 -0
- package/src/http/README.md +25 -0
- package/src/http/index.ts +94 -0
- package/src/i18n/README.md +206 -3
- package/src/i18n/catalog.ts +139 -0
- package/src/i18n/check.ts +376 -0
- package/src/i18n/cli.ts +90 -0
- package/src/i18n/content-rules.ts +179 -0
- package/src/i18n/i18next.ts +89 -0
- package/src/i18n/index.ts +34 -0
- package/src/i18n/locales.ts +135 -0
- package/src/i18n/vite.ts +40 -0
- package/src/i18n/vue.ts +82 -0
- package/src/index.ts +1 -1
- package/src/iso/README.md +44 -0
- package/src/iso/countries.ts +272 -0
- package/src/iso/currencies.ts +172 -0
- package/src/iso/index.ts +8 -0
- package/src/iso/languages.ts +208 -0
- package/src/iso/phone-language.ts +46 -0
- package/src/promise/index.ts +100 -0
- package/src/redis/README.md +112 -0
- package/src/redis/client.ts +239 -0
- package/src/redis/concurrency-limit.ts +591 -0
- package/src/redis/index.ts +15 -0
- package/src/redis/lua-script.ts +43 -0
- package/src/secrets/README.md +5 -1
- package/src/secrets/infisical-identity-file.ts +69 -0
- package/src/secrets/infisical.ts +18 -3
- package/src/string/index.ts +59 -0
- package/src/testing/README.md +119 -0
- package/src/testing/connection-auth-contract.ts +303 -0
- package/src/testing/helpers.ts +41 -0
- package/src/testing/index.ts +12 -0
- package/src/testing/module-mocker.ts +50 -0
- package/src/testing/prisma.ts +78 -0
- package/src/testing/window.ts +119 -0
- package/src/typedswitch/README.md +55 -39
- package/src/typedswitch/index.ts +97 -83
- package/src/zod/index.ts +20 -0
- package/src/zod/introspect.ts +1 -1
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defOf,
|
|
3
|
+
unwrapOnce
|
|
4
|
+
} from "./index-ww09bm49.js";
|
|
5
|
+
|
|
6
|
+
// src/cli/index.ts
|
|
7
|
+
import { parseArgs } from "node:util";
|
|
8
|
+
|
|
9
|
+
// src/cli/fields.ts
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
class UsageError extends Error {
|
|
12
|
+
}
|
|
13
|
+
var kebab = (key) => key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
14
|
+
function fieldsOf(schema) {
|
|
15
|
+
if (!schema)
|
|
16
|
+
return [];
|
|
17
|
+
return Object.entries(schema.shape).map(([key, child]) => toField(key, child));
|
|
18
|
+
}
|
|
19
|
+
function toField(key, schema) {
|
|
20
|
+
const metas = [];
|
|
21
|
+
let optional = false;
|
|
22
|
+
let hasDefault = false;
|
|
23
|
+
let defaultValue;
|
|
24
|
+
let current = schema;
|
|
25
|
+
for (;; ) {
|
|
26
|
+
const meta = z.globalRegistry.get(current);
|
|
27
|
+
if (meta)
|
|
28
|
+
metas.unshift(meta);
|
|
29
|
+
const def = defOf(current);
|
|
30
|
+
if (def.type === "optional")
|
|
31
|
+
optional = true;
|
|
32
|
+
if ((def.type === "default" || def.type === "prefault") && !hasDefault) {
|
|
33
|
+
hasDefault = true;
|
|
34
|
+
defaultValue = def.defaultValue;
|
|
35
|
+
}
|
|
36
|
+
const inner = unwrapOnce(def);
|
|
37
|
+
if (!inner)
|
|
38
|
+
break;
|
|
39
|
+
current = inner;
|
|
40
|
+
}
|
|
41
|
+
const meta = Object.assign({}, ...metas);
|
|
42
|
+
let def = defOf(current);
|
|
43
|
+
const multiple = def.type === "array";
|
|
44
|
+
if (def.type === "array") {
|
|
45
|
+
let element = def.element;
|
|
46
|
+
for (let inner = unwrapOnce(defOf(element));inner; ) {
|
|
47
|
+
element = inner;
|
|
48
|
+
inner = unwrapOnce(defOf(element));
|
|
49
|
+
}
|
|
50
|
+
def = defOf(element);
|
|
51
|
+
}
|
|
52
|
+
const alias = meta.alias;
|
|
53
|
+
if (alias !== undefined && (typeof alias !== "string" || alias.length !== 1))
|
|
54
|
+
throw new Error(`hanzio/cli: alias ${JSON.stringify(alias)} of "${key}" must be one character`);
|
|
55
|
+
return {
|
|
56
|
+
key,
|
|
57
|
+
flag: kebab(key),
|
|
58
|
+
kind: def.type === "boolean" ? "boolean" : def.type === "number" ? "number" : "string",
|
|
59
|
+
multiple,
|
|
60
|
+
positional: meta.positional === true,
|
|
61
|
+
optional,
|
|
62
|
+
hasDefault,
|
|
63
|
+
defaultValue,
|
|
64
|
+
description: typeof meta.description === "string" ? meta.description : undefined,
|
|
65
|
+
alias,
|
|
66
|
+
env: typeof meta.env === "string" ? meta.env : undefined,
|
|
67
|
+
choices: def.type === "enum" ? Object.values(def.entries).map(String) : def.type === "literal" ? def.values.map(String) : undefined
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function coerce(field, value) {
|
|
71
|
+
if (typeof value !== "string")
|
|
72
|
+
return value;
|
|
73
|
+
if (field.kind === "number" && value.trim() !== "") {
|
|
74
|
+
const number = Number(value);
|
|
75
|
+
return Number.isNaN(number) ? value : number;
|
|
76
|
+
}
|
|
77
|
+
if (field.kind === "boolean") {
|
|
78
|
+
if (value === "true" || value === "1")
|
|
79
|
+
return true;
|
|
80
|
+
if (value === "false" || value === "0" || value === "")
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
function readFields(fields, values, positionals, env) {
|
|
86
|
+
const result = {};
|
|
87
|
+
const queue = [...positionals];
|
|
88
|
+
for (const field of fields) {
|
|
89
|
+
let raw;
|
|
90
|
+
if (field.positional)
|
|
91
|
+
raw = field.multiple ? queue.length ? queue.splice(0) : undefined : queue.shift();
|
|
92
|
+
else
|
|
93
|
+
raw = values[field.flag];
|
|
94
|
+
if (raw === undefined && field.env && env[field.env] !== undefined) {
|
|
95
|
+
const value = env[field.env];
|
|
96
|
+
raw = field.multiple ? [value] : value;
|
|
97
|
+
}
|
|
98
|
+
if (raw === undefined) {
|
|
99
|
+
if (field.optional || field.hasDefault)
|
|
100
|
+
continue;
|
|
101
|
+
if (field.multiple)
|
|
102
|
+
raw = [];
|
|
103
|
+
else if (field.kind === "boolean")
|
|
104
|
+
raw = false;
|
|
105
|
+
else
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
result[field.key] = Array.isArray(raw) ? raw.map((item) => coerce(field, item)) : coerce(field, raw);
|
|
109
|
+
}
|
|
110
|
+
if (queue.length)
|
|
111
|
+
throw new UsageError(`Unexpected argument "${queue[0]}"`);
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
function fieldLabel(field) {
|
|
115
|
+
if (field.positional)
|
|
116
|
+
return field.multiple ? `<${field.flag}...>` : field.optional || field.hasDefault ? `[${field.flag}]` : `<${field.flag}>`;
|
|
117
|
+
const name = field.kind === "boolean" && field.defaultValue === true ? `--[no-]${field.flag}` : `--${field.flag}`;
|
|
118
|
+
const value = field.kind === "boolean" ? "" : ` <${field.choices?.join("|") ?? field.kind}>${field.multiple ? "..." : ""}`;
|
|
119
|
+
return `${field.alias ? `-${field.alias}, ` : ""}${name}${value}`;
|
|
120
|
+
}
|
|
121
|
+
function fieldHelp(field) {
|
|
122
|
+
const showDefault = field.hasDefault && field.defaultValue !== undefined && !(field.kind === "boolean" && field.defaultValue === false);
|
|
123
|
+
return [
|
|
124
|
+
field.description,
|
|
125
|
+
showDefault && `(default: ${JSON.stringify(field.defaultValue)})`,
|
|
126
|
+
field.env && `[env: ${field.env}]`
|
|
127
|
+
].filter(Boolean).join(" ");
|
|
128
|
+
}
|
|
129
|
+
function closest(input, candidates) {
|
|
130
|
+
let best;
|
|
131
|
+
let bestDistance = Math.max(2, Math.floor(input.length / 3)) + 1;
|
|
132
|
+
for (const candidate of candidates) {
|
|
133
|
+
const distance = candidate.startsWith(input) ? 1 : levenshtein(input, candidate);
|
|
134
|
+
if (distance < bestDistance) {
|
|
135
|
+
best = candidate;
|
|
136
|
+
bestDistance = distance;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return best;
|
|
140
|
+
}
|
|
141
|
+
function levenshtein(a, b) {
|
|
142
|
+
let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
143
|
+
for (let i = 1;i <= a.length; i++) {
|
|
144
|
+
const current = [i];
|
|
145
|
+
for (let j = 1;j <= b.length; j++)
|
|
146
|
+
current[j] = Math.min((previous[j] ?? 0) + 1, (current[j - 1] ?? 0) + 1, (previous[j - 1] ?? 0) + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
147
|
+
previous = current;
|
|
148
|
+
}
|
|
149
|
+
return previous[b.length] ?? 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/cli/index.ts
|
|
153
|
+
var commandKey = Symbol.for("hanzio/cli/command");
|
|
154
|
+
|
|
155
|
+
class CliError2 extends Error {
|
|
156
|
+
exitCode;
|
|
157
|
+
constructor(message, options) {
|
|
158
|
+
super(message, options?.cause === undefined ? undefined : { cause: options.cause });
|
|
159
|
+
this.name = "CliError";
|
|
160
|
+
this.exitCode = options?.exitCode ?? 1;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function defineCommand2(definition) {
|
|
164
|
+
return { ...definition, [commandKey]: true };
|
|
165
|
+
}
|
|
166
|
+
var isCommand = (node) => typeof node === "object" && node !== null && (commandKey in node);
|
|
167
|
+
async function loadCommand(node) {
|
|
168
|
+
if (typeof node !== "function" && !node.load)
|
|
169
|
+
return node;
|
|
170
|
+
const loaded = await (typeof node === "function" ? node() : node.load?.());
|
|
171
|
+
const command = isCommand(loaded) ? loaded : loaded?.default;
|
|
172
|
+
if (!isCommand(command))
|
|
173
|
+
throw new Error("hanzio/cli: a lazy command must resolve to defineCommand() or a module that default-exports one");
|
|
174
|
+
return loadCommand(command);
|
|
175
|
+
}
|
|
176
|
+
function listCommands(group, prefix = []) {
|
|
177
|
+
return Object.entries(group).flatMap(([name, node]) => typeof node === "function" || isCommand(node) ? [
|
|
178
|
+
[
|
|
179
|
+
[...prefix, name],
|
|
180
|
+
typeof node === "function" ? undefined : node.description
|
|
181
|
+
]
|
|
182
|
+
] : listCommands(node, [...prefix, name]));
|
|
183
|
+
}
|
|
184
|
+
function table(rows) {
|
|
185
|
+
const width = Math.max(...rows.map(([left]) => left.length));
|
|
186
|
+
return rows.map(([left, right]) => ` ${left.padEnd(width)} ${right ?? ""}`.trimEnd()).join(`
|
|
187
|
+
`);
|
|
188
|
+
}
|
|
189
|
+
var optionRows = (fields) => fields.map((field) => [fieldLabel(field), fieldHelp(field)]);
|
|
190
|
+
var helpRow = ["-h, --help", "Show help"];
|
|
191
|
+
function defineCli2(config) {
|
|
192
|
+
const root = config.commands;
|
|
193
|
+
const builtIn = (group) => group === root && !Object.hasOwn(group, "completion");
|
|
194
|
+
const versionRow = config.version ? [["--version", "Show version"]] : [];
|
|
195
|
+
function groupHelp(group, path, globals) {
|
|
196
|
+
const usage = [config.name, ...path].join(" ");
|
|
197
|
+
const commands = listCommands(group).map(([names, description]) => [
|
|
198
|
+
names.join(" "),
|
|
199
|
+
description
|
|
200
|
+
]);
|
|
201
|
+
if (builtIn(group))
|
|
202
|
+
commands.push([
|
|
203
|
+
"completion <bash|zsh>",
|
|
204
|
+
"Print a shell completion script"
|
|
205
|
+
]);
|
|
206
|
+
return [
|
|
207
|
+
...path.length ? [] : [
|
|
208
|
+
[config.name, config.version].filter(Boolean).join(" "),
|
|
209
|
+
...config.description ? [config.description] : [],
|
|
210
|
+
""
|
|
211
|
+
],
|
|
212
|
+
`Usage: ${usage} <command> [options]`,
|
|
213
|
+
"",
|
|
214
|
+
"Commands:",
|
|
215
|
+
table(commands),
|
|
216
|
+
"",
|
|
217
|
+
"Options:",
|
|
218
|
+
table([
|
|
219
|
+
...optionRows(globals),
|
|
220
|
+
helpRow,
|
|
221
|
+
...path.length ? [] : versionRow
|
|
222
|
+
]),
|
|
223
|
+
"",
|
|
224
|
+
`Run "${usage} <command> --help" for a command's options.`
|
|
225
|
+
].join(`
|
|
226
|
+
`);
|
|
227
|
+
}
|
|
228
|
+
function commandHelp(command, path, fields, globals) {
|
|
229
|
+
const positionals = fields.filter((field) => field.positional);
|
|
230
|
+
const options = fields.filter((field) => !field.positional);
|
|
231
|
+
const sections = [
|
|
232
|
+
`Usage: ${[config.name, ...path, ...positionals.map(fieldLabel)].join(" ")} [options]`
|
|
233
|
+
];
|
|
234
|
+
const description = command.description ?? config.description;
|
|
235
|
+
if (description)
|
|
236
|
+
sections.push(description);
|
|
237
|
+
if (positionals.length)
|
|
238
|
+
sections.push(`Arguments:
|
|
239
|
+
${table(optionRows(positionals))}`);
|
|
240
|
+
sections.push(`Options:
|
|
241
|
+
${table([
|
|
242
|
+
...optionRows(options),
|
|
243
|
+
helpRow,
|
|
244
|
+
...path.length ? [] : versionRow
|
|
245
|
+
])}`);
|
|
246
|
+
if (globals.length)
|
|
247
|
+
sections.push(`Global options:
|
|
248
|
+
${table(optionRows(globals))}`);
|
|
249
|
+
return sections.join(`
|
|
250
|
+
|
|
251
|
+
`);
|
|
252
|
+
}
|
|
253
|
+
async function completion(shell, globals) {
|
|
254
|
+
const fn = `_${config.name.replace(/\W/g, "_")}_completion`;
|
|
255
|
+
const flags = (fields) => [...fields, ...globals].filter((field) => !field.positional).map((field) => `--${field.flag}`);
|
|
256
|
+
const cases = [];
|
|
257
|
+
const visit = async (node, path) => {
|
|
258
|
+
if (typeof node === "function" || isCommand(node)) {
|
|
259
|
+
const command = await loadCommand(node);
|
|
260
|
+
cases.push([
|
|
261
|
+
path.join(" "),
|
|
262
|
+
command.raw ? [] : [...flags(fieldsOf(command.input)), "--help"]
|
|
263
|
+
]);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
for (const [name, child] of Object.entries(node))
|
|
267
|
+
await visit(child, [...path, name]);
|
|
268
|
+
const names = Object.keys(node);
|
|
269
|
+
if (builtIn(node))
|
|
270
|
+
names.push("completion");
|
|
271
|
+
cases.push([path.join(" "), [...names, ...flags([]), "--help"]]);
|
|
272
|
+
};
|
|
273
|
+
await visit(root, []);
|
|
274
|
+
cases.sort(([a], [b]) => b.length - a.length);
|
|
275
|
+
const branches = cases.map(([path, words]) => path ? ` "${path}"|"${path} "*) opts="${words.join(" ")}" ;;` : ` *) opts="${words.join(" ")}" ;;`);
|
|
276
|
+
const script = `${fn}() {
|
|
277
|
+
local cur="\${COMP_WORDS[COMP_CWORD]}" words="" word opts
|
|
278
|
+
for word in "\${COMP_WORDS[@]:1:COMP_CWORD-1}"; do
|
|
279
|
+
[[ $word == -* ]] || words="$words $word"
|
|
280
|
+
done
|
|
281
|
+
case "\${words# }" in
|
|
282
|
+
${branches.join(`
|
|
283
|
+
`)}
|
|
284
|
+
esac
|
|
285
|
+
COMPREPLY=($(compgen -W "$opts" -- "$cur"))
|
|
286
|
+
}
|
|
287
|
+
complete -F ${fn} ${config.name}
|
|
288
|
+
`;
|
|
289
|
+
if (shell === "bash")
|
|
290
|
+
return script;
|
|
291
|
+
if (shell === "zsh")
|
|
292
|
+
return `autoload -U +X bashcompinit && bashcompinit
|
|
293
|
+
${script}`;
|
|
294
|
+
throw new UsageError(`Unknown shell "${shell}": use bash or zsh`);
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
name: config.name,
|
|
298
|
+
async run(argv = process.argv.slice(2), io = {}) {
|
|
299
|
+
const stdout = io.stdout ?? process.stdout;
|
|
300
|
+
const stderr = io.stderr ?? process.stderr;
|
|
301
|
+
const env = io.env ?? process.env;
|
|
302
|
+
let usage = config.name;
|
|
303
|
+
let json = false;
|
|
304
|
+
try {
|
|
305
|
+
const globalFields = fieldsOf(config.globals);
|
|
306
|
+
const positional = globalFields.find((field) => field.positional);
|
|
307
|
+
if (positional)
|
|
308
|
+
throw new Error(`hanzio/cli: global "${positional.key}" can't be positional`);
|
|
309
|
+
const valueFlags = new Set(globalFields.filter((field) => field.kind !== "boolean").flatMap((field) => [
|
|
310
|
+
`--${field.flag}`,
|
|
311
|
+
...field.alias ? [`-${field.alias}`] : []
|
|
312
|
+
]));
|
|
313
|
+
const isHelp = (token) => token === "--help" || token === "-h";
|
|
314
|
+
let node = root;
|
|
315
|
+
const path = [];
|
|
316
|
+
const pre = [];
|
|
317
|
+
let index = 0;
|
|
318
|
+
while (typeof node !== "function" && !isCommand(node)) {
|
|
319
|
+
const group = node;
|
|
320
|
+
const token = argv[index];
|
|
321
|
+
if (token === undefined || token === "--") {
|
|
322
|
+
if (pre.some(isHelp)) {
|
|
323
|
+
stdout.write(`${groupHelp(group, path, globalFields)}
|
|
324
|
+
`);
|
|
325
|
+
return 0;
|
|
326
|
+
}
|
|
327
|
+
if (!path.length && config.version && pre.includes("--version")) {
|
|
328
|
+
stdout.write(`${config.version}
|
|
329
|
+
`);
|
|
330
|
+
return 0;
|
|
331
|
+
}
|
|
332
|
+
throw new UsageError(`${path.length ? `${usage}: missing` : "Missing"} command
|
|
333
|
+
|
|
334
|
+
${groupHelp(group, path, globalFields)}`);
|
|
335
|
+
}
|
|
336
|
+
index++;
|
|
337
|
+
if (token.startsWith("-") && token !== "-") {
|
|
338
|
+
pre.push(token);
|
|
339
|
+
if (valueFlags.has(token) && argv[index] !== undefined)
|
|
340
|
+
pre.push(argv[index++]);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (token === "completion" && builtIn(group)) {
|
|
344
|
+
const [shell = "bash"] = argv.slice(index);
|
|
345
|
+
stdout.write(await completion(shell, globalFields));
|
|
346
|
+
return 0;
|
|
347
|
+
}
|
|
348
|
+
const child = Object.hasOwn(group, token) ? group[token] : undefined;
|
|
349
|
+
if (!child) {
|
|
350
|
+
const names = Object.keys(group);
|
|
351
|
+
const guess = closest(token, names);
|
|
352
|
+
throw new UsageError(`Unknown command "${[...path, token].join(" ")}".${guess ? ` Did you mean "${[...path, guess].join(" ")}"?` : ` Commands: ${names.join(", ")}`}`);
|
|
353
|
+
}
|
|
354
|
+
node = child;
|
|
355
|
+
path.push(token);
|
|
356
|
+
usage = `${usage} ${token}`;
|
|
357
|
+
}
|
|
358
|
+
const command = await loadCommand(node);
|
|
359
|
+
const rest = argv.slice(index);
|
|
360
|
+
const fields = command.raw ? [] : fieldsOf(command.input);
|
|
361
|
+
const dash = rest.indexOf("--");
|
|
362
|
+
const tokens = command.raw ? pre : [...pre, ...dash < 0 ? rest : rest.slice(0, dash)];
|
|
363
|
+
if (!command.raw && tokens.some(isHelp)) {
|
|
364
|
+
stdout.write(`${commandHelp(command, path, fields, globalFields)}
|
|
365
|
+
`);
|
|
366
|
+
return 0;
|
|
367
|
+
}
|
|
368
|
+
if (!path.length && config.version && tokens.includes("--version")) {
|
|
369
|
+
stdout.write(`${config.version}
|
|
370
|
+
`);
|
|
371
|
+
return 0;
|
|
372
|
+
}
|
|
373
|
+
const options = {};
|
|
374
|
+
for (const field of [...globalFields, ...fields]) {
|
|
375
|
+
if (field.positional)
|
|
376
|
+
continue;
|
|
377
|
+
if (field.flag === "help" || Object.hasOwn(options, field.flag))
|
|
378
|
+
throw new Error(`hanzio/cli: --${field.flag} is declared twice`);
|
|
379
|
+
options[field.flag] = {
|
|
380
|
+
type: field.kind === "boolean" ? "boolean" : "string",
|
|
381
|
+
multiple: field.multiple,
|
|
382
|
+
...field.alias ? { short: field.alias } : {}
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
let parsed;
|
|
386
|
+
try {
|
|
387
|
+
parsed = parseArgs({
|
|
388
|
+
args: tokens,
|
|
389
|
+
options,
|
|
390
|
+
allowPositionals: true,
|
|
391
|
+
allowNegative: true,
|
|
392
|
+
strict: true
|
|
393
|
+
});
|
|
394
|
+
} catch (error) {
|
|
395
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
396
|
+
const unknown = /^Unknown option '(-[^']*)'/.exec(message)?.[1];
|
|
397
|
+
if (!unknown)
|
|
398
|
+
throw new UsageError(message.split(". ")[0] ?? message);
|
|
399
|
+
const guess = closest(unknown.replace(/^--?/, ""), Object.keys(options));
|
|
400
|
+
throw new UsageError(`Unknown option ${unknown}.${guess ? ` Did you mean --${guess}?` : ""}`);
|
|
401
|
+
}
|
|
402
|
+
const issues = [];
|
|
403
|
+
const validate = async (schema, fields, positionals) => {
|
|
404
|
+
const raw = readFields(fields, parsed.values, positionals, env);
|
|
405
|
+
if (!schema)
|
|
406
|
+
return raw;
|
|
407
|
+
const result = await schema.safeParseAsync(raw);
|
|
408
|
+
if (result.success)
|
|
409
|
+
return result.data;
|
|
410
|
+
for (const issue of result.error.issues) {
|
|
411
|
+
const field = fields.find((field) => field.key === issue.path[0]);
|
|
412
|
+
const label = field ? field.positional ? `<${field.flag}>` : `--${field.flag}` : issue.path.join(".");
|
|
413
|
+
issues.push(label ? `${label}: ${issue.message}` : issue.message);
|
|
414
|
+
}
|
|
415
|
+
return raw;
|
|
416
|
+
};
|
|
417
|
+
const globals = await validate(config.globals, globalFields, []);
|
|
418
|
+
const input = await validate(command.raw ? undefined : command.input, fields, parsed.positionals);
|
|
419
|
+
if (issues.length)
|
|
420
|
+
throw new UsageError(issues.join(`
|
|
421
|
+
`));
|
|
422
|
+
json = input.json === true || globals.json === true;
|
|
423
|
+
const context = await config.context?.(globals);
|
|
424
|
+
await config.guard?.({
|
|
425
|
+
path,
|
|
426
|
+
input,
|
|
427
|
+
globals,
|
|
428
|
+
context
|
|
429
|
+
});
|
|
430
|
+
const ctx = {
|
|
431
|
+
context,
|
|
432
|
+
globals,
|
|
433
|
+
path,
|
|
434
|
+
argv: rest,
|
|
435
|
+
passthrough: dash < 0 ? [] : rest.slice(dash + 1),
|
|
436
|
+
json,
|
|
437
|
+
stdout,
|
|
438
|
+
stderr,
|
|
439
|
+
env,
|
|
440
|
+
exitCode: 0
|
|
441
|
+
};
|
|
442
|
+
const run = command.run;
|
|
443
|
+
const result = await run(input, ctx);
|
|
444
|
+
const output = command.output ?? printResult;
|
|
445
|
+
await output(result, ctx);
|
|
446
|
+
return ctx.exitCode;
|
|
447
|
+
} catch (error) {
|
|
448
|
+
if (error instanceof UsageError) {
|
|
449
|
+
stderr.write(`${error.message}
|
|
450
|
+
Run "${usage} --help" for usage.
|
|
451
|
+
`);
|
|
452
|
+
return 2;
|
|
453
|
+
}
|
|
454
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
455
|
+
if (json)
|
|
456
|
+
stderr.write(`${JSON.stringify({ error: message })}
|
|
457
|
+
`);
|
|
458
|
+
else
|
|
459
|
+
stderr.write(`${error instanceof Error && !(error instanceof CliError2) ? error.stack ?? message : message}
|
|
460
|
+
`);
|
|
461
|
+
return error instanceof CliError2 ? error.exitCode : 1;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function printResult(result, ctx) {
|
|
467
|
+
if (result === undefined)
|
|
468
|
+
return;
|
|
469
|
+
ctx.stdout.write(`${ctx.json ? JSON.stringify(result) : typeof result === "string" ? result : JSON.stringify(result, null, 2)}
|
|
470
|
+
`);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export { CliError2, defineCommand2, defineCli2 };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// src/storage/index.ts
|
|
2
|
+
function createMemoryStorage2() {
|
|
3
|
+
const map = new Map;
|
|
4
|
+
return {
|
|
5
|
+
getItem: (key) => map.get(key) ?? null,
|
|
6
|
+
setItem: (key, value) => {
|
|
7
|
+
map.set(key, String(value));
|
|
8
|
+
},
|
|
9
|
+
removeItem: (key) => {
|
|
10
|
+
map.delete(key);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
var sharedMemoryStorage;
|
|
15
|
+
function defaultStorage() {
|
|
16
|
+
try {
|
|
17
|
+
const storage = globalThis.localStorage;
|
|
18
|
+
if (storage)
|
|
19
|
+
return storage;
|
|
20
|
+
} catch {}
|
|
21
|
+
sharedMemoryStorage ??= createMemoryStorage2();
|
|
22
|
+
return sharedMemoryStorage;
|
|
23
|
+
}
|
|
24
|
+
function createCodec(options) {
|
|
25
|
+
const {
|
|
26
|
+
fallback,
|
|
27
|
+
parse = (raw) => raw,
|
|
28
|
+
serialize = JSON.stringify,
|
|
29
|
+
deserialize = JSON.parse
|
|
30
|
+
} = options;
|
|
31
|
+
return {
|
|
32
|
+
serialize,
|
|
33
|
+
decode(raw) {
|
|
34
|
+
if (raw === null)
|
|
35
|
+
return fallback;
|
|
36
|
+
try {
|
|
37
|
+
return parse(deserialize(raw));
|
|
38
|
+
} catch {
|
|
39
|
+
return fallback;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function createStorageItem2(options) {
|
|
45
|
+
const { key, fallback, storage = defaultStorage() } = options;
|
|
46
|
+
const { serialize, decode } = createCodec(options);
|
|
47
|
+
let mirror;
|
|
48
|
+
const write = (value, action) => {
|
|
49
|
+
try {
|
|
50
|
+
action();
|
|
51
|
+
mirror = undefined;
|
|
52
|
+
} catch {
|
|
53
|
+
mirror = { value };
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const get = () => {
|
|
57
|
+
if (mirror)
|
|
58
|
+
return mirror.value;
|
|
59
|
+
try {
|
|
60
|
+
return decode(storage.getItem(key));
|
|
61
|
+
} catch {
|
|
62
|
+
return fallback;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
key,
|
|
67
|
+
get,
|
|
68
|
+
set: (value) => write(value, () => storage.setItem(key, serialize(value))),
|
|
69
|
+
remove: () => write(fallback, () => storage.removeItem(key)),
|
|
70
|
+
subscribe(listener) {
|
|
71
|
+
const target = globalThis;
|
|
72
|
+
if (!target.addEventListener || !target.removeEventListener) {
|
|
73
|
+
return () => {};
|
|
74
|
+
}
|
|
75
|
+
const onStorage = (event) => {
|
|
76
|
+
if (event.storageArea !== storage)
|
|
77
|
+
return;
|
|
78
|
+
if (event.key !== key && event.key !== null)
|
|
79
|
+
return;
|
|
80
|
+
mirror = undefined;
|
|
81
|
+
listener(get());
|
|
82
|
+
};
|
|
83
|
+
target.addEventListener("storage", onStorage);
|
|
84
|
+
return () => target.removeEventListener?.("storage", onStorage);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function createAsyncStorageItem2(options) {
|
|
89
|
+
const { key, fallback, storage } = options;
|
|
90
|
+
const { serialize, decode } = createCodec(options);
|
|
91
|
+
let mirror;
|
|
92
|
+
const write = async (value, action) => {
|
|
93
|
+
try {
|
|
94
|
+
await action();
|
|
95
|
+
mirror = undefined;
|
|
96
|
+
} catch {
|
|
97
|
+
mirror = { value };
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
key,
|
|
102
|
+
async get() {
|
|
103
|
+
if (mirror)
|
|
104
|
+
return mirror.value;
|
|
105
|
+
try {
|
|
106
|
+
return decode(await storage.getItem(key));
|
|
107
|
+
} catch {
|
|
108
|
+
return fallback;
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
set: (value) => write(value, () => storage.setItem(key, serialize(value))),
|
|
112
|
+
remove: () => write(fallback, () => storage.removeItem(key))
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export { createMemoryStorage2, createStorageItem2, createAsyncStorageItem2 };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// src/i18n/locales.ts
|
|
2
|
+
var normalizeTag = (tag) => tag.trim().replaceAll("_", "-").toLowerCase();
|
|
3
|
+
function defineLocales2(options) {
|
|
4
|
+
const { locales: info, defaultLocale, aliases = {} } = options;
|
|
5
|
+
const locales = Object.keys(info);
|
|
6
|
+
const lookup = new Map;
|
|
7
|
+
for (const locale of locales)
|
|
8
|
+
lookup.set(normalizeTag(locale), locale);
|
|
9
|
+
for (const locale of locales) {
|
|
10
|
+
const tag = normalizeTag(info[locale].tag);
|
|
11
|
+
if (!lookup.has(tag))
|
|
12
|
+
lookup.set(tag, locale);
|
|
13
|
+
}
|
|
14
|
+
const aliasLookup = new Map;
|
|
15
|
+
for (const [alias, locale] of Object.entries(aliases)) {
|
|
16
|
+
if (!Object.hasOwn(info, locale)) {
|
|
17
|
+
throw new Error(`Alias "${alias}" points at unknown locale "${locale}"`);
|
|
18
|
+
}
|
|
19
|
+
aliasLookup.set(normalizeTag(alias), locale);
|
|
20
|
+
}
|
|
21
|
+
if (!Object.hasOwn(info, defaultLocale)) {
|
|
22
|
+
throw new Error(`Default locale "${defaultLocale}" is not in locales`);
|
|
23
|
+
}
|
|
24
|
+
const match = (tag) => {
|
|
25
|
+
if (!tag)
|
|
26
|
+
return null;
|
|
27
|
+
const full = normalizeTag(tag);
|
|
28
|
+
const language = full.split("-")[0] ?? "";
|
|
29
|
+
return lookup.get(full) ?? aliasLookup.get(full) ?? lookup.get(language) ?? aliasLookup.get(language) ?? null;
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
locales,
|
|
33
|
+
defaultLocale,
|
|
34
|
+
tag: (locale) => info[locale].tag,
|
|
35
|
+
name: (locale) => info[locale].name,
|
|
36
|
+
match,
|
|
37
|
+
resolve: (...candidates) => {
|
|
38
|
+
for (const candidate of candidates) {
|
|
39
|
+
const locale = match(candidate);
|
|
40
|
+
if (locale)
|
|
41
|
+
return locale;
|
|
42
|
+
}
|
|
43
|
+
return defaultLocale;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function createLocaleSelector2(options) {
|
|
48
|
+
const { getLocale, setLocale, persist, onError } = options;
|
|
49
|
+
return async (next) => {
|
|
50
|
+
const previous = getLocale();
|
|
51
|
+
if (previous === next)
|
|
52
|
+
return;
|
|
53
|
+
await setLocale(next);
|
|
54
|
+
if (!persist)
|
|
55
|
+
return;
|
|
56
|
+
try {
|
|
57
|
+
await persist(next);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
await setLocale(previous);
|
|
60
|
+
onError?.(error);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/i18n/index.ts
|
|
66
|
+
var PLACEHOLDER_PATTERN = /\{(\w+)\}/g;
|
|
67
|
+
function extractPlaceholders2(template) {
|
|
68
|
+
const names = template.matchAll(PLACEHOLDER_PATTERN).map((match) => match[1]);
|
|
69
|
+
return [...new Set(names)];
|
|
70
|
+
}
|
|
71
|
+
function interpolate2(template, ...args) {
|
|
72
|
+
const params = args[0] ?? {};
|
|
73
|
+
return template.replace(PLACEHOLDER_PATTERN, (match, name) => {
|
|
74
|
+
const value = Object.hasOwn(params, name) ? params[name] : undefined;
|
|
75
|
+
return value == null ? match : String(value);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function flattenMessages2(messages) {
|
|
79
|
+
const out = {};
|
|
80
|
+
const visit = (node, prefix) => {
|
|
81
|
+
for (const [key, value] of Object.entries(node)) {
|
|
82
|
+
if (typeof value === "string")
|
|
83
|
+
out[prefix + key] = value;
|
|
84
|
+
else
|
|
85
|
+
visit(value, `${prefix + key}.`);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
visit(messages, "");
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
function createTranslator2(messages) {
|
|
92
|
+
const flat = flattenMessages2(messages);
|
|
93
|
+
return (key, ...args) => interpolate2(flat[key] ?? key, ...args);
|
|
94
|
+
}
|
|
95
|
+
function typedTranslate2(t) {
|
|
96
|
+
return t;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { defineLocales2, createLocaleSelector2, extractPlaceholders2, interpolate2, flattenMessages2, createTranslator2, typedTranslate2 };
|