hanzio 3.0.0 → 3.1.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/README.md +114 -3
- 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-mq2qgc20.js +116 -0
- package/dist/index-najbyzpr.js +99 -0
- package/dist/index-nfvq3zax.js +475 -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 +14 -4
- 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/zod/index.d.ts +10 -0
- package/dist/zod/introspect.d.ts +2 -1
- package/package.json +96 -2
- package/src/api-wrapper/README.md +52 -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 +533 -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/zod/index.ts +20 -0
- package/src/zod/introspect.ts +1 -1
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { unwrapOnce } from '../zod/introspect'
|
|
3
|
+
import { defOf, type Schema } from '../zod/zodToTypeString'
|
|
4
|
+
|
|
5
|
+
/** Thrown for bad argv. The CLI prints it with a `--help` hint and exits 2. */
|
|
6
|
+
export class UsageError extends Error {}
|
|
7
|
+
|
|
8
|
+
/** One key of an input or globals object, read as a flag or a positional. */
|
|
9
|
+
export type Field = {
|
|
10
|
+
key: string
|
|
11
|
+
flag: string
|
|
12
|
+
kind: 'string' | 'number' | 'boolean'
|
|
13
|
+
multiple: boolean
|
|
14
|
+
positional: boolean
|
|
15
|
+
optional: boolean
|
|
16
|
+
hasDefault: boolean
|
|
17
|
+
defaultValue: unknown
|
|
18
|
+
description?: string
|
|
19
|
+
alias?: string
|
|
20
|
+
env?: string
|
|
21
|
+
choices?: string[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const kebab = (key: string) =>
|
|
25
|
+
key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)
|
|
26
|
+
|
|
27
|
+
export function fieldsOf(schema: z.ZodObject | undefined): Field[] {
|
|
28
|
+
if (!schema) return []
|
|
29
|
+
return Object.entries(schema.shape).map(([key, child]) => toField(key, child))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function toField(key: string, schema: Schema): Field {
|
|
33
|
+
// Metadata can sit on any wrapper; the outermost `.meta()`/`.describe()` wins.
|
|
34
|
+
const metas: Record<string, unknown>[] = []
|
|
35
|
+
let optional = false
|
|
36
|
+
let hasDefault = false
|
|
37
|
+
let defaultValue: unknown
|
|
38
|
+
let current = schema
|
|
39
|
+
for (;;) {
|
|
40
|
+
const meta = z.globalRegistry.get(current)
|
|
41
|
+
if (meta) metas.unshift(meta)
|
|
42
|
+
const def = defOf(current)
|
|
43
|
+
if (def.type === 'optional') optional = true
|
|
44
|
+
if ((def.type === 'default' || def.type === 'prefault') && !hasDefault) {
|
|
45
|
+
hasDefault = true
|
|
46
|
+
defaultValue = def.defaultValue
|
|
47
|
+
}
|
|
48
|
+
const inner = unwrapOnce(def)
|
|
49
|
+
if (!inner) break
|
|
50
|
+
current = inner
|
|
51
|
+
}
|
|
52
|
+
const meta: Record<string, unknown> = Object.assign({}, ...metas)
|
|
53
|
+
|
|
54
|
+
let def = defOf(current)
|
|
55
|
+
const multiple = def.type === 'array'
|
|
56
|
+
if (def.type === 'array') {
|
|
57
|
+
let element = def.element
|
|
58
|
+
for (let inner = unwrapOnce(defOf(element)); inner; ) {
|
|
59
|
+
element = inner
|
|
60
|
+
inner = unwrapOnce(defOf(element))
|
|
61
|
+
}
|
|
62
|
+
def = defOf(element)
|
|
63
|
+
}
|
|
64
|
+
const alias = meta.alias
|
|
65
|
+
if (alias !== undefined && (typeof alias !== 'string' || alias.length !== 1))
|
|
66
|
+
throw new Error(
|
|
67
|
+
`hanzio/cli: alias ${JSON.stringify(alias)} of "${key}" must be one character`
|
|
68
|
+
)
|
|
69
|
+
return {
|
|
70
|
+
key,
|
|
71
|
+
flag: kebab(key),
|
|
72
|
+
kind:
|
|
73
|
+
def.type === 'boolean'
|
|
74
|
+
? 'boolean'
|
|
75
|
+
: def.type === 'number'
|
|
76
|
+
? 'number'
|
|
77
|
+
: 'string',
|
|
78
|
+
multiple,
|
|
79
|
+
positional: meta.positional === true,
|
|
80
|
+
optional,
|
|
81
|
+
hasDefault,
|
|
82
|
+
defaultValue,
|
|
83
|
+
description:
|
|
84
|
+
typeof meta.description === 'string' ? meta.description : undefined,
|
|
85
|
+
alias,
|
|
86
|
+
env: typeof meta.env === 'string' ? meta.env : undefined,
|
|
87
|
+
choices:
|
|
88
|
+
def.type === 'enum'
|
|
89
|
+
? Object.values(def.entries).map(String)
|
|
90
|
+
: def.type === 'literal'
|
|
91
|
+
? def.values.map(String)
|
|
92
|
+
: undefined
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function coerce(field: Field, value: string | boolean): unknown {
|
|
97
|
+
if (typeof value !== 'string') return value
|
|
98
|
+
if (field.kind === 'number' && value.trim() !== '') {
|
|
99
|
+
const number = Number(value)
|
|
100
|
+
return Number.isNaN(number) ? value : number
|
|
101
|
+
}
|
|
102
|
+
if (field.kind === 'boolean') {
|
|
103
|
+
if (value === 'true' || value === '1') return true
|
|
104
|
+
if (value === 'false' || value === '0' || value === '') return false
|
|
105
|
+
}
|
|
106
|
+
return value
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Builds the raw object Zod validates: flag values and positionals by key,
|
|
111
|
+
* numbers coerced, `.meta({ env })` as fallback, and a missing required
|
|
112
|
+
* boolean or array read as `false` or `[]`.
|
|
113
|
+
*/
|
|
114
|
+
export function readFields(
|
|
115
|
+
fields: Field[],
|
|
116
|
+
values: Record<string, string | boolean | (string | boolean)[] | undefined>,
|
|
117
|
+
positionals: string[],
|
|
118
|
+
env: Record<string, string | undefined>
|
|
119
|
+
): Record<string, unknown> {
|
|
120
|
+
const result: Record<string, unknown> = {}
|
|
121
|
+
const queue = [...positionals]
|
|
122
|
+
for (const field of fields) {
|
|
123
|
+
let raw: string | boolean | (string | boolean)[] | undefined
|
|
124
|
+
if (field.positional)
|
|
125
|
+
raw = field.multiple
|
|
126
|
+
? queue.length
|
|
127
|
+
? queue.splice(0)
|
|
128
|
+
: undefined
|
|
129
|
+
: queue.shift()
|
|
130
|
+
else raw = values[field.flag]
|
|
131
|
+
if (raw === undefined && field.env && env[field.env] !== undefined) {
|
|
132
|
+
const value = env[field.env] as string
|
|
133
|
+
raw = field.multiple ? [value] : value
|
|
134
|
+
}
|
|
135
|
+
if (raw === undefined) {
|
|
136
|
+
if (field.optional || field.hasDefault) continue
|
|
137
|
+
if (field.multiple) raw = []
|
|
138
|
+
else if (field.kind === 'boolean') raw = false
|
|
139
|
+
else continue
|
|
140
|
+
}
|
|
141
|
+
result[field.key] = Array.isArray(raw)
|
|
142
|
+
? raw.map((item) => coerce(field, item))
|
|
143
|
+
: coerce(field, raw)
|
|
144
|
+
}
|
|
145
|
+
if (queue.length) throw new UsageError(`Unexpected argument "${queue[0]}"`)
|
|
146
|
+
return result
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function fieldLabel(field: Field): string {
|
|
150
|
+
if (field.positional)
|
|
151
|
+
return field.multiple
|
|
152
|
+
? `<${field.flag}...>`
|
|
153
|
+
: field.optional || field.hasDefault
|
|
154
|
+
? `[${field.flag}]`
|
|
155
|
+
: `<${field.flag}>`
|
|
156
|
+
const name =
|
|
157
|
+
field.kind === 'boolean' && field.defaultValue === true
|
|
158
|
+
? `--[no-]${field.flag}`
|
|
159
|
+
: `--${field.flag}`
|
|
160
|
+
const value =
|
|
161
|
+
field.kind === 'boolean'
|
|
162
|
+
? ''
|
|
163
|
+
: ` <${field.choices?.join('|') ?? field.kind}>${field.multiple ? '...' : ''}`
|
|
164
|
+
return `${field.alias ? `-${field.alias}, ` : ''}${name}${value}`
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function fieldHelp(field: Field): string {
|
|
168
|
+
const showDefault =
|
|
169
|
+
field.hasDefault &&
|
|
170
|
+
field.defaultValue !== undefined &&
|
|
171
|
+
!(field.kind === 'boolean' && field.defaultValue === false)
|
|
172
|
+
return [
|
|
173
|
+
field.description,
|
|
174
|
+
showDefault && `(default: ${JSON.stringify(field.defaultValue)})`,
|
|
175
|
+
field.env && `[env: ${field.env}]`
|
|
176
|
+
]
|
|
177
|
+
.filter(Boolean)
|
|
178
|
+
.join(' ')
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Closest candidate within a few edits, for "Did you mean ...?" hints. */
|
|
182
|
+
export function closest(
|
|
183
|
+
input: string,
|
|
184
|
+
candidates: readonly string[]
|
|
185
|
+
): string | undefined {
|
|
186
|
+
let best: string | undefined
|
|
187
|
+
let bestDistance = Math.max(2, Math.floor(input.length / 3)) + 1
|
|
188
|
+
for (const candidate of candidates) {
|
|
189
|
+
const distance = candidate.startsWith(input)
|
|
190
|
+
? 1
|
|
191
|
+
: levenshtein(input, candidate)
|
|
192
|
+
if (distance < bestDistance) {
|
|
193
|
+
best = candidate
|
|
194
|
+
bestDistance = distance
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return best
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function levenshtein(a: string, b: string): number {
|
|
201
|
+
let previous = Array.from({ length: b.length + 1 }, (_, index) => index)
|
|
202
|
+
for (let i = 1; i <= a.length; i++) {
|
|
203
|
+
const current = [i]
|
|
204
|
+
for (let j = 1; j <= b.length; j++)
|
|
205
|
+
current[j] = Math.min(
|
|
206
|
+
(previous[j] ?? 0) + 1,
|
|
207
|
+
(current[j - 1] ?? 0) + 1,
|
|
208
|
+
(previous[j - 1] ?? 0) + (a[i - 1] === b[j - 1] ? 0 : 1)
|
|
209
|
+
)
|
|
210
|
+
previous = current
|
|
211
|
+
}
|
|
212
|
+
return previous[b.length] ?? 0
|
|
213
|
+
}
|