@xlameiro/env-typegen 0.1.5 → 0.1.6

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
@@ -461,9 +461,12 @@ type EnvContract$1 = {
461
461
 
462
462
  /**
463
463
  * Contract file names searched in order when calling {@link loadContract}.
464
- * Mirrors the search strategy used by `loadConfig` in `config.ts`.
464
+ * BUG-01: .mjs and .js are searched before .ts — TypeScript files cannot be
465
+ * loaded by Node.js import() at runtime without a loader like tsx or ts-node.
466
+ * Discovering a .mjs file first prevents a confusing ERR_UNKNOWN_FILE_EXTENSION
467
+ * failure when both .ts and .mjs files coexist in the same directory.
465
468
  */
466
- declare const CONTRACT_FILE_NAMES: readonly ["env.contract.ts", "env.contract.mjs", "env.contract.js"];
469
+ declare const CONTRACT_FILE_NAMES: readonly ["env.contract.mjs", "env.contract.js", "env.contract.ts"];
467
470
  /**
468
471
  * Type-safe contract factory. Returns the contract object unchanged — exists
469
472
  * purely for IDE autocompletion and compile-time validation of the contract shape.
package/dist/index.d.ts CHANGED
@@ -461,9 +461,12 @@ type EnvContract$1 = {
461
461
 
462
462
  /**
463
463
  * Contract file names searched in order when calling {@link loadContract}.
464
- * Mirrors the search strategy used by `loadConfig` in `config.ts`.
464
+ * BUG-01: .mjs and .js are searched before .ts — TypeScript files cannot be
465
+ * loaded by Node.js import() at runtime without a loader like tsx or ts-node.
466
+ * Discovering a .mjs file first prevents a confusing ERR_UNKNOWN_FILE_EXTENSION
467
+ * failure when both .ts and .mjs files coexist in the same directory.
465
468
  */
466
- declare const CONTRACT_FILE_NAMES: readonly ["env.contract.ts", "env.contract.mjs", "env.contract.js"];
469
+ declare const CONTRACT_FILE_NAMES: readonly ["env.contract.mjs", "env.contract.js", "env.contract.ts"];
467
470
  /**
468
471
  * Type-safe contract factory. Returns the contract object unchanged — exists
469
472
  * purely for IDE autocompletion and compile-time validation of the contract shape.
package/dist/index.js CHANGED
@@ -157,7 +157,9 @@ var inferenceRules = [
157
157
  {
158
158
  id: "P10_url_scheme",
159
159
  priority: 10,
160
- match: (_key, value) => /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value),
160
+ // BUG-02: comma-separated URL lists (e.g. ALLOWED_ORIGINS) must NOT be inferred as
161
+ // a single URL — they don't pass z.string().url() or new URL() validation at runtime.
162
+ match: (_key, value) => !value.includes(",") && /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value),
161
163
  type: "url"
162
164
  },
163
165
  {
@@ -285,7 +287,16 @@ function parseEnvFileContent(content, filePath, options) {
285
287
  );
286
288
  commentBlock = [];
287
289
  }
288
- return { filePath, vars, groups };
290
+ const seenKeys = /* @__PURE__ */ new Set();
291
+ const deduped = [];
292
+ for (let i = vars.length - 1; i >= 0; i--) {
293
+ const variable = vars[i];
294
+ if (variable !== void 0 && !seenKeys.has(variable.key)) {
295
+ seenKeys.add(variable.key);
296
+ deduped.unshift(variable);
297
+ }
298
+ }
299
+ return { filePath, vars: deduped, groups };
289
300
  }
