celery-env 0.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/LICENSE +21 -0
- package/README.md +151 -0
- package/SECURITY.md +75 -0
- package/docs/BENCHMARKS.md +44 -0
- package/docs/CLI.md +68 -0
- package/docs/COMPARISON.md +58 -0
- package/docs/GETTING_STARTED.md +134 -0
- package/docs/MIGRATION.md +54 -0
- package/docs/README.md +15 -0
- package/docs/RUNTIME.md +43 -0
- package/docs/SCHEMA.md +149 -0
- package/docs/TROUBLESHOOTING.md +74 -0
- package/docs/TYPESCRIPT.md +105 -0
- package/docs/assets/celery-mark.svg +7 -0
- package/package.json +77 -0
- package/src/cli.js +133 -0
- package/src/compiler.d.ts +7 -0
- package/src/compiler.js +811 -0
- package/src/index.d.ts +22 -0
- package/src/index.js +375 -0
package/docs/SCHEMA.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Schema API
|
|
2
|
+
|
|
3
|
+
Schemas are plain JavaScript modules. Each key describes one env var.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import { defineEnv, int, str } from "celery-env";
|
|
7
|
+
|
|
8
|
+
export default defineEnv({
|
|
9
|
+
DATABASE_URL: str({ min: 1 }),
|
|
10
|
+
PORT: int({ default: 3000 })
|
|
11
|
+
});
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Think of the schema as executable documentation for your configuration. The key
|
|
15
|
+
is the env var name, the validator describes the accepted string format, and
|
|
16
|
+
options describe defaults, examples, and environment-specific behavior.
|
|
17
|
+
|
|
18
|
+
## Validators
|
|
19
|
+
|
|
20
|
+
| Validator | Output | Use For |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| `str(options)` | `string` | Text, secrets, tokens. |
|
|
23
|
+
| `int(options)` | `number` | Whole numbers like ports and limits. |
|
|
24
|
+
| `num(options)` | `number` | Decimal numbers. |
|
|
25
|
+
| `bool(options)` | `boolean` | Feature flags. |
|
|
26
|
+
| `oneOf(values, options)` | union | Enums like `NODE_ENV`. |
|
|
27
|
+
| `url(options)` | `string` | URLs with optional protocols. |
|
|
28
|
+
| `json(options)` | `unknown` | JSON strings parsed with `JSON.parse`. |
|
|
29
|
+
| `list(item, options)` | `readonly T[]` | Comma-separated lists. |
|
|
30
|
+
|
|
31
|
+
## Common Options
|
|
32
|
+
|
|
33
|
+
| Option | Meaning |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| `default` | Value used when the env var is missing. |
|
|
36
|
+
| `devDefault` | Value used when `NODE_ENV` is not `production`. |
|
|
37
|
+
| `testDefault` | Value used when `NODE_ENV` is `test`. |
|
|
38
|
+
| `optional` | Allows the value to be missing. |
|
|
39
|
+
| `requiredWhen` | Function that can make a value required. |
|
|
40
|
+
| `desc` | Description used in generated `.env.example`. |
|
|
41
|
+
| `example` | Example value used in generated `.env.example`. |
|
|
42
|
+
| `docs` | Longer documentation text for generated metadata. |
|
|
43
|
+
|
|
44
|
+
`testDefault` wins over `devDefault`, and `default` applies in every
|
|
45
|
+
environment.
|
|
46
|
+
|
|
47
|
+
## Missing Values
|
|
48
|
+
|
|
49
|
+
Empty strings are treated as missing. If a value is missing, Celery checks
|
|
50
|
+
options in this order:
|
|
51
|
+
|
|
52
|
+
1. `testDefault` when `NODE_ENV` is `test`.
|
|
53
|
+
2. `devDefault` when `NODE_ENV` is not `production`.
|
|
54
|
+
3. `default`.
|
|
55
|
+
4. `optional`.
|
|
56
|
+
5. Otherwise, the variable is required.
|
|
57
|
+
|
|
58
|
+
## Strings
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
str({ min: 8, max: 128 })
|
|
62
|
+
str({ startsWith: "sk_" })
|
|
63
|
+
str({ includes: "@" })
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Numbers
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
int({ min: 1, max: 65535 })
|
|
70
|
+
num({ min: 0, max: 1 })
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
By default, numeric parsing follows JavaScript `Number()`. Use `strict: true`
|
|
74
|
+
to reject values such as hex and exponent notation:
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
int({ strict: true })
|
|
78
|
+
num({ strict: true })
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Booleans
|
|
82
|
+
|
|
83
|
+
Accepted true values:
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
true, 1, yes, on
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Accepted false values:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
false, 0, no, off
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Enums
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
oneOf(["development", "test", "production"], {
|
|
99
|
+
default: "development"
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Values can be strings, numbers, or booleans.
|
|
104
|
+
|
|
105
|
+
## URLs
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
url({ protocols: ["https"] })
|
|
109
|
+
url({ protocols: ["postgres", "postgresql"] })
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Write protocols without the colon. Use `postgres`, not `postgres:`.
|
|
113
|
+
|
|
114
|
+
## JSON
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
json()
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Celery validates that the value is valid JSON. It does not validate the object
|
|
121
|
+
shape inside that JSON.
|
|
122
|
+
|
|
123
|
+
## Lists
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
list(str())
|
|
127
|
+
list(int({ strict: true }))
|
|
128
|
+
list(url({ protocols: ["https"] }))
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Options:
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
list(str(), { separator: ",", trim: true })
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`separator` defaults to `","`. `trim` defaults to `true`.
|
|
138
|
+
|
|
139
|
+
## Conditional Required Values
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
SESSION_SECRET: str({
|
|
143
|
+
optional: true,
|
|
144
|
+
requiredWhen: (env) => env.NODE_ENV === "production"
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Generated validators serialize `requiredWhen` with `Function#toString()`.
|
|
149
|
+
Keep the function self-contained and do not close over local variables.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
## The CLI Refuses To Overwrite A File
|
|
4
|
+
|
|
5
|
+
Generation does not overwrite existing files unless you pass `--force`.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx celery-env generate \
|
|
9
|
+
--schema env.schema.mjs \
|
|
10
|
+
--out src/env.mjs \
|
|
11
|
+
--types src/env.d.ts \
|
|
12
|
+
--force
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## TypeScript Cannot Find The Generated Types
|
|
16
|
+
|
|
17
|
+
Generate declarations with `--types` and import the generated module path:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npx celery-env generate --schema env.schema.mjs --out src/env.mjs --types src/env.d.ts
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { loadEnv } from "./env.mjs";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The `.d.ts` file must sit next to the generated `.mjs` file with the same base
|
|
28
|
+
name.
|
|
29
|
+
|
|
30
|
+
## A URL Protocol Is Rejected
|
|
31
|
+
|
|
32
|
+
Write protocols without the colon:
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
url({ protocols: ["postgres"] })
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Use `postgres`, not `postgres:`.
|
|
39
|
+
|
|
40
|
+
## A Production Secret Is Missing
|
|
41
|
+
|
|
42
|
+
Use `requiredWhen` for values that are optional in development but required in
|
|
43
|
+
production:
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
SESSION_SECRET: str({
|
|
47
|
+
optional: true,
|
|
48
|
+
requiredWhen: (env) => env.NODE_ENV === "production"
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Keep `requiredWhen` self-contained. Generated validators serialize the function
|
|
53
|
+
source, so it should not close over local variables.
|
|
54
|
+
|
|
55
|
+
## `json()` Is Typed As `unknown`
|
|
56
|
+
|
|
57
|
+
Celery only checks that the env value is valid JSON. It does not validate the
|
|
58
|
+
object shape inside the JSON string. Narrow or validate the parsed value in your
|
|
59
|
+
app before using fields from it.
|
|
60
|
+
|
|
61
|
+
## Generated Mode Feels Like Too Much
|
|
62
|
+
|
|
63
|
+
Use runtime mode:
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
import { defineEnv, int, parseEnv, str } from "celery-env";
|
|
67
|
+
|
|
68
|
+
const schema = defineEnv({
|
|
69
|
+
DATABASE_URL: str({ min: 1 }),
|
|
70
|
+
PORT: int({ default: 3000 })
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export const env = parseEnv(schema, process.env);
|
|
74
|
+
```
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# TypeScript
|
|
2
|
+
|
|
3
|
+
You do not need to be a TypeScript expert to use Celery. The main idea is:
|
|
4
|
+
|
|
5
|
+
1. Write a schema.
|
|
6
|
+
2. Generate `src/env.d.ts`.
|
|
7
|
+
3. Import `env` or `loadEnv` and let your editor infer the types.
|
|
8
|
+
|
|
9
|
+
## Generated Types
|
|
10
|
+
|
|
11
|
+
Generate both JavaScript and declarations:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npx celery-env generate \
|
|
15
|
+
--schema env.schema.mjs \
|
|
16
|
+
--out src/env.mjs \
|
|
17
|
+
--types src/env.d.ts
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Then import the generated module:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { loadEnv } from "./env.mjs";
|
|
24
|
+
|
|
25
|
+
const env = loadEnv(process.env);
|
|
26
|
+
|
|
27
|
+
env.PORT;
|
|
28
|
+
// ^ number
|
|
29
|
+
|
|
30
|
+
env.DATABASE_URL;
|
|
31
|
+
// ^ string
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Optional Values
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
import { defineEnv, url } from "celery-env";
|
|
38
|
+
|
|
39
|
+
export default defineEnv({
|
|
40
|
+
SENTRY_DSN: url({ optional: true })
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
TypeScript sees:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
env.SENTRY_DSN;
|
|
48
|
+
// string | undefined
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Defaults Remove Undefined
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
import { defineEnv, int } from "celery-env";
|
|
55
|
+
|
|
56
|
+
export default defineEnv({
|
|
57
|
+
PORT: int({ default: 3000 })
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
TypeScript sees:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
env.PORT;
|
|
65
|
+
// number
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Inferring From A Schema
|
|
69
|
+
|
|
70
|
+
If you use runtime mode or want a named type:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import type { InferEnv } from "celery-env";
|
|
74
|
+
import schema from "../env.schema.mjs";
|
|
75
|
+
|
|
76
|
+
export type Env = InferEnv<typeof schema>;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## JSON Types
|
|
80
|
+
|
|
81
|
+
Generated declarations type `json()` values as `unknown`, because Celery only
|
|
82
|
+
validates JSON syntax.
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
import { defineEnv, json } from "celery-env";
|
|
86
|
+
|
|
87
|
+
const schema = defineEnv({
|
|
88
|
+
RATE_LIMIT_JSON: json()
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Narrow the value in your app after parsing:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
const rateLimit = env.RATE_LIMIT_JSON;
|
|
96
|
+
|
|
97
|
+
if (
|
|
98
|
+
rateLimit &&
|
|
99
|
+
typeof rateLimit === "object" &&
|
|
100
|
+
"windowMs" in rateLimit &&
|
|
101
|
+
"max" in rateLimit
|
|
102
|
+
) {
|
|
103
|
+
// rateLimit has the fields you checked for here.
|
|
104
|
+
}
|
|
105
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">celery-env mark</title>
|
|
3
|
+
<desc id="desc">A compact green celery-env leaf mark.</desc>
|
|
4
|
+
<rect width="128" height="128" rx="28" fill="#0F766E"/>
|
|
5
|
+
<path d="M71 19c-7 9-11 18-12 27 12-3 23-11 34-24 3 21-9 37-31 45 12 6 24 7 38 3-13 18-30 25-50 22-8-1-15-5-20-10 8-2 15-5 21-10-13-3-22-12-26-26 13 7 25 10 36 7-2-10 1-21 10-34Z" fill="#D9F99D"/>
|
|
6
|
+
<path d="M38 103c16-2 32-2 48 0" stroke="#ECFDF5" stroke-width="8" stroke-linecap="round"/>
|
|
7
|
+
</svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "celery-env",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Zero-dependency environment validation with generated standalone validators.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"types": "./src/index.d.ts",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.d.ts",
|
|
11
|
+
"import": "./src/index.js",
|
|
12
|
+
"default": "./src/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./compiler": {
|
|
15
|
+
"types": "./src/compiler.d.ts",
|
|
16
|
+
"import": "./src/compiler.js",
|
|
17
|
+
"default": "./src/compiler.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"bin": {
|
|
21
|
+
"celery-env": "src/cli.js"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/theaaravagarwal/celery-env.git"
|
|
26
|
+
},
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/theaaravagarwal/celery-env/issues"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/theaaravagarwal/celery-env#readme",
|
|
31
|
+
"files": [
|
|
32
|
+
"src/index.js",
|
|
33
|
+
"src/compiler.js",
|
|
34
|
+
"src/index.d.ts",
|
|
35
|
+
"src/compiler.d.ts",
|
|
36
|
+
"src/cli.js",
|
|
37
|
+
"docs/assets/celery-mark.svg",
|
|
38
|
+
"docs/BENCHMARKS.md",
|
|
39
|
+
"docs/CLI.md",
|
|
40
|
+
"docs/COMPARISON.md",
|
|
41
|
+
"docs/GETTING_STARTED.md",
|
|
42
|
+
"docs/MIGRATION.md",
|
|
43
|
+
"docs/README.md",
|
|
44
|
+
"docs/RUNTIME.md",
|
|
45
|
+
"docs/SCHEMA.md",
|
|
46
|
+
"docs/TROUBLESHOOTING.md",
|
|
47
|
+
"docs/TYPESCRIPT.md",
|
|
48
|
+
"SECURITY.md",
|
|
49
|
+
"README.md",
|
|
50
|
+
"LICENSE"
|
|
51
|
+
],
|
|
52
|
+
"scripts": {
|
|
53
|
+
"ci": "npm run security:scan",
|
|
54
|
+
"test": "node --test test/*.mjs",
|
|
55
|
+
"size": "node scripts/size.mjs",
|
|
56
|
+
"security:scan": "node scripts/security-scan.mjs && npm run prepublishOnly",
|
|
57
|
+
"validate:publish": "node scripts/validate-publish.mjs",
|
|
58
|
+
"prepublishOnly": "npm test && npm run size && npm run validate:publish",
|
|
59
|
+
"demo:generate": "node src/cli.js --schema examples/env.schema.mjs --out .tmp/generated/env.mjs --types .tmp/generated/env.d.ts"
|
|
60
|
+
},
|
|
61
|
+
"keywords": [
|
|
62
|
+
"env",
|
|
63
|
+
"validation",
|
|
64
|
+
"environment",
|
|
65
|
+
"config",
|
|
66
|
+
"schema",
|
|
67
|
+
"typescript",
|
|
68
|
+
"serverless",
|
|
69
|
+
"zod",
|
|
70
|
+
"dotenv",
|
|
71
|
+
"cli"
|
|
72
|
+
],
|
|
73
|
+
"license": "MIT",
|
|
74
|
+
"engines": {
|
|
75
|
+
"node": ">=18"
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { mkdir, open, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
|
|
7
|
+
const NOFOLLOW = constants.O_NOFOLLOW || 0;
|
|
8
|
+
|
|
9
|
+
const args = parseArgs(process.argv.slice(2));
|
|
10
|
+
|
|
11
|
+
if (args.help) usage(0);
|
|
12
|
+
|
|
13
|
+
if (args.command === "init") {
|
|
14
|
+
await init(args);
|
|
15
|
+
} else {
|
|
16
|
+
await generate(args);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function generate(args) {
|
|
20
|
+
if (!args.schema || !args.out) usage(1);
|
|
21
|
+
|
|
22
|
+
const schemaPath = resolve(args.schema);
|
|
23
|
+
const mod = await import(pathToFileURL(schemaPath).href);
|
|
24
|
+
const schema = mod.default || mod.schema;
|
|
25
|
+
|
|
26
|
+
if (!schema) {
|
|
27
|
+
throw new Error(`No default export or named "schema" export found in ${schemaPath}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const outPath = resolve(args.out);
|
|
31
|
+
const { generateExample, generateTypes, generateValidator } = await import("./compiler.js");
|
|
32
|
+
await mkdir(dirname(outPath), { recursive: true });
|
|
33
|
+
const options = { functionName: args.functionName, processDefault: args.processDefault, minify: args.minify, failFast: args.failFast, optimize: args.optimize };
|
|
34
|
+
await writeOutput(outPath, generateValidator(schema, options), args.force);
|
|
35
|
+
|
|
36
|
+
if (args.types) {
|
|
37
|
+
const typesPath = resolve(args.types);
|
|
38
|
+
await mkdir(dirname(typesPath), { recursive: true });
|
|
39
|
+
await writeOutput(typesPath, generateTypes(schema, options), args.force);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (args.example) {
|
|
43
|
+
const examplePath = resolve(args.example);
|
|
44
|
+
await mkdir(dirname(examplePath), { recursive: true });
|
|
45
|
+
await writeOutput(examplePath, generateExample(schema), args.force);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function init(args) {
|
|
50
|
+
if (!args.schema) usage(1);
|
|
51
|
+
const target = args.target || "node";
|
|
52
|
+
const source = template(target);
|
|
53
|
+
const schemaPath = resolve(args.schema);
|
|
54
|
+
await mkdir(dirname(schemaPath), { recursive: true });
|
|
55
|
+
await writeFile(schemaPath, source, { encoding: "utf8", flag: "wx" });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parseArgs(argv) {
|
|
59
|
+
const out = { command: "generate", functionName: "loadEnv" };
|
|
60
|
+
if (argv[0] === "generate" || argv[0] === "init") out.command = argv.shift();
|
|
61
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
62
|
+
const arg = argv[i];
|
|
63
|
+
if (arg === "--help" || arg === "-h") out.help = true;
|
|
64
|
+
else if (arg === "--schema") out.schema = argv[++i];
|
|
65
|
+
else if (arg === "--target") out.target = argv[++i];
|
|
66
|
+
else if (arg === "--out") out.out = argv[++i];
|
|
67
|
+
else if (arg === "--types") out.types = argv[++i];
|
|
68
|
+
else if (arg === "--example") out.example = argv[++i];
|
|
69
|
+
else if (arg === "--function-name") out.functionName = argv[++i];
|
|
70
|
+
else if (arg === "--no-process-default") out.processDefault = false;
|
|
71
|
+
else if (arg === "--minify") out.minify = true;
|
|
72
|
+
else if (arg === "--fail-fast") out.failFast = true;
|
|
73
|
+
else if (arg === "--force") out.force = true;
|
|
74
|
+
else if (arg === "--optimize") out.optimize = argv[++i];
|
|
75
|
+
else throw new Error(`Unknown argument: ${arg}`);
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function writeOutput(path, source, force) {
|
|
81
|
+
const flags = constants.O_WRONLY | constants.O_CREAT | NOFOLLOW | (force ? constants.O_TRUNC : constants.O_EXCL);
|
|
82
|
+
let file;
|
|
83
|
+
try {
|
|
84
|
+
file = await open(path, flags, 0o666);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error.code === "EEXIST") throw new Error(`${path} already exists; pass --force to overwrite`);
|
|
87
|
+
if (error.code === "ELOOP") throw new Error(`${path} is a symlink; refusing to write`);
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
await file.writeFile(source, "utf8");
|
|
92
|
+
} finally {
|
|
93
|
+
await file.close();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function template(target) {
|
|
98
|
+
if (target === "node") return `import { bool, defineEnv, int, str } from "celery-env";
|
|
99
|
+
|
|
100
|
+
export default defineEnv({
|
|
101
|
+
NODE_ENV: str({ default: "development", desc: "Current runtime environment." }),
|
|
102
|
+
DATABASE_URL: str({ min: 1, desc: "Primary database connection string.", example: "postgres://user:pass@localhost:5432/app" }),
|
|
103
|
+
PORT: int({ default: 3000, min: 1, max: 65535 }),
|
|
104
|
+
DEBUG: bool({ default: false })
|
|
105
|
+
});
|
|
106
|
+
`;
|
|
107
|
+
if (target === "next") return `import { bool, defineEnv, str } from "celery-env";
|
|
108
|
+
|
|
109
|
+
export default defineEnv({
|
|
110
|
+
NODE_ENV: str({ default: "development" }),
|
|
111
|
+
DATABASE_URL: str({ min: 1, desc: "Server-only database connection string." }),
|
|
112
|
+
NEXT_PUBLIC_API_URL: str({ min: 1, startsWith: "https://", desc: "Browser-visible API origin.", example: "https://api.example.com" }),
|
|
113
|
+
NEXT_PUBLIC_ENABLE_ANALYTICS: bool({ default: false })
|
|
114
|
+
});
|
|
115
|
+
`;
|
|
116
|
+
if (target === "vite") return `import { bool, defineEnv, str } from "celery-env";
|
|
117
|
+
|
|
118
|
+
export default defineEnv({
|
|
119
|
+
MODE: str({ default: "development" }),
|
|
120
|
+
VITE_API_URL: str({ min: 1, startsWith: "https://", desc: "Browser-visible API origin.", example: "https://api.example.com" }),
|
|
121
|
+
VITE_ENABLE_SEARCH: bool({ default: false })
|
|
122
|
+
});
|
|
123
|
+
`;
|
|
124
|
+
throw new Error(`Unknown init target: ${target}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function usage(code) {
|
|
128
|
+
console.log(`Usage:
|
|
129
|
+
celery-env --schema env.schema.mjs --out src/env.mjs [--types src/env.d.ts]
|
|
130
|
+
celery-env generate --schema env.schema.mjs --out src/env.mjs [--types src/env.d.ts] [--example .env.example] [--force] [--optimize speed]
|
|
131
|
+
celery-env init --target node|next|vite --schema env.schema.mjs`);
|
|
132
|
+
process.exit(code);
|
|
133
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Spec } from "./index.js";
|
|
2
|
+
export type GenerateValidatorOptions = { functionName?: string; processDefault?: boolean; minify?: boolean; failFast?: boolean; optimize?: "default" | "speed"; splitLarge?: boolean; splitLargeThreshold?: number };
|
|
3
|
+
export type GenerateJsonSchemaOptions = { title?: string; additionalProperties?: boolean };
|
|
4
|
+
export function generateValidator(schema: Record<string, Spec<unknown>>, options?: GenerateValidatorOptions): string;
|
|
5
|
+
export function generateTypes(schema: Record<string, Spec<unknown>>, options?: { functionName?: string; processDefault?: boolean }): string;
|
|
6
|
+
export function generateExample(schema: Record<string, Spec<unknown>>): string;
|
|
7
|
+
export function generateJsonSchema(schema: Record<string, Spec<unknown>>, options?: GenerateJsonSchemaOptions): Record<string, unknown>;
|