celery-env 0.1.3 → 0.1.4
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 +1 -0
- package/docs/COMPARISON.md +1 -1
- package/package.json +1 -1
- package/src/infer.js +44 -11
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
|
@@ -42,6 +42,7 @@ npx celery-env infer \
|
|
|
42
42
|
Inference is conservative. Ambiguous values become `str({ min: 1 })`. Only
|
|
43
43
|
example, sample, or template env files can emit `example` metadata; local env
|
|
44
44
|
values and secret-looking values are not copied into the generated schema.
|
|
45
|
+
Safe example values can infer enums and string-list item enums.
|
|
45
46
|
Review the result for project-specific constraints such as `requiredWhen`,
|
|
46
47
|
`min`, `max`, or stricter URL protocols.
|
|
47
48
|
|
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/infer.js
CHANGED
|
@@ -10,6 +10,7 @@ const DEFAULT_SCAN_PATHS = ["src", "app", "pages", "lib", "server"];
|
|
|
10
10
|
const BOOL_VALUES = new Set(["true", "yes", "on", "false", "no", "off"]);
|
|
11
11
|
const SECRET_KEY = /(?:SECRET|TOKEN|PASSWORD|PASS|PRIVATE|CREDENTIAL|AUTH|API_KEY|ACCESS_KEY)/i;
|
|
12
12
|
const SECRET_VALUE = /(?:^sk_|^pk_|^gh[pousr]_|^xox[baprs]-|^eyJ|-----BEGIN |:\/\/[^/\s:@]+:[^/\s:@]+@|[A-Za-z0-9+/=_-]{32,})/;
|
|
13
|
+
const ENUM_VALUE = /^[A-Za-z][A-Za-z0-9_.:-]{0,31}$/;
|
|
13
14
|
const MAX_ENV_FILE_BYTES = 256 * 1024;
|
|
14
15
|
const MAX_SOURCE_FILE_BYTES = 1024 * 1024;
|
|
15
16
|
const MAX_SOURCE_FILES = 2000;
|
|
@@ -256,28 +257,29 @@ function inferRule(entry) {
|
|
|
256
257
|
return { kind: "oneOf", values: ["development", "test", "production"], options: { default: "development" } };
|
|
257
258
|
}
|
|
258
259
|
|
|
259
|
-
const samples = entry.values.
|
|
260
|
+
const samples = entry.values.filter((item) => item.value !== "");
|
|
260
261
|
if (!samples.length) return { kind: "str", options: { min: 1 } };
|
|
261
262
|
|
|
262
|
-
const
|
|
263
|
+
const enumRule = sampleEnumRule(entry, samples);
|
|
264
|
+
if (enumRule) return withExample(entry, enumRule);
|
|
265
|
+
|
|
266
|
+
const kinds = samples.map((item) => inferValue(item.value, enumSafe(entry, item)));
|
|
263
267
|
if (kinds.some((kind) => kind.kind === "str")) return stringRule(entry);
|
|
264
268
|
|
|
265
269
|
const first = kinds[0].kind;
|
|
266
270
|
if (!kinds.every((kind) => kind.kind === first)) return stringRule(entry);
|
|
267
271
|
|
|
268
272
|
const rule = mergeRules(first, kinds);
|
|
269
|
-
|
|
270
|
-
if (example !== undefined) rule.options = { ...rule.options, example };
|
|
271
|
-
return rule;
|
|
273
|
+
return withExample(entry, rule);
|
|
272
274
|
}
|
|
273
275
|
|
|
274
|
-
function inferValue(value) {
|
|
276
|
+
function inferValue(value, allowEnum) {
|
|
275
277
|
if (BOOL_VALUES.has(value)) return { kind: "bool" };
|
|
276
278
|
if (strictInt(value)) return { kind: "int", options: { strict: true } };
|
|
277
279
|
if (strictNumber(value)) return { kind: "num", options: { strict: true } };
|
|
278
280
|
const jsonRule = inferJson(value);
|
|
279
281
|
if (jsonRule) return jsonRule;
|
|
280
|
-
const listRule = inferList(value);
|
|
282
|
+
const listRule = inferList(value, allowEnum);
|
|
281
283
|
if (listRule) return listRule;
|
|
282
284
|
const urlRule = inferUrl(value);
|
|
283
285
|
if (urlRule) return urlRule;
|
|
@@ -292,7 +294,7 @@ function inferJson(value) {
|
|
|
292
294
|
} catch {}
|
|
293
295
|
}
|
|
294
296
|
|
|
295
|
-
function inferList(value) {
|
|
297
|
+
function inferList(value, allowEnum) {
|
|
296
298
|
if (!value.includes(",") || /^\s*[\[{]/.test(value)) return;
|
|
297
299
|
const parts = value.split(",").map((part) => part.trim());
|
|
298
300
|
if (parts.length < 2 || parts.some((part) => part === "")) return;
|
|
@@ -304,7 +306,11 @@ function inferList(value) {
|
|
|
304
306
|
return urlRule || { kind: "str" };
|
|
305
307
|
});
|
|
306
308
|
const first = items[0].kind;
|
|
307
|
-
if (first === "str"
|
|
309
|
+
if (first === "str") {
|
|
310
|
+
const values = allowEnum && enumValues(parts);
|
|
311
|
+
return { kind: "list", item: values ? { kind: "oneOf", values } : { kind: "str", options: { min: 1 } } };
|
|
312
|
+
}
|
|
313
|
+
if (!items.every((item) => item.kind === first)) return;
|
|
308
314
|
return { kind: "list", item: mergeRules(first, items) };
|
|
309
315
|
}
|
|
310
316
|
|
|
@@ -334,9 +340,12 @@ function mergeRules(kind, rules) {
|
|
|
334
340
|
}
|
|
335
341
|
if (kind === "list") {
|
|
336
342
|
const itemKind = rules[0].item.kind;
|
|
343
|
+
if (!rules.every((rule) => rule.item.kind === itemKind)) return { kind: "list", item: { kind: "str", options: { min: 1 } } };
|
|
337
344
|
return { kind: "list", item: mergeRules(itemKind, rules.map((rule) => rule.item)) };
|
|
338
345
|
}
|
|
346
|
+
if (kind === "oneOf") return { kind, values: enumValues(rules.flatMap((rule) => rule.values)) };
|
|
339
347
|
if (kind === "int" || kind === "num") return { kind, options: { strict: true } };
|
|
348
|
+
if (kind === "str" && rules.some((rule) => rule.options)) return { kind, options: { min: 1 } };
|
|
340
349
|
return { kind };
|
|
341
350
|
}
|
|
342
351
|
|
|
@@ -349,10 +358,29 @@ function stringRule(entry) {
|
|
|
349
358
|
|
|
350
359
|
function safeExample(entry, rule) {
|
|
351
360
|
const sample = entry.values.find((item) => item.safeExamples && item.value !== "");
|
|
352
|
-
if (!sample ||
|
|
361
|
+
if (!sample || unsafe(entry.key, sample.value)) return;
|
|
353
362
|
return exampleValue(sample.value, rule);
|
|
354
363
|
}
|
|
355
364
|
|
|
365
|
+
function withExample(entry, rule) {
|
|
366
|
+
const example = safeExample(entry, rule);
|
|
367
|
+
if (example !== undefined) rule.options = { ...rule.options, example };
|
|
368
|
+
return rule;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function enumSafe(entry, item) {
|
|
372
|
+
return item.safeExamples && !unsafe(entry.key, item.value);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function sampleEnumRule(entry, samples) {
|
|
376
|
+
const values = samples.every((item) => enumSafe(entry, item)) && enumValues(samples.map((item) => item.value));
|
|
377
|
+
if (values && values.length > 1 && values.length <= 8) return { kind: "oneOf", values };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function unsafe(key, value) {
|
|
381
|
+
return SECRET_KEY.test(key) || SECRET_VALUE.test(value);
|
|
382
|
+
}
|
|
383
|
+
|
|
356
384
|
function exampleValue(value, rule) {
|
|
357
385
|
if (rule.kind === "bool") return value === "true" || value === "1" || value === "yes" || value === "on";
|
|
358
386
|
if (rule.kind === "int" || rule.kind === "num") return Number(value);
|
|
@@ -373,7 +401,7 @@ function collectRuleImports(imports, rule) {
|
|
|
373
401
|
}
|
|
374
402
|
|
|
375
403
|
function ruleSource(rule) {
|
|
376
|
-
if (rule.kind === "oneOf") return `oneOf(${literal(rule.values)}, ${literal(rule.options)})`;
|
|
404
|
+
if (rule.kind === "oneOf") return rule.options && Object.keys(rule.options).length ? `oneOf(${literal(rule.values)}, ${literal(rule.options)})` : `oneOf(${literal(rule.values)})`;
|
|
377
405
|
if (rule.kind === "list") {
|
|
378
406
|
const item = ruleSource(rule.item);
|
|
379
407
|
return rule.options && Object.keys(rule.options).length ? `list(${item}, ${literal(rule.options)})` : `list(${item})`;
|
|
@@ -390,6 +418,11 @@ function literal(value) {
|
|
|
390
418
|
return JSON.stringify(value);
|
|
391
419
|
}
|
|
392
420
|
|
|
421
|
+
function enumValues(values) {
|
|
422
|
+
if (!values.every((value) => ENUM_VALUE.test(value))) return;
|
|
423
|
+
return [...new Set(values)].sort();
|
|
424
|
+
}
|
|
425
|
+
|
|
393
426
|
function importSort(a, b) {
|
|
394
427
|
return a.localeCompare(b);
|
|
395
428
|
}
|