290
301
  function parseEnvFile(filePath) {
291
302
  const content = readFileSync(filePath, "utf8");
@@ -486,9 +497,9 @@ function generateT3Env(parsed) {
486
497
  return lines.join("\n") + "\n";
487
498
  }
488
499
  var CONTRACT_FILE_NAMES = [
489
- "env.contract.ts",
490
500
  "env.contract.mjs",
491
- "env.contract.js"
501
+ "env.contract.js",
502
+ "env.contract.ts"
492
503
  ];
493
504
  function defineContract(contract) {
494
505
  return contract;
@@ -497,6 +508,12 @@ async function loadContract(cwd = process.cwd()) {
497
508
  for (const name of CONTRACT_FILE_NAMES) {
498
509
  const filePath = path6.resolve(cwd, name);
499
510
  if (existsSync(filePath)) {
511
+ if (filePath.endsWith(".ts")) {
512
+ throw new Error(
513
+ `Contract file ${filePath} is a TypeScript file and cannot be loaded at runtime by Node.js.
514
+ Rename it to ${filePath.replace(/\.ts$/, ".mjs")} and use ESM syntax (export default ...), or run via \`tsx\` / \`ts-node\` as a loader.`
515
+ );
516
+ }
500
517
  const fileUrl = pathToFileURL(filePath).href;
501
518
  const mod = await import(fileUrl);
502
519
  return mod.default;
@@ -579,7 +596,14 @@ function success(message) {
579
596
 
580
597
  // src/pipeline.ts
581
598
  function deriveOutputPath(base, generator, isSingle) {
582
- if (isSingle) return base;
599
+ if (isSingle) {
600
+ if (generator === "declaration" && !base.endsWith(".d.ts")) {
601
+ const ext2 = path6.extname(base);
602
+ const noExt2 = ext2.length > 0 ? base.slice(0, -ext2.length) : base;
603
+ return `${noExt2}.d.ts`;
604
+ }
605
+ return base;
606
+ }
583
607
  const ext = path6.extname(base);
584
608
  const noExt = ext.length > 0 ? base.slice(0, -ext.length) : base;
585
609
  const baseExt = ext.length > 0 ? ext : ".ts";
@@ -724,7 +748,8 @@ function checkInvalidType(variable, entry, environment) {
724
748
  break;
725
749
  }
726
750
  case "boolean": {
727
- if (value !== "true" && value !== "false") {
751
+ const lower = value.toLowerCase();
752
+ if (!["true", "false", "1", "0", "yes", "no"].includes(lower)) {
728
753
  return {
729
754
  code: "ENV_INVALID_TYPE",
730
755
  key: variable.key,
@@ -847,7 +872,8 @@ function validateContract(parsed, contract, opts = {}) {
847
872
  const parsedByKey = new Map(parsed.vars.map((v) => [v.key, v]));
848
873
  for (const entry of contract.vars) {
849
874
  if (!entry.required) continue;
850
- if (parsedByKey.has(entry.name)) continue;
875
+ const existing = parsedByKey.get(entry.name);
876
+ if (existing !== void 0 && existing.rawValue !== "") continue;
851
877
  issues.push({
852
878
  code: "ENV_MISSING",
853
879
  key: entry.name,
@@ -1041,7 +1067,7 @@ async function loadCloudSource(options) {
1041
1067
  return parseProviderPayload(options.provider, parsed);
1042
1068
  }
1043
1069
  var SECRET_KEY_RE = /(SECRET|TOKEN|PASSWORD|PRIVATE|API_KEY|ACCESS_KEY|CLIENT_SECRET)/i;
1044
- var CONTRACT_FILE_NAMES2 = ["env.contract.ts", "env.contract.mjs", "env.contract.js"];
1070
+ var CONTRACT_FILE_NAMES2 = ["env.contract.mjs", "env.contract.js", "env.contract.ts"];
1045
1071
  function isRecord2(value) {
1046
1072
  return typeof value === "object" && value !== null && !Array.isArray(value);
1047
1073
  }
@@ -1165,6 +1191,12 @@ async function loadValidationContract(options) {
1165
1191
  const discoveredContractPath = findDefaultContractPath(cwd);
1166
1192
  const resolvedContractPath = contractPath === void 0 ? discoveredContractPath : path6.resolve(cwd, contractPath);
1167
1193
  if (resolvedContractPath !== void 0 && existsSync(resolvedContractPath)) {
1194
+ if (resolvedContractPath.endsWith(".ts")) {
1195
+ throw new Error(
1196
+ `Contract file ${resolvedContractPath} is a TypeScript file and cannot be loaded at runtime by Node.js.
1197
+ Rename it to ${resolvedContractPath.replace(/\.ts$/, ".mjs")} and use ESM syntax (export default ...), or run via \`tsx\` / \`ts-node\` as a loader.`
1198
+ );
1199
+ }
1168
1200
  const moduleUrl = pathToFileURL(resolvedContractPath).href;
1169
1201
  const moduleValue = await import(moduleUrl);
1170
1202
  const candidate = moduleValue.default ?? moduleValue.contract;
@@ -1418,7 +1450,7 @@ function isClientSecret(variable, key) {
1418
1450
  function checkContractVariable(key, variable, context) {
1419
1451
  const { options, issues } = context;
1420
1452
  const value = options.values[key];
1421
- const hasValue = value !== void 0;
1453
+ const hasValue = value !== void 0 && (value !== "" || !variable.required);
1422
1454
  if (variable.required && !hasValue) {
1423
1455
  issues.push(
1424
1456
  createIssue({
@@ -1520,7 +1552,11 @@ function diffTypeConflicts(key, present, context) {
1520
1552
  for (const entry of present) {
1521
1553
  typeBySource.set(entry.sourceName, detectReceivedType(entry.value ?? ""));
1522
1554
  }
1523
- if (new Set(typeBySource.values()).size <= 1) return;
1555
+ const knownTypes = /* @__PURE__ */ new Set();
1556
+ for (const t of typeBySource.values()) {
1557
+ if (t !== "unknown") knownTypes.add(t);
1558
+ }
1559
+ if (knownTypes.size <= 1) return;
1524
1560
  for (const [sourceName, detectedType] of typeBySource.entries()) {
1525
1561
  issues.push(
1526
1562
  createIssue({
@@ -1687,6 +1723,7 @@ async function loadEnvSource(options) {
1687
1723
  return parseEnvSourceContent(content);
1688
1724
  } catch (error_) {
1689
1725
  if (options.allowMissing === true && error_ instanceof Error && "code" in error_ && error_.code === "ENOENT") {
1726
+ warn(`Target file not found: ${options.filePath} \u2014 treating as empty`);
1690
1727
  return {};
1691
1728
  }
1692
1729
  throw error_;