celery-env 0.1.3 → 0.1.5
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 +1 -1
- package/docs/BENCHMARKS.md +1 -1
- package/docs/CLI.md +13 -7
- package/docs/COMPARISON.md +1 -1
- package/package.json +1 -1
- package/src/cli.js +7 -2
- package/src/infer.js +148 -40
package/README.md
CHANGED
|
@@ -171,7 +171,7 @@ competitors were checked with `npm view` on 2026-06-25.
|
|
|
171
171
|
|
|
172
172
|
| Package | Version checked | Runtime deps | Unpacked npm size | Files |
|
|
173
173
|
| --- | ---: | ---: | ---: | ---: |
|
|
174
|
-
| `celery-env` | 0.1.
|
|
174
|
+
| `celery-env` | 0.1.4 | 0 | 119.2 kB | 26 |
|
|
175
175
|
| `zod` | 4.4.3 | 0 | 4.56 MB | 718 |
|
|
176
176
|
| `valibot` | 1.4.1 | 0 | 1.84 MB | 9 |
|
|
177
177
|
| `envalid` | 8.2.0 | 1 | 88.8 kB | 39 |
|
package/docs/BENCHMARKS.md
CHANGED
|
@@ -73,7 +73,7 @@ Package footprint is separate from runtime speed. Celery is this branch's
|
|
|
73
73
|
|
|
74
74
|
| Package | Version Checked | Runtime Deps | Unpacked npm Size | Files |
|
|
75
75
|
| --- | ---: | ---: | ---: | ---: |
|
|
76
|
-
| `celery-env` | 0.1.
|
|
76
|
+
| `celery-env` | 0.1.4 | 0 | 119.2 kB | 26 |
|
|
77
77
|
| `zod` | 4.4.3 | 0 | 4.56 MB | 718 |
|
|
78
78
|
| `valibot` | 1.4.1 | 0 | 1.84 MB | 9 |
|
|
79
79
|
| `envalid` | 8.2.0 | 1 | 88.8 kB | 39 |
|
package/docs/CLI.md
CHANGED
|
@@ -26,9 +26,9 @@ npx celery-env infer --schema env.schema.mjs
|
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Use `infer` when a project already has `.env` files or source code that reads
|
|
29
|
-
env vars. It discovers `.env.example`, `.env`, `.env.local`,
|
|
30
|
-
directories
|
|
31
|
-
you pass `--force`.
|
|
29
|
+
env vars. It discovers `.env.example`, `.env`, `.env.local`, common source
|
|
30
|
+
directories, common config files, `scripts`, and `prisma` by default. It writes
|
|
31
|
+
a starter schema and refuses overwrite unless you pass `--force`.
|
|
32
32
|
|
|
33
33
|
You can pass sources explicitly:
|
|
34
34
|
|
|
@@ -39,11 +39,17 @@ npx celery-env infer \
|
|
|
39
39
|
--scan src
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Inference is conservative
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
Inference is conservative starter-schema generation, not a replacement for
|
|
43
|
+
review. Ambiguous values become `str({ min: 1 })`. Safe example values can
|
|
44
|
+
infer enums and string-list item enums. A few common source defaults are also
|
|
45
|
+
detected, such as `process.env.PORT ?? "3000"`, `Number(process.env.X ?? 60)`,
|
|
46
|
+
and `process.env.DEBUG !== "false"`.
|
|
47
|
+
|
|
48
|
+
Only example, sample, or template env files can emit `example` metadata. Local
|
|
49
|
+
env values and secret-looking values are not copied into the generated schema.
|
|
45
50
|
Review the result for project-specific constraints such as `requiredWhen`,
|
|
46
|
-
`min`, `max`, or stricter URL
|
|
51
|
+
optional values, conditional requirements, `min`, `max`, or stricter URL
|
|
52
|
+
protocols.
|
|
47
53
|
|
|
48
54
|
## Generate
|
|
49
55
|
|
package/docs/COMPARISON.md
CHANGED
|
@@ -94,7 +94,7 @@ This table is package metadata, not benchmark speed. Celery is this branch's
|
|
|
94
94
|
|
|
95
95
|
| Package | Version Checked | Runtime Deps | Unpacked npm Size | Files |
|
|
96
96
|
| --- | ---: | ---: | ---: | ---: |
|
|
97
|
-
| `celery-env` | 0.1.
|
|
97
|
+
| `celery-env` | 0.1.4 | 0 | 119.2 kB | 26 |
|
|
98
98
|
| `zod` | 4.4.3 | 0 | 4.56 MB | 718 |
|
|
99
99
|
| `valibot` | 1.4.1 | 0 | 1.84 MB | 9 |
|
|
100
100
|
| `envalid` | 8.2.0 | 1 | 88.8 kB | 39 |
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -64,9 +64,14 @@ async function init(args) {
|
|
|
64
64
|
async function infer(args) {
|
|
65
65
|
if (!args.schema) usage(1);
|
|
66
66
|
const schemaPath = resolve(args.schema);
|
|
67
|
-
const {
|
|
67
|
+
const { inferSchema } = await import("./infer.js");
|
|
68
68
|
await mkdir(dirname(schemaPath), { recursive: true });
|
|
69
|
-
|
|
69
|
+
const result = await inferSchema({ envFiles: args.envFiles, scanPaths: args.scanPaths });
|
|
70
|
+
await writeOutput(schemaPath, result.source, args.force);
|
|
71
|
+
console.log(`Wrote ${schemaPath}
|
|
72
|
+
Scanned ${result.envFileCount} env file(s) and ${result.sourceFileCount} source file(s).
|
|
73
|
+
Found ${result.keyCount} environment variable(s).
|
|
74
|
+
Next: review the schema, then run celery-env generate --schema ${schemaPath} --out src/env.mjs --types src/env.d.ts`);
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
function parseArgs(argv) {
|
package/src/infer.js
CHANGED
|
@@ -6,10 +6,20 @@ const JS_IDENT = /^[$A-Z_a-z][$\w]*$/;
|
|
|
6
6
|
const SOURCE_EXTENSIONS = new Set([".cjs", ".cts", ".js", ".jsx", ".mjs", ".mts", ".svelte", ".ts", ".tsx", ".vue"]);
|
|
7
7
|
const SKIP_DIRS = new Set([".git", ".next", ".nuxt", ".output", ".tmp", "build", "coverage", "dist", "node_modules"]);
|
|
8
8
|
const DEFAULT_ENV_FILES = [".env.example", ".env", ".env.local"];
|
|
9
|
-
const DEFAULT_SCAN_PATHS = ["src", "app", "pages", "lib", "server"];
|
|
10
|
-
const
|
|
9
|
+
const DEFAULT_SCAN_PATHS = ["src", "app", "pages", "lib", "server", "scripts", "prisma", ...["next", "vite", "astro"].flatMap((name) => configNames(name)), ...["server", "index"].flatMap((name) => configNames(name, ""))];
|
|
10
|
+
const BOOL_VALUE = /^(?:true|yes|on|false|no|off)$/;
|
|
11
|
+
const BOOLISH_KEY = /^(?:DEBUG|VERBOSE|(?:ENABLE|DISABLE|ENABLED|DISABLED|IS|HAS|USE|ALLOW|REGISTER)_.*|.*_(?:ENABLED|DISABLED|FLAG|FLAGS|ACTIVE))$/;
|
|
11
12
|
const SECRET_KEY = /(?:SECRET|TOKEN|PASSWORD|PASS|PRIVATE|CREDENTIAL|AUTH|API_KEY|ACCESS_KEY)/i;
|
|
12
13
|
const SECRET_VALUE = /(?:^sk_|^pk_|^gh[pousr]_|^xox[baprs]-|^eyJ|-----BEGIN |:\/\/[^/\s:@]+:[^/\s:@]+@|[A-Za-z0-9+/=_-]{32,})/;
|
|
14
|
+
const ENUM_VALUE = /^[A-Za-z][A-Za-z0-9_.:-]{0,31}$/;
|
|
15
|
+
const LOG_LEVELS = ["trace", "debug", "info", "warn", "error", "fatal"];
|
|
16
|
+
const NAMED_ENUMS = {
|
|
17
|
+
COMMAND_SCOPE: ["global", "guild"],
|
|
18
|
+
LOGGER_LEVEL: LOG_LEVELS,
|
|
19
|
+
LOG_LEVEL: LOG_LEVELS,
|
|
20
|
+
NODE_ENV: ["development", "test", "production"],
|
|
21
|
+
VERCEL_ENV: ["development", "preview", "production"]
|
|
22
|
+
};
|
|
13
23
|
const MAX_ENV_FILE_BYTES = 256 * 1024;
|
|
14
24
|
const MAX_SOURCE_FILE_BYTES = 1024 * 1024;
|
|
15
25
|
const MAX_SOURCE_FILES = 2000;
|
|
@@ -17,6 +27,10 @@ const MAX_SOURCE_BYTES = 8 * 1024 * 1024;
|
|
|
17
27
|
const MAX_SCAN_DEPTH = 32;
|
|
18
28
|
|
|
19
29
|
export async function inferSchemaSource(options = {}) {
|
|
30
|
+
return (await inferSchema(options)).source;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function inferSchema(options = {}) {
|
|
20
34
|
const cwd = resolve(options.cwd || process.cwd());
|
|
21
35
|
const explicitEnvFiles = options.envFiles?.length;
|
|
22
36
|
const explicitScanPaths = options.scanPaths?.length;
|
|
@@ -32,10 +46,11 @@ export async function inferSchemaSource(options = {}) {
|
|
|
32
46
|
}
|
|
33
47
|
}
|
|
34
48
|
|
|
35
|
-
|
|
49
|
+
const scannedSources = await sourceFiles(scanPaths, { rejectRootSymlinks: Boolean(explicitScanPaths) });
|
|
50
|
+
for (const file of scannedSources) {
|
|
36
51
|
const source = await readFile(file, "utf8");
|
|
37
|
-
for (const
|
|
38
|
-
record(entries, key, {
|
|
52
|
+
for (const hint of scanEnvHints(source)) {
|
|
53
|
+
record(entries, hint.key, { defaults: hint.defaults });
|
|
39
54
|
}
|
|
40
55
|
}
|
|
41
56
|
|
|
@@ -43,7 +58,12 @@ export async function inferSchemaSource(options = {}) {
|
|
|
43
58
|
throw new Error("No environment variables found; pass --env or --scan");
|
|
44
59
|
}
|
|
45
60
|
|
|
46
|
-
return
|
|
61
|
+
return {
|
|
62
|
+
source: generateSchemaModule(entries),
|
|
63
|
+
envFileCount: envFiles.length,
|
|
64
|
+
sourceFileCount: scannedSources.length,
|
|
65
|
+
keyCount: entries.size
|
|
66
|
+
};
|
|
47
67
|
}
|
|
48
68
|
|
|
49
69
|
export function parseEnvSource(source) {
|
|
@@ -56,20 +76,33 @@ export function parseEnvSource(source) {
|
|
|
56
76
|
}
|
|
57
77
|
|
|
58
78
|
export function scanEnvKeys(source) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
79
|
+
return scanEnvHints(source).map((hint) => hint.key);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function scanEnvHints(source) {
|
|
83
|
+
const hints = new Map();
|
|
84
|
+
collectMatches(hints, source, /\b(?:process\.env|import\.meta\.env)\.([A-Za-z_][A-Za-z0-9_]*)\b/g, 1);
|
|
85
|
+
collectMatches(hints, source, /\b(?:process\.env|import\.meta\.env)\[\s*(["'`])([A-Za-z_][A-Za-z0-9_]*)\1\s*\]/g, 2);
|
|
86
|
+
|
|
87
|
+
for (const match of source.matchAll(/\{([^}]+)\}\s*=\s*(?:process\.env|import\.meta\.env)\b/g)) {
|
|
88
|
+
for (const key of destructuredKeys(match[1])) addHint(hints, key);
|
|
67
89
|
}
|
|
68
|
-
|
|
69
|
-
|
|
90
|
+
|
|
91
|
+
const ref = String.raw`\b(?:process\.env|import\.meta\.env)\.([A-Za-z_][A-Za-z0-9_]*)\s*`;
|
|
92
|
+
for (const match of source.matchAll(new RegExp(`${ref}\\?\\?\\s*(["'\`])([^"'\`\\\\]*(?:\\\\.[^"'\`\\\\]*)*)\\2`, "g"))) {
|
|
93
|
+
addHint(hints, match[1], unescapeQuoted(match[3]));
|
|
94
|
+
}
|
|
95
|
+
for (const match of source.matchAll(new RegExp(`${ref}\\?\\?\\s*([+-]?(?:\\d+\\.\\d*|\\.\\d+|\\d+)|true|false)`, "g"))) {
|
|
96
|
+
addHint(hints, match[1], match[2]);
|
|
97
|
+
}
|
|
98
|
+
for (const match of source.matchAll(new RegExp(`${ref}!==\\s*(["'])(?:false|0)\\2`, "g"))) {
|
|
99
|
+
addHint(hints, match[1], "true");
|
|
100
|
+
}
|
|
101
|
+
for (const match of source.matchAll(new RegExp(`${ref}===\\s*(["'])(?:true|1)\\2`, "g"))) {
|
|
102
|
+
addHint(hints, match[1], "false");
|
|
70
103
|
}
|
|
71
104
|
|
|
72
|
-
return [...
|
|
105
|
+
return [...hints.values()].sort((a, b) => a.key.localeCompare(b.key));
|
|
73
106
|
}
|
|
74
107
|
|
|
75
108
|
function parseEnvLine(line) {
|
|
@@ -126,8 +159,19 @@ function stripInlineComment(value) {
|
|
|
126
159
|
return value;
|
|
127
160
|
}
|
|
128
161
|
|
|
129
|
-
function collectMatches(
|
|
130
|
-
for (const match of source.matchAll(pattern))
|
|
162
|
+
function collectMatches(hints, source, pattern, group) {
|
|
163
|
+
for (const match of source.matchAll(pattern)) addHint(hints, match[group]);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function addHint(hints, key, defaultValue) {
|
|
167
|
+
if (!ENV_NAME.test(key)) return;
|
|
168
|
+
const hint = hints.get(key) || { key, defaults: [] };
|
|
169
|
+
if (defaultValue !== undefined) hint.defaults.push(defaultValue);
|
|
170
|
+
hints.set(key, hint);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function configNames(name, infix = ".config") {
|
|
174
|
+
return ["js", "mjs", "ts"].map((ext) => `${name}${infix}.${ext}`);
|
|
131
175
|
}
|
|
132
176
|
|
|
133
177
|
function destructuredKeys(source) {
|
|
@@ -223,11 +267,11 @@ async function collectSourceFiles(out, path, state, depth, rejectSymlink) {
|
|
|
223
267
|
function record(entries, key, source) {
|
|
224
268
|
let entry = entries.get(key);
|
|
225
269
|
if (!entry) {
|
|
226
|
-
entry = { key, values: [],
|
|
270
|
+
entry = { key, values: [], defaults: [] };
|
|
227
271
|
entries.set(key, entry);
|
|
228
272
|
}
|
|
229
|
-
if (source.
|
|
230
|
-
|
|
273
|
+
if (source.defaults) entry.defaults.push(...source.defaults.filter((value) => !unsafe(key, value)));
|
|
274
|
+
if (source.value !== undefined) entry.values.push({ value: source.value, safeExamples: source.safeExamples });
|
|
231
275
|
}
|
|
232
276
|
|
|
233
277
|
function generateSchemaModule(entries) {
|
|
@@ -252,38 +296,44 @@ function generateSchemaModule(entries) {
|
|
|
252
296
|
}
|
|
253
297
|
|
|
254
298
|
function inferRule(entry) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
299
|
+
const samples = entry.values.filter((item) => item.value !== "");
|
|
300
|
+
const defaults = cleanDefaults(entry);
|
|
301
|
+
const knownEnum = namedEnumRule(entry, samples, defaults);
|
|
302
|
+
if (knownEnum) return withExample(entry, withDefault(defaults, knownEnum));
|
|
258
303
|
|
|
259
|
-
const
|
|
260
|
-
if (!
|
|
304
|
+
const observations = samples.concat(defaults.map((value) => ({ value, safeExamples: false })));
|
|
305
|
+
if (!observations.length) return { kind: "str", options: { min: 1 } };
|
|
261
306
|
|
|
262
|
-
const
|
|
307
|
+
const enumRule = sampleEnumRule(entry, samples);
|
|
308
|
+
if (enumRule) return withExample(entry, withDefault(defaults, enumRule));
|
|
309
|
+
|
|
310
|
+
const kinds = observations.map((item) => inferValue(entry.key, item.value, enumSafe(entry, item)));
|
|
263
311
|
if (kinds.some((kind) => kind.kind === "str")) return stringRule(entry);
|
|
264
312
|
|
|
265
313
|
const first = kinds[0].kind;
|
|
266
314
|
if (!kinds.every((kind) => kind.kind === first)) return stringRule(entry);
|
|
267
315
|
|
|
268
316
|
const rule = mergeRules(first, kinds);
|
|
269
|
-
|
|
270
|
-
if (example !== undefined) rule.options = { ...rule.options, example };
|
|
271
|
-
return rule;
|
|
317
|
+
return withExample(entry, withDefault(defaults, rule));
|
|
272
318
|
}
|
|
273
319
|
|
|
274
|
-
function inferValue(value) {
|
|
275
|
-
if (
|
|
320
|
+
function inferValue(key, value, allowEnum) {
|
|
321
|
+
if (isBoolValue(key, value)) return { kind: "bool" };
|
|
276
322
|
if (strictInt(value)) return { kind: "int", options: { strict: true } };
|
|
277
323
|
if (strictNumber(value)) return { kind: "num", options: { strict: true } };
|
|
278
324
|
const jsonRule = inferJson(value);
|
|
279
325
|
if (jsonRule) return jsonRule;
|
|
280
|
-
const listRule = inferList(value);
|
|
326
|
+
const listRule = inferList(key, value, allowEnum);
|
|
281
327
|
if (listRule) return listRule;
|
|
282
328
|
const urlRule = inferUrl(value);
|
|
283
329
|
if (urlRule) return urlRule;
|
|
284
330
|
return { kind: "str", options: { min: 1 } };
|
|
285
331
|
}
|
|
286
332
|
|
|
333
|
+
function isBoolValue(key, value) {
|
|
334
|
+
return BOOL_VALUE.test(value) || ((value === "1" || value === "0") && BOOLISH_KEY.test(key));
|
|
335
|
+
}
|
|
336
|
+
|
|
287
337
|
function inferJson(value) {
|
|
288
338
|
if (!/^\s*[\[{]/.test(value)) return;
|
|
289
339
|
try {
|
|
@@ -292,19 +342,23 @@ function inferJson(value) {
|
|
|
292
342
|
} catch {}
|
|
293
343
|
}
|
|
294
344
|
|
|
295
|
-
function inferList(value) {
|
|
345
|
+
function inferList(key, value, allowEnum) {
|
|
296
346
|
if (!value.includes(",") || /^\s*[\[{]/.test(value)) return;
|
|
297
347
|
const parts = value.split(",").map((part) => part.trim());
|
|
298
348
|
if (parts.length < 2 || parts.some((part) => part === "")) return;
|
|
299
349
|
const items = parts.map((part) => {
|
|
300
|
-
if (
|
|
350
|
+
if (isBoolValue(key, part)) return { kind: "bool" };
|
|
301
351
|
if (strictInt(part)) return { kind: "int", options: { strict: true } };
|
|
302
352
|
if (strictNumber(part)) return { kind: "num", options: { strict: true } };
|
|
303
353
|
const urlRule = inferUrl(part);
|
|
304
354
|
return urlRule || { kind: "str" };
|
|
305
355
|
});
|
|
306
356
|
const first = items[0].kind;
|
|
307
|
-
if (first === "str"
|
|
357
|
+
if (first === "str") {
|
|
358
|
+
const values = allowEnum && enumValues(parts);
|
|
359
|
+
return { kind: "list", item: values ? { kind: "oneOf", values } : { kind: "str", options: { min: 1 } } };
|
|
360
|
+
}
|
|
361
|
+
if (!items.every((item) => item.kind === first)) return;
|
|
308
362
|
return { kind: "list", item: mergeRules(first, items) };
|
|
309
363
|
}
|
|
310
364
|
|
|
@@ -334,25 +388,72 @@ function mergeRules(kind, rules) {
|
|
|
334
388
|
}
|
|
335
389
|
if (kind === "list") {
|
|
336
390
|
const itemKind = rules[0].item.kind;
|
|
391
|
+
if (!rules.every((rule) => rule.item.kind === itemKind)) return { kind: "list", item: { kind: "str", options: { min: 1 } } };
|
|
337
392
|
return { kind: "list", item: mergeRules(itemKind, rules.map((rule) => rule.item)) };
|
|
338
393
|
}
|
|
394
|
+
if (kind === "oneOf") return { kind, values: enumValues(rules.flatMap((rule) => rule.values)) };
|
|
339
395
|
if (kind === "int" || kind === "num") return { kind, options: { strict: true } };
|
|
396
|
+
if (kind === "str" && rules.some((rule) => rule.options)) return { kind, options: { min: 1 } };
|
|
340
397
|
return { kind };
|
|
341
398
|
}
|
|
342
399
|
|
|
343
400
|
function stringRule(entry) {
|
|
344
401
|
const rule = { kind: "str", options: { min: 1 } };
|
|
402
|
+
withDefault(cleanDefaults(entry), rule);
|
|
345
403
|
const example = safeExample(entry, rule);
|
|
346
|
-
if (example !== undefined) rule.options.example = example;
|
|
404
|
+
if (example !== undefined && rule.options.default !== example) rule.options.example = example;
|
|
347
405
|
return rule;
|
|
348
406
|
}
|
|
349
407
|
|
|
350
408
|
function safeExample(entry, rule) {
|
|
351
409
|
const sample = entry.values.find((item) => item.safeExamples && item.value !== "");
|
|
352
|
-
if (!sample ||
|
|
410
|
+
if (!sample || unsafe(entry.key, sample.value)) return;
|
|
353
411
|
return exampleValue(sample.value, rule);
|
|
354
412
|
}
|
|
355
413
|
|
|
414
|
+
function withExample(entry, rule) {
|
|
415
|
+
const example = safeExample(entry, rule);
|
|
416
|
+
if (example !== undefined && rule.options?.default !== example) rule.options = { ...rule.options, example };
|
|
417
|
+
return rule;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function enumSafe(entry, item) {
|
|
421
|
+
return item.safeExamples && !unsafe(entry.key, item.value);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function sampleEnumRule(entry, samples) {
|
|
425
|
+
const values = samples.every((item) => enumSafe(entry, item)) && enumValues(samples.map((item) => item.value));
|
|
426
|
+
if (values && values.length > 1 && values.length <= 8) return { kind: "oneOf", values };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function namedEnumRule(entry, samples, defaults) {
|
|
430
|
+
const values = NAMED_ENUMS[entry.key];
|
|
431
|
+
if (!values) return;
|
|
432
|
+
const seen = samples.map((item) => item.value).concat(defaults);
|
|
433
|
+
if (seen.some((value) => !values.includes(value))) return;
|
|
434
|
+
if (entry.key !== "NODE_ENV" && !seen.length) return;
|
|
435
|
+
const options = entry.key === "NODE_ENV" ? { default: "development" } : undefined;
|
|
436
|
+
return { kind: "oneOf", values, options };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function cleanDefaults(entry) {
|
|
440
|
+
const values = [...new Set(entry.defaults.filter(Boolean))];
|
|
441
|
+
return values[1] ? [] : values;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function withDefault(defaults, rule) {
|
|
445
|
+
if (!defaults.length) return rule;
|
|
446
|
+
const value = exampleValue(defaults[0], rule);
|
|
447
|
+
if (value === undefined) return rule;
|
|
448
|
+
if (rule.kind === "oneOf" && !rule.values.includes(defaults[0])) return rule;
|
|
449
|
+
rule.options = { ...rule.options, default: value };
|
|
450
|
+
return rule;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function unsafe(key, value) {
|
|
454
|
+
return SECRET_KEY.test(key) || SECRET_VALUE.test(value);
|
|
455
|
+
}
|
|
456
|
+
|
|
356
457
|
function exampleValue(value, rule) {
|
|
357
458
|
if (rule.kind === "bool") return value === "true" || value === "1" || value === "yes" || value === "on";
|
|
358
459
|
if (rule.kind === "int" || rule.kind === "num") return Number(value);
|
|
@@ -373,7 +474,7 @@ function collectRuleImports(imports, rule) {
|
|
|
373
474
|
}
|
|
374
475
|
|
|
375
476
|
function ruleSource(rule) {
|
|
376
|
-
if (rule.kind === "oneOf") return `oneOf(${literal(rule.values)}, ${literal(rule.options)})`;
|
|
477
|
+
if (rule.kind === "oneOf") return rule.options && Object.keys(rule.options).length ? `oneOf(${literal(rule.values)}, ${literal(rule.options)})` : `oneOf(${literal(rule.values)})`;
|
|
377
478
|
if (rule.kind === "list") {
|
|
378
479
|
const item = ruleSource(rule.item);
|
|
379
480
|
return rule.options && Object.keys(rule.options).length ? `list(${item}, ${literal(rule.options)})` : `list(${item})`;
|
|
@@ -387,9 +488,16 @@ function schemaKey(key) {
|
|
|
387
488
|
}
|
|
388
489
|
|
|
389
490
|
function literal(value) {
|
|
491
|
+
if (Array.isArray(value)) return `[${value.map(literal).join(", ")}]`;
|
|
492
|
+
if (value && typeof value === "object") return `{ ${Object.entries(value).map(([key, item]) => `${schemaKey(key)}: ${literal(item)}`).join(", ")} }`;
|
|
390
493
|
return JSON.stringify(value);
|
|
391
494
|
}
|
|
392
495
|
|
|
496
|
+
function enumValues(values) {
|
|
497
|
+
if (!values.every((value) => ENUM_VALUE.test(value))) return;
|
|
498
|
+
return [...new Set(values)].sort();
|
|
499
|
+
}
|
|
500
|
+
|
|
393
501
|
function importSort(a, b) {
|
|
394
502
|
return a.localeCompare(b);
|
|
395
503
|
}
|