@xlameiro/env-typegen 0.1.8 → 0.1.9
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/CHANGELOG.md +6 -0
- package/dist/cli.js +121 -45
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +78 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +78 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/dist/cli.js
CHANGED
|
@@ -109,28 +109,11 @@ import { inspect, parseArgs as parseArgs2 } from "util";
|
|
|
109
109
|
import { existsSync } from "fs";
|
|
110
110
|
import path from "path";
|
|
111
111
|
import { pathToFileURL } from "url";
|
|
112
|
-
var CONFIG_FILE_NAMES = [
|
|
113
|
-
"env-typegen.config.mjs",
|
|
114
|
-
"env-typegen.config.js",
|
|
115
|
-
"env-typegen.config.ts"
|
|
116
|
-
];
|
|
112
|
+
var CONFIG_FILE_NAMES = ["env-typegen.config.mjs", "env-typegen.config.js"];
|
|
117
113
|
async function loadConfig(cwd = process.cwd()) {
|
|
118
114
|
for (const name of CONFIG_FILE_NAMES) {
|
|
119
115
|
const filePath = path.resolve(cwd, name);
|
|
120
116
|
if (existsSync(filePath)) {
|
|
121
|
-
if (filePath.endsWith(".ts")) {
|
|
122
|
-
throw new Error(
|
|
123
|
-
`Config file "${name}" was found but TypeScript files cannot be loaded directly at runtime.
|
|
124
|
-
Rename it to "env-typegen.config.mjs" and use ESM export syntax:
|
|
125
|
-
|
|
126
|
-
// env-typegen.config.mjs
|
|
127
|
-
import { defineConfig } from "@xlameiro/env-typegen";
|
|
128
|
-
export default defineConfig({ input: ".env.example" });
|
|
129
|
-
|
|
130
|
-
Tip: keep env-typegen.config.ts for IDE autocompletion and create a sibling
|
|
131
|
-
env-typegen.config.mjs for runtime loading.`
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
117
|
const fileUrl = pathToFileURL(filePath).href;
|
|
135
118
|
const mod = await import(fileUrl);
|
|
136
119
|
return mod.default;
|
|
@@ -178,8 +161,8 @@ function escapeJsStringLiteral(value) {
|
|
|
178
161
|
function toT3ZodType(envVarType) {
|
|
179
162
|
if (envVarType === "number") return "z.coerce.number()";
|
|
180
163
|
if (envVarType === "boolean") return "z.coerce.boolean()";
|
|
181
|
-
if (envVarType === "url") return "z.
|
|
182
|
-
if (envVarType === "email") return "z.
|
|
164
|
+
if (envVarType === "url") return "z.url()";
|
|
165
|
+
if (envVarType === "email") return "z.email()";
|
|
183
166
|
return "z.string()";
|
|
184
167
|
}
|
|
185
168
|
function buildZodExpr(variable) {
|
|
@@ -284,8 +267,8 @@ function generateTypeScriptTypes(parsed) {
|
|
|
284
267
|
function toZodType(envVarType) {
|
|
285
268
|
if (envVarType === "number") return "z.coerce.number()";
|
|
286
269
|
if (envVarType === "boolean") return "z.coerce.boolean()";
|
|
287
|
-
if (envVarType === "url") return "z.
|
|
288
|
-
if (envVarType === "email") return "z.
|
|
270
|
+
if (envVarType === "url") return "z.url()";
|
|
271
|
+
if (envVarType === "email") return "z.email()";
|
|
289
272
|
return "z.string()";
|
|
290
273
|
}
|
|
291
274
|
function generateZodSchema(parsed) {
|
|
@@ -312,7 +295,10 @@ function generateZodSchema(parsed) {
|
|
|
312
295
|
lines.push(
|
|
313
296
|
"});",
|
|
314
297
|
"",
|
|
315
|
-
"export const envSchema =
|
|
298
|
+
"export const envSchema = z.object({",
|
|
299
|
+
" ...serverEnvSchema.shape,",
|
|
300
|
+
" ...clientEnvSchema.shape,",
|
|
301
|
+
"});",
|
|
316
302
|
"export type Env = z.infer<typeof envSchema>;"
|
|
317
303
|
);
|
|
318
304
|
return lines.join("\n") + "\n";
|
|
@@ -1636,7 +1622,7 @@ var HELP_TEXT = {
|
|
|
1636
1622
|
"Usage: env-typegen diff [options]",
|
|
1637
1623
|
"",
|
|
1638
1624
|
"Options:",
|
|
1639
|
-
" --targets <list> Comma-separated targets (default: .env,.env.
|
|
1625
|
+
" --targets <list> Comma-separated targets (default: .env,.env.production)",
|
|
1640
1626
|
" --contract <path> Contract file path (default: env.contract.ts)",
|
|
1641
1627
|
" --example <path> Fallback .env.example used to bootstrap contract",
|
|
1642
1628
|
" --strict Validate extras as errors (default: true)",
|
|
@@ -1660,7 +1646,7 @@ var HELP_TEXT = {
|
|
|
1660
1646
|
"",
|
|
1661
1647
|
"Options:",
|
|
1662
1648
|
" --env <path> Environment file to validate (default: .env)",
|
|
1663
|
-
" --targets <list> Comma-separated targets for drift analysis",
|
|
1649
|
+
" --targets <list> Comma-separated targets for drift analysis (default: .env,.env.production)",
|
|
1664
1650
|
" --contract <path> Contract file path (default: env.contract.ts)",
|
|
1665
1651
|
" --example <path> Fallback .env.example used to bootstrap contract",
|
|
1666
1652
|
" --strict Validate extras as errors (default: true)",
|
|
@@ -1793,7 +1779,58 @@ function parseTargets(values, fileConfig) {
|
|
|
1793
1779
|
if (fileConfig?.diffTargets !== void 0 && fileConfig.diffTargets.length > 0) {
|
|
1794
1780
|
return fileConfig.diffTargets;
|
|
1795
1781
|
}
|
|
1796
|
-
return [".env", ".env.
|
|
1782
|
+
return [".env", ".env.production"];
|
|
1783
|
+
}
|
|
1784
|
+
function splitExistingAndMissingTargets(targets) {
|
|
1785
|
+
const existingTargets = [];
|
|
1786
|
+
const missingTargets = [];
|
|
1787
|
+
for (const target of targets) {
|
|
1788
|
+
const resolvedTarget = path11.resolve(target);
|
|
1789
|
+
if (existsSync4(resolvedTarget)) {
|
|
1790
|
+
existingTargets.push(target);
|
|
1791
|
+
continue;
|
|
1792
|
+
}
|
|
1793
|
+
missingTargets.push(target);
|
|
1794
|
+
}
|
|
1795
|
+
return { existingTargets, missingTargets };
|
|
1796
|
+
}
|
|
1797
|
+
function buildMissingTargetIssue(target, missingCount) {
|
|
1798
|
+
const suffix = missingCount === 1 ? "" : "s";
|
|
1799
|
+
return {
|
|
1800
|
+
code: "ENV_MISSING",
|
|
1801
|
+
type: "missing",
|
|
1802
|
+
severity: "error",
|
|
1803
|
+
key: "*",
|
|
1804
|
+
environment: target,
|
|
1805
|
+
message: `Target file ${target} was not found; treating as empty (${missingCount} missing variable${suffix}).`,
|
|
1806
|
+
value: null
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
function summarizeDoctorMissingTargetIssues(report, missingTargets) {
|
|
1810
|
+
if (missingTargets.length === 0) return report;
|
|
1811
|
+
const missingTargetSet = new Set(missingTargets);
|
|
1812
|
+
const retainedIssues = report.issues.filter(
|
|
1813
|
+
(issue) => !(issue.code === "ENV_MISSING" && missingTargetSet.has(issue.environment))
|
|
1814
|
+
);
|
|
1815
|
+
const groupedIssues = missingTargets.map((target) => {
|
|
1816
|
+
const missingCount = report.issues.filter(
|
|
1817
|
+
(issue) => issue.code === "ENV_MISSING" && issue.environment === target
|
|
1818
|
+
).length;
|
|
1819
|
+
return buildMissingTargetIssue(target, missingCount);
|
|
1820
|
+
});
|
|
1821
|
+
const issues = [...retainedIssues, ...groupedIssues];
|
|
1822
|
+
const errors = issues.filter((issue) => issue.severity === "error").length;
|
|
1823
|
+
const warnings = issues.filter((issue) => issue.severity === "warning").length;
|
|
1824
|
+
return {
|
|
1825
|
+
...report,
|
|
1826
|
+
status: errors > 0 ? "fail" : "ok",
|
|
1827
|
+
issues,
|
|
1828
|
+
summary: {
|
|
1829
|
+
errors,
|
|
1830
|
+
warnings,
|
|
1831
|
+
total: issues.length
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1797
1834
|
}
|
|
1798
1835
|
async function prepareCommonContext(values) {
|
|
1799
1836
|
const fileConfig = await loadCommandConfig(values.config);
|
|
@@ -1878,8 +1915,13 @@ async function runDiffCommand(args) {
|
|
|
1878
1915
|
await loadValidationContract(loadContractOptions),
|
|
1879
1916
|
context.plugins
|
|
1880
1917
|
);
|
|
1918
|
+
const targets = parseTargets(args.values, context.fileConfig);
|
|
1919
|
+
const { existingTargets, missingTargets } = splitExistingAndMissingTargets(targets);
|
|
1920
|
+
for (const missingTarget of missingTargets) {
|
|
1921
|
+
warn(`Target file not found: ${missingTarget} \u2014 treating as empty`);
|
|
1922
|
+
}
|
|
1881
1923
|
const sources = {};
|
|
1882
|
-
for (const target of
|
|
1924
|
+
for (const target of existingTargets) {
|
|
1883
1925
|
const values = await loadEnvSource({ filePath: target, allowMissing: true });
|
|
1884
1926
|
sources[target] = applySourcePlugins({ environment: target, values }, context.plugins);
|
|
1885
1927
|
}
|
|
@@ -1933,8 +1975,13 @@ async function runDoctorCommand(args) {
|
|
|
1933
1975
|
strict: context.strict,
|
|
1934
1976
|
debugValues: context.debugValues
|
|
1935
1977
|
});
|
|
1978
|
+
const targets = parseTargets(args.values, context.fileConfig);
|
|
1979
|
+
const { existingTargets, missingTargets } = splitExistingAndMissingTargets(targets);
|
|
1980
|
+
for (const missingTarget of missingTargets) {
|
|
1981
|
+
warn(`Target file not found: ${missingTarget} \u2014 treating as empty`);
|
|
1982
|
+
}
|
|
1936
1983
|
const sources = {};
|
|
1937
|
-
for (const target of
|
|
1984
|
+
for (const target of existingTargets) {
|
|
1938
1985
|
const values = await loadEnvSource({ filePath: target, allowMissing: true });
|
|
1939
1986
|
sources[target] = applySourcePlugins({ environment: target, values }, context.plugins);
|
|
1940
1987
|
}
|
|
@@ -1956,10 +2003,9 @@ async function runDoctorCommand(args) {
|
|
|
1956
2003
|
strict: context.strict,
|
|
1957
2004
|
debugValues: context.debugValues
|
|
1958
2005
|
});
|
|
1959
|
-
const
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
);
|
|
2006
|
+
const rawReport = buildDoctorReport({ checkReport, diffReport });
|
|
2007
|
+
const summarizedReport = summarizeDoctorMissingTargetIssues(rawReport, missingTargets);
|
|
2008
|
+
const report = applyReportPlugins(summarizedReport, context.plugins);
|
|
1963
2009
|
return emitAndReturnExitCode(report, {
|
|
1964
2010
|
jsonMode: args.jsonMode,
|
|
1965
2011
|
...context.outputFile !== void 0 && { outputFile: context.outputFile }
|
|
@@ -2085,9 +2131,11 @@ var HELP_TEXT2 = [
|
|
|
2085
2131
|
" -h, --help Show this help",
|
|
2086
2132
|
"",
|
|
2087
2133
|
"Config file:",
|
|
2088
|
-
" Auto-discovered in order: env-typegen.config.mjs \u2192 .js
|
|
2134
|
+
" Auto-discovered in order: env-typegen.config.mjs \u2192 .js (in cwd)",
|
|
2089
2135
|
" CLI flags always override config file values.",
|
|
2090
2136
|
" Use defineConfig() from @xlameiro/env-typegen for IDE autocompletion.",
|
|
2137
|
+
" Note: with multiple --input values, --output basename is ignored;",
|
|
2138
|
+
" outputs are named from each input file stem.",
|
|
2091
2139
|
"",
|
|
2092
2140
|
"Exit codes:",
|
|
2093
2141
|
" 0 Success \u2014 files generated without errors",
|
|
@@ -2157,17 +2205,50 @@ async function loadExplicitConfig(configPath, userPath) {
|
|
|
2157
2205
|
const rawConfig = mod.default;
|
|
2158
2206
|
return rawConfig ? applyConfigPaths2(rawConfig, configDir) : void 0;
|
|
2159
2207
|
}
|
|
2160
|
-
|
|
2208
|
+
function findAutoDiscoveredConfigPath(cwd) {
|
|
2209
|
+
for (const configName of CONFIG_FILE_NAMES) {
|
|
2210
|
+
const resolvedPath = path13.resolve(cwd, configName);
|
|
2211
|
+
if (existsSync6(resolvedPath)) {
|
|
2212
|
+
return resolvedPath;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
return void 0;
|
|
2216
|
+
}
|
|
2217
|
+
function parseGenerateAlias(argv) {
|
|
2161
2218
|
if (argv[0] === "generate") {
|
|
2162
|
-
|
|
2219
|
+
return argv.slice(1);
|
|
2163
2220
|
}
|
|
2221
|
+
return argv;
|
|
2222
|
+
}
|
|
2223
|
+
async function maybeRunValidationSubcommand(argv) {
|
|
2164
2224
|
const maybeSubcommand = argv[0];
|
|
2165
|
-
if (maybeSubcommand
|
|
2166
|
-
|
|
2225
|
+
if (maybeSubcommand === void 0 || !VALIDATION_SUBCOMMANDS.has(maybeSubcommand)) {
|
|
2226
|
+
return false;
|
|
2227
|
+
}
|
|
2228
|
+
await runValidationSubcommand(maybeSubcommand, argv.slice(1));
|
|
2229
|
+
return true;
|
|
2230
|
+
}
|
|
2231
|
+
async function loadCliConfig(values) {
|
|
2232
|
+
if (values.config !== void 0) {
|
|
2233
|
+
return loadExplicitConfig(path13.resolve(values.config), values.config);
|
|
2234
|
+
}
|
|
2235
|
+
const cwd = process.cwd();
|
|
2236
|
+
const fileConfig = await loadConfig(cwd);
|
|
2237
|
+
if (fileConfig !== void 0) {
|
|
2238
|
+
const autoConfigPath = findAutoDiscoveredConfigPath(cwd);
|
|
2239
|
+
if (autoConfigPath !== void 0) {
|
|
2240
|
+
log(`Using config: ${autoConfigPath}`);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
return fileConfig;
|
|
2244
|
+
}
|
|
2245
|
+
async function runCli(argv = process.argv.slice(2)) {
|
|
2246
|
+
const normalizedArgv = parseGenerateAlias(argv);
|
|
2247
|
+
if (await maybeRunValidationSubcommand(normalizedArgv)) {
|
|
2167
2248
|
return;
|
|
2168
2249
|
}
|
|
2169
2250
|
const { values } = parseArgs2({
|
|
2170
|
-
args:
|
|
2251
|
+
args: normalizedArgv,
|
|
2171
2252
|
options: {
|
|
2172
2253
|
input: { type: "string", short: "i", multiple: true },
|
|
2173
2254
|
output: { type: "string", short: "o" },
|
|
@@ -2191,16 +2272,11 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
2191
2272
|
console.log(HELP_TEXT2);
|
|
2192
2273
|
return;
|
|
2193
2274
|
}
|
|
2194
|
-
|
|
2195
|
-
if (values.config === void 0) {
|
|
2196
|
-
fileConfig = await loadConfig(process.cwd());
|
|
2197
|
-
} else {
|
|
2198
|
-
fileConfig = await loadExplicitConfig(path13.resolve(values.config), values.config);
|
|
2199
|
-
}
|
|
2275
|
+
const fileConfig = await loadCliConfig(values);
|
|
2200
2276
|
const cliInput = values.input?.length ? values.input : void 0;
|
|
2201
2277
|
const input = cliInput ?? fileConfig?.input;
|
|
2202
2278
|
if (input === void 0) {
|
|
2203
|
-
error("No input file specified. Use -i <path> or set input in env-typegen.config.
|
|
2279
|
+
error("No input file specified. Use -i <path> or set input in env-typegen.config.mjs");
|
|
2204
2280
|
process.exit(1);
|
|
2205
2281
|
}
|
|
2206
2282
|
const output = values.output ?? fileConfig?.output ?? "env.generated.ts";
|