@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/dist/index.d.cts CHANGED
@@ -210,21 +210,21 @@ declare function generateEnvValidation(parsed: ParsedEnvFile): string;
210
210
  * import { z } from "zod";
211
211
  *
212
212
  * export const serverEnvSchema = z.object({
213
- * DATABASE_URL: z.string().url(),
213
+ * DATABASE_URL: z.url(),
214
214
  * PORT: z.coerce.number(),
215
215
  * });
216
216
  *
217
217
  * export const clientEnvSchema = z.object({
218
- * NEXT_PUBLIC_API_URL: z.string().url(),
218
+ * NEXT_PUBLIC_API_URL: z.url(),
219
219
  * });
220
220
  *
221
- * export const envSchema = serverEnvSchema.merge(clientEnvSchema);
221
+ * export const envSchema = z.object({ ...serverEnvSchema.shape, ...clientEnvSchema.shape });
222
222
  * export type Env = z.infer<typeof envSchema>;
223
223
  * ```
224
224
  *
225
225
  * - Server-side vars (non-`NEXT_PUBLIC_`) go into `serverEnvSchema`
226
226
  * - Client-side vars (`NEXT_PUBLIC_` prefix) go into `clientEnvSchema`
227
- * - `envSchema` is the merged union of both, for full-stack validation
227
+ * - `envSchema` merges both shapes via object spread to avoid deprecated `.merge()`
228
228
  * - `annotatedType` takes precedence over `inferredType` when both are set
229
229
  * - Optional vars have `.optional()` appended to their Zod expression
230
230
  */
@@ -666,7 +666,7 @@ type EnvTypegenConfig = {
666
666
  declare function defineConfig(config: EnvTypegenConfig): EnvTypegenConfig;
667
667
  /**
668
668
  * Loads env-typegen config by searching for a config file in `cwd`.
669
- * Searches for env-typegen.config.ts → .mjs → .js in order.
669
+ * Searches for env-typegen.config.mjs → .js in order.
670
670
  * Returns `undefined` when no config file is found.
671
671
  */
672
672
  declare function loadConfig(cwd?: string): Promise<EnvTypegenConfig | undefined>;
package/dist/index.d.ts CHANGED
@@ -210,21 +210,21 @@ declare function generateEnvValidation(parsed: ParsedEnvFile): string;
210
210
  * import { z } from "zod";
211
211
  *
212
212
  * export const serverEnvSchema = z.object({
213
- * DATABASE_URL: z.string().url(),
213
+ * DATABASE_URL: z.url(),
214
214
  * PORT: z.coerce.number(),
215
215
  * });
216
216
  *
217
217
  * export const clientEnvSchema = z.object({
218
- * NEXT_PUBLIC_API_URL: z.string().url(),
218
+ * NEXT_PUBLIC_API_URL: z.url(),
219
219
  * });
220
220
  *
221
- * export const envSchema = serverEnvSchema.merge(clientEnvSchema);
221
+ * export const envSchema = z.object({ ...serverEnvSchema.shape, ...clientEnvSchema.shape });
222
222
  * export type Env = z.infer<typeof envSchema>;
223
223
  * ```
224
224
  *
225
225
  * - Server-side vars (non-`NEXT_PUBLIC_`) go into `serverEnvSchema`
226
226
  * - Client-side vars (`NEXT_PUBLIC_` prefix) go into `clientEnvSchema`
227
- * - `envSchema` is the merged union of both, for full-stack validation
227
+ * - `envSchema` merges both shapes via object spread to avoid deprecated `.merge()`
228
228
  * - `annotatedType` takes precedence over `inferredType` when both are set
229
229
  * - Optional vars have `.optional()` appended to their Zod expression
230
230
  */
