@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.cjs CHANGED
@@ -164,7 +164,9 @@ var inferenceRules = [
164
164
  {
165
165
  id: "P10_url_scheme",
166
166
  priority: 10,
167
- match: (_key, value) => /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value),
167
+ // BUG-02: comma-separated URL lists (e.g. ALLOWED_ORIGINS) must NOT be inferred as
168
+ // a single URL — they don't pass z.string().url() or new URL() validation at runtime.
169
+ match: (_key, value) => !value.includes(",") && /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value),
168
170
  type: "url"
169
171
  },
170
172
  {
@@ -292,7 +294,16 @@ function parseEnvFileContent(content, filePath, options) {
292
294
  );
293
295
  commentBlock = [];
294
296
  }
295
- return { filePath, vars, groups };
297
+ const seenKeys = /* @__PURE__ */ new Set();
298
+ const deduped = [];
299
+ for (let i = vars.length - 1; i >= 0; i--) {
300
+ const variable = vars[i];
301
+ if (variable !== void 0 && !seenKeys.has(variable.key)) {
302
+ seenKeys.add(variable.key);
303
+ deduped.unshift(variable);
304
+ }
305
+ }
306
+ return { filePath, vars: deduped, groups };
296
307
  }
297
308
  function parseEnvFile(filePath) {
298
309
  const content = fs.readFileSync(filePath, "utf8");
@@ -493,9 +504,9 @@ function generateT3Env(parsed) {
493
504
  return lines.join("\n") + "\n";
494
505
  }
495
506
  var CONTRACT_FILE_NAMES = [
496
- "env.contract.ts",
497
507
  "env.contract.mjs",
498
- "env.contract.js"
508
+ "env.contract.js",
509
+ "env.contract.ts"
499
510
  ];
500
511
  function defineContract(contract) {
501
512
  return contract;
@@ -504,6 +515,12 @@ async function loadContract(cwd = process.cwd()) {
504
515
  for (const name of CONTRACT_FILE_NAMES) {
505
516
  const filePath = path6__default.default.resolve(cwd, name);
506
517
  if (fs.existsSync(filePath)) {
518
+ if (filePath.endsWith(".ts")) {
519
+ throw new Error(
520
+ `Contract file ${filePath} is a TypeScript file and cannot be loaded at runtime by Node.js.
521
+ Rename it to ${filePath.replace(/\.ts$/, ".mjs")} and use ESM syntax (export default ...), or run via \`tsx\` / \`ts-node\` as a loader.`
522
+ );
523
+ }
507
524
  const fileUrl = url.pathToFileURL(filePath).href;
508
525
  const mod = await import(fileUrl);
509
526
  return mod.default;
@@ -586,7 +603,14 @@ function success(message) {
586
603
 
587
604
  // src/pipeline.ts
588
605
  function deriveOutputPath(base, generator, isSingle) {
589
- if (isSingle) return base;
606
+ if (isSingle) {
607
+ if (generator === "declaration" && !base.endsWith(".d.ts")) {
608
+ const ext2 = path6__default.default.extname(base);
609
+ const noExt2 = ext2.length > 0 ? base.slice(0, -ext2.length) : base;
610
+ return `${noExt2}.d.ts`;
611
+ }
612
+ return base;
613
+ }
590
614
  const ext = path6__default.default.extname(base);
591
615
  const noExt = ext.length > 0 ? base.slice(0, -ext.length) : base;
592
616
  const baseExt = ext.length > 0 ? ext : ".ts";
@@ -731,7 +755,8 @@ function checkInvalidType(variable, entry, environment) {
731
755
  break;
732
756
  }
733
757
  case "boolean": {
734
- if (value !== "true" && value !== "false") {
758
+ const lower = value.toLowerCase();
759
+ if (!["true", "false", "1", "0", "yes", "no"].includes(lower)) {
735
760
  return {
736
761
  code: "ENV_INVALID_TYPE",
737
762
  key: variable.key,
@@ -854,7 +879,8 @@ function validateContract(parsed, contract, opts = {}) {
854
879
  const parsedByKey = new Map(parsed.vars.map((v) => [v.key, v]));
855
880
  for (const entry of contract.vars) {
856
881
  if (!entry.required) continue;
857
- if (parsedByKey.has(entry.name)) continue;
882
+ const existing = parsedByKey.get(entry.name);
883
+ if (existing !== void 0 && existing.rawValue !== "") continue;
858
884
  issues.push({
859
885
  code: "ENV_MISSING",
860
886
  key: entry.name,
@@ -1048,7 +1074,7 @@ async function loadCloudSource(options) {
1048
1074
  return parseProviderPayload(options.provider, parsed);
1049
1075
  }
1050
1076
  var SECRET_KEY_RE = /(SECRET|TOKEN|PASSWORD|PRIVATE|API_KEY|ACCESS_KEY|CLIENT_SECRET)/i;
1051
- var CONTRACT_FILE_NAMES2 = ["env.contract.ts", "env.contract.mjs", "env.contract.js"];
1077
+ var CONTRACT_FILE_NAMES2 = ["env.contract.mjs", "env.contract.js", "env.contract.ts"];
1052
1078
  function isRecord2(value) {
1053
1079
  return typeof value === "object" && value !== null && !Array.isArray(value);
1054
1080
  }
@@ -1172,6 +1198,12 @@ async function loadValidationContract(options) {
1172
1198
  const discoveredContractPath = findDefaultContractPath(cwd);
1173
1199
  const resolvedContractPath = contractPath === void 0 ? discoveredContractPath : path6__default.default.resolve(cwd, contractPath);
1174
1200
  if (resolvedContractPath !== void 0 && fs.existsSync(resolvedContractPath)) {
1201
+ if (resolvedContractPath.endsWith(".ts")) {
1202
+ throw new Error(
1203
+ `Contract file ${resolvedContractPath} is a TypeScript file and cannot be loaded at runtime by Node.js.
1204
+ Rename it to ${resolvedContractPath.replace(/\.ts$/, ".mjs")} and use ESM syntax (export default ...), or run via \`tsx\` / \`ts-node\` as a loader.`
1205
+ );
1206
+ }
1175
1207
  const moduleUrl = url.pathToFileURL(resolvedContractPath).href;
1176
1208
  const moduleValue = await import(moduleUrl);
1177
1209
  const candidate = moduleValue.default ?? moduleValue.contract;
@@ -1425,7 +1457,7 @@ function isClientSecret(variable, key) {
1425
1457
  function checkContractVariable(key, variable, context) {
1426
1458
  const { options, issues } = context;
1427
1459
  const value = options.values[key];
1428
- const hasValue = value !== void 0;
1460
+ const hasValue = value !== void 0 && (value !== "" || !variable.required);
1429
1461
  if (variable.required && !hasValue) {
1430
1462
  issues.push(
1431
1463
  createIssue({
@@ -1527,7 +1559,11 @@ function diffTypeConflicts(key, present, context) {
1527
1559
  for (const entry of present) {
1528
1560
  typeBySource.set(entry.sourceName, detectReceivedType(entry.value ?? ""));
1529
1561
  }
1530
- if (new Set(typeBySource.values()).size <= 1) return;
1562
+ const knownTypes = /* @__PURE__ */ new Set();
1563
+ for (const t of typeBySource.values()) {
1564
+ if (t !== "unknown") knownTypes.add(t);
1565
+ }
1566
+ if (knownTypes.size <= 1) return;
1531
1567
  for (const [sourceName, detectedType] of typeBySource.entries()) {
1532
1568
  issues.push(
1533
1569
  createIssue({
@@ -1694,6 +1730,7 @@ async function loadEnvSource(options) {
1694
1730
  return parseEnvSourceContent(content);
1695
1731
  } catch (error_) {
1696
1732
  if (options.allowMissing === true && error_ instanceof Error && "code" in error_ && error_.code === "ENOENT") {
1733
+ warn(`Target file not found: ${options.filePath} \u2014 treating as empty`);
1697
1734
  return {};
1698
1735
  }
1699
1736
  throw error_;