@xlameiro/env-typegen 0.1.7 → 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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.9
4
+
5
+ ### Patch Changes
6
+
7
+ - ddf2ea1: ## Fuzzy Dancers Find — env-typegen QA deficiency fixes (D1-D12)
8
+
9
+ ## 0.1.8
10
+
11
+ ### Patch Changes
12
+
13
+ - c339449: ## Fuzzy Dancers Find — env-typegen QA deficiency fixes (D1-D12)
14
+
3
15
  ## 0.1.7
4
16
 
5
17
  ### Patch Changes
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.string().url()";
182
- if (envVarType === "email") return "z.string().email()";
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.string().url()";
288
- if (envVarType === "email") return "z.string().email()";
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 = serverEnvSchema.merge(clientEnvSchema);",
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.example,.env.production)",
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.example", ".env.production"];
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 parseTargets(args.values, context.fileConfig)) {
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 parseTargets(args.values, context.fileConfig)) {
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 report = applyReportPlugins(
1960
- buildDoctorReport({ checkReport, diffReport }),
1961
- context.plugins
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 \u2192 .ts (in cwd)",
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
- async function runCli(argv = process.argv.slice(2)) {
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
- argv = argv.slice(1);
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 !== void 0 && VALIDATION_SUBCOMMANDS.has(maybeSubcommand)) {
2166
- await runValidationSubcommand(maybeSubcommand, argv.slice(1));
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: argv,
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
- let fileConfig;
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.ts");
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";