@@ -666,7 +666,7 @@ type EnvTypegenConfig = {
666
666
  declare function defineConfig(config: EnvTypegenConfig): EnvTypegenConfig;
667
667
  /**
668
668
  * Loads env-typegen config by searching for a config file in `cwd`.
669
- * Searches for env-typegen.config.ts → .mjs → .js in order.
669
+ * Searches for env-typegen.config.mjs → .js in order.
670
670
  * Returns `undefined` when no config file is found.
671
671
  */
672
672
  declare function loadConfig(cwd?: string): Promise<EnvTypegenConfig | undefined>;
package/dist/index.js CHANGED
@@ -400,8 +400,8 @@ function generateEnvValidation(parsed) {
400
400
  function toZodType(envVarType) {
401
401
  if (envVarType === "number") return "z.coerce.number()";
402
402
  if (envVarType === "boolean") return "z.coerce.boolean()";
403
- if (envVarType === "url") return "z.string().url()";
404
- if (envVarType === "email") return "z.string().email()";
403
+ if (envVarType === "url") return "z.url()";
404
+ if (envVarType === "email") return "z.email()";
405
405
  return "z.string()";
406
406
  }
407
407
  function generateZodSchema(parsed) {
@@ -428,7 +428,10 @@ function generateZodSchema(parsed) {
428
428
  lines.push(
429
429
  "});",
430
430
  "",
431
- "export const envSchema = serverEnvSchema.merge(clientEnvSchema);",
431
+ "export const envSchema = z.object({",
432
+ " ...serverEnvSchema.shape,",
433
+ " ...clientEnvSchema.shape,",
434
+ "});",
432
435
  "export type Env = z.infer<typeof envSchema>;"
433
436
  );
434
437
  return lines.join("\n") + "\n";
@@ -467,8 +470,8 @@ function escapeJsStringLiteral(value) {
467
470
  function toT3ZodType(envVarType) {
468
471
  if (envVarType === "number") return "z.coerce.number()";
469
472
  if (envVarType === "boolean") return "z.coerce.boolean()";
470
- if (envVarType === "url") return "z.string().url()";
471
- if (envVarType === "email") return "z.string().email()";
473
+ if (envVarType === "url") return "z.url()";
474
+ if (envVarType === "email") return "z.email()";
472
475
  return "z.string()";
473
476
  }
474
477
  function buildZodExpr(variable) {
@@ -539,11 +542,7 @@ Rename it to ${filePath.replace(/\.ts$/, ".mjs")} and use ESM syntax (export def
539
542
  }
540
543
  return void 0;
541
544
  }
542
- var CONFIG_FILE_NAMES = [
543
- "env-typegen.config.mjs",
544
- "env-typegen.config.js",
545
- "env-typegen.config.ts"
546
- ];
545
+ var CONFIG_FILE_NAMES = ["env-typegen.config.mjs", "env-typegen.config.js"];
547
546
  function defineConfig(config) {
548
547
  return config;
549
548
  }
@@ -551,19 +550,6 @@ async function loadConfig(cwd = process.cwd()) {
551
550
  for (const name of CONFIG_FILE_NAMES) {
552
551
  const filePath = path6.resolve(cwd, name);
553
552
  if (existsSync(filePath)) {
554
- if (filePath.endsWith(".ts")) {
555
- throw new Error(
556
- `Config file "${name}" was found but TypeScript files cannot be loaded directly at runtime.
557
- Rename it to "env-typegen.config.mjs" and use ESM export syntax:
558
-
559
- // env-typegen.config.mjs
560
- import { defineConfig } from "@xlameiro/env-typegen";
561
- export default defineConfig({ input: ".env.example" });
562
-
563
- Tip: keep env-typegen.config.ts for IDE autocompletion and create a sibling
564
- env-typegen.config.mjs for runtime loading.`
565
- );
566
- }
567
553
  const fileUrl = pathToFileURL(filePath).href;
568
554
  const mod = await import(fileUrl);
569
555
  return mod.default;
@@ -1840,7 +1826,7 @@ var HELP_TEXT = {
1840
1826
  "Usage: env-typegen diff [options]",
1841
1827
  "",
1842
1828
  "Options:",
1843
- " --targets <list> Comma-separated targets (default: .env,.env.example,.env.production)",
1829
+ " --targets <list> Comma-separated targets (default: .env,.env.production)",
1844
1830
  " --contract <path> Contract file path (default: env.contract.ts)",
1845
1831
  " --example <path> Fallback .env.example used to bootstrap contract",
1846
1832
  " --strict Validate extras as errors (default: true)",
@@ -1864,7 +1850,7 @@ var HELP_TEXT = {
1864
1850
  "",
1865
1851
  "Options:",
1866
1852
  " --env <path> Environment file to validate (default: .env)",
1867
- " --targets <list> Comma-separated targets for drift analysis",
1853
+ " --targets <list> Comma-separated targets for drift analysis (default: .env,.env.production)",
1868
1854
  " --contract <path> Contract file path (default: env.contract.ts)",
1869
1855
  " --example <path> Fallback .env.example used to bootstrap contract",
1870
1856
  " --strict Validate extras as errors (default: true)",
@@ -1997,7 +1983,58 @@ function parseTargets(values, fileConfig) {
1997
1983
  if (fileConfig?.diffTargets !== void 0 && fileConfig.diffTargets.length > 0) {
1998
1984
  return fileConfig.diffTargets;
1999
1985
  }
2000
- return [".env", ".env.example", ".env.production"];
1986
+ return [".env", ".env.production"];
1987
+ }
1988
+ function splitExistingAndMissingTargets(targets) {
1989
+ const existingTargets = [];
1990
+ const missingTargets = [];
1991
+ for (const target of targets) {
1992
+ const resolvedTarget = path6.resolve(target);
1993
+ if (existsSync(resolvedTarget)) {
1994
+ existingTargets.push(target);
1995
+ continue;
1996
+ }
1997
+ missingTargets.push(target);
1998
+ }
1999
+ return { existingTargets, missingTargets };
2000
+ }
2001
+ function buildMissingTargetIssue(target, missingCount) {
2002
+ const suffix = missingCount === 1 ? "" : "s";
2003
+ return {
2004
+ code: "ENV_MISSING",
2005
+ type: "missing",
2006
+ severity: "error",
2007
+ key: "*",
2008
+ environment: target,
2009
+ message: `Target file ${target} was not found; treating as empty (${missingCount} missing variable${suffix}).`,
2010
+ value: null
2011
+ };
2012
+ }
2013
+ function summarizeDoctorMissingTargetIssues(report, missingTargets) {
2014
+ if (missingTargets.length === 0) return report;
2015
+ const missingTargetSet = new Set(missingTargets);
2016
+ const retainedIssues = report.issues.filter(
2017
+ (issue) => !(issue.code === "ENV_MISSING" && missingTargetSet.has(issue.environment))
2018
+ );
2019
+ const groupedIssues = missingTargets.map((target) => {
2020
+ const missingCount = report.issues.filter(
2021
+ (issue) => issue.code === "ENV_MISSING" && issue.environment === target
2022
+ ).length;
2023
+ return buildMissingTargetIssue(target, missingCount);
2024
+ });
2025
+ const issues = [...retainedIssues, ...groupedIssues];
2026
+ const errors = issues.filter((issue) => issue.severity === "error").length;
2027
+ const warnings = issues.filter((issue) => issue.severity === "warning").length;
2028
+ return {
2029
+ ...report,
2030
+ status: errors > 0 ? "fail" : "ok",
2031
+ issues,
2032
+ summary: {
2033
+ errors,
2034
+ warnings,
2035
+ total: issues.length
2036
+ }
2037
+ };
2001
2038
  }
2002
2039
  async function prepareCommonContext(values) {
2003
2040
  const fileConfig = await loadCommandConfig(values.config);
@@ -2082,8 +2119,13 @@ async function runDiffCommand(args) {
2082
2119
  await loadValidationContract(loadContractOptions),
2083
2120
  context.plugins
2084
2121
  );
2122
+ const targets = parseTargets(args.values, context.fileConfig);
2123
+ const { existingTargets, missingTargets } = splitExistingAndMissingTargets(targets);
2124
+ for (const missingTarget of missingTargets) {
2125
+ warn(`Target file not found: ${missingTarget} \u2014 treating as empty`);
2126
+ }
2085
2127
  const sources = {};
2086
- for (const target of parseTargets(args.values, context.fileConfig)) {
2128
+ for (const target of existingTargets) {
2087
2129
  const values = await loadEnvSource({ filePath: target, allowMissing: true });
2088
2130
  sources[target] = applySourcePlugins({ environment: target, values }, context.plugins);
2089
2131
  }
@@ -2137,8 +2179,13 @@ async function runDoctorCommand(args) {
2137
2179
  strict: context.strict,
2138
2180
  debugValues: context.debugValues
2139
2181
  });
2182
+ const targets = parseTargets(args.values, context.fileConfig);
2183
+ const { existingTargets, missingTargets } = splitExistingAndMissingTargets(targets);
2184
+ for (const missingTarget of missingTargets) {
2185
+ warn(`Target file not found: ${missingTarget} \u2014 treating as empty`);
2186
+ }
2140
2187
  const sources = {};
2141
- for (const target of parseTargets(args.values, context.fileConfig)) {
2188
+ for (const target of existingTargets) {
2142
2189
  const values = await loadEnvSource({ filePath: target, allowMissing: true });
2143
2190
  sources[target] = applySourcePlugins({ environment: target, values }, context.plugins);
2144
2191
  }
@@ -2160,10 +2207,9 @@ async function runDoctorCommand(args) {
2160
2207
  strict: context.strict,
2161
2208
  debugValues: context.debugValues
2162
2209
  });
2163
- const report = applyReportPlugins(
2164
- buildDoctorReport({ checkReport, diffReport }),
2165
- context.plugins
2166
- );
2210
+ const rawReport = buildDoctorReport({ checkReport, diffReport });
2211
+ const summarizedReport = summarizeDoctorMissingTargetIssues(rawReport, missingTargets);
2212
+ const report = applyReportPlugins(summarizedReport, context.plugins);
2167
2213
  return emitAndReturnExitCode(report, {
2168
2214
  jsonMode: args.jsonMode,
2169
2215
  ...context.outputFile !== void 0 && { outputFile: context.outputFile }