@xlameiro/env-typegen 0.1.4 → 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
@@ -5,12 +5,13 @@ var path6 = require('path');
5
5
  var url = require('url');
6
6
  var promises = require('fs/promises');
7
7
  var prettier = require('prettier');
8
- var picocolors = require('picocolors');
8
+ var pc = require('picocolors');
9
9
  var util = require('util');
10
10
 
11
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
12
 
13
13
  var path6__default = /*#__PURE__*/_interopDefault(path6);
14
+ var pc__default = /*#__PURE__*/_interopDefault(pc);
14
15
 
15
16
  // src/parser/comment-parser.ts
16
17
  var VALID_ENV_VAR_TYPES = /* @__PURE__ */ new Set([
@@ -163,7 +164,9 @@ var inferenceRules = [
163
164
  {
164
165
  id: "P10_url_scheme",
165
166
  priority: 10,
166
- 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),
167
170
  type: "url"
168
171
  },
169
172
  {
@@ -291,7 +294,16 @@ function parseEnvFileContent(content, filePath, options) {
291
294
  );
292
295
  commentBlock = [];
293
296
  }
294
- 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 };
295
307
  }
296
308
  function parseEnvFile(filePath) {
297
309
  const content = fs.readFileSync(filePath, "utf8");
@@ -492,9 +504,9 @@ function generateT3Env(parsed) {
492
504
  return lines.join("\n") + "\n";
493
505
  }
494
506
  var CONTRACT_FILE_NAMES = [
495
- "env.contract.ts",
496
507
  "env.contract.mjs",
497
- "env.contract.js"
508
+ "env.contract.js",
509
+ "env.contract.ts"
498
510
  ];
499
511
  function defineContract(contract) {
500
512
  return contract;
@@ -503,6 +515,12 @@ async function loadContract(cwd = process.cwd()) {
503
515
  for (const name of CONTRACT_FILE_NAMES) {
504
516
  const filePath = path6__default.default.resolve(cwd, name);
505
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
+ }
506
524
  const fileUrl = url.pathToFileURL(filePath).href;
507
525
  const mod = await import(fileUrl);
508
526
  return mod.default;
@@ -543,7 +561,15 @@ env-typegen.config.mjs for runtime loading.`
543
561
  return void 0;
544
562
  }
545
563
  async function readEnvFile(filePath) {
546
- return promises.readFile(path6__default.default.resolve(filePath), "utf8");
564
+ const resolved = path6__default.default.resolve(filePath);
565
+ try {
566
+ return await promises.readFile(resolved, "utf8");
567
+ } catch (err) {
568
+ if (err instanceof Error && err.code === "ENOENT") {
569
+ throw new Error(`File not found: ${filePath}`);
570
+ }
571
+ throw err;
572
+ }
547
573
  }
548
574
  async function writeOutput(filePath, content) {
549
575
  const resolved = path6__default.default.resolve(filePath);
@@ -561,22 +587,30 @@ async function formatOutput(content, parser = "typescript") {
561
587
  return content;
562
588
  }
563
589
  }
590
+ var { green, red, yellow } = pc__default.default;
564
591
  function log(message) {
565
592
  console.log(message);
566
593
  }
567
594
  function warn(message) {
568
- console.warn(picocolors.yellow(`\u26A0 ${message}`));
595
+ console.warn(yellow(`\u26A0 ${message}`));
569
596
  }
570
597
  function error(message) {
571
- console.error(picocolors.red(`\u2716 ${message}`));
598
+ console.error(red(`\u2716 ${message}`));
572
599
  }
573
600
  function success(message) {
574
- console.log(picocolors.green(`\u2714 ${message}`));
601
+ console.log(green(`\u2714 ${message}`));
575
602
  }
576
603
 
577
604
  // src/pipeline.ts
578
605
  function deriveOutputPath(base, generator, isSingle) {
579
- 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
+ }
580
614
  const ext = path6__default.default.extname(base);
581
615
  const noExt = ext.length > 0 ? base.slice(0, -ext.length) : base;
582
616
  const baseExt = ext.length > 0 ? ext : ".ts";
@@ -614,10 +648,6 @@ async function persistOutput(params) {
614
648
  }
615
649
  if (dryRun) {
616
650
  if (!silent) {
617
- if (!isSingle) {
618
- console.log(`// --- ${generator}: ${outputPath} ---`);
619
- }
620
- console.log(generated);
621
651
  success(`Dry run: would write ${outputPath}`);
622
652
  }
623
653
  return;
@@ -725,7 +755,8 @@ function checkInvalidType(variable, entry, environment) {
725
755
  break;
726
756
  }
727
757
  case "boolean": {
728
- if (value !== "true" && value !== "false") {
758
+ const lower = value.toLowerCase();
759
+ if (!["true", "false", "1", "0", "yes", "no"].includes(lower)) {
729
760
  return {
730
761
  code: "ENV_INVALID_TYPE",
731
762
  key: variable.key,
@@ -848,7 +879,8 @@ function validateContract(parsed, contract, opts = {}) {
848
879
  const parsedByKey = new Map(parsed.vars.map((v) => [v.key, v]));
849
880
  for (const entry of contract.vars) {
850
881
  if (!entry.required) continue;
851
- if (parsedByKey.has(entry.name)) continue;
882
+ const existing = parsedByKey.get(entry.name);
883
+ if (existing !== void 0 && existing.rawValue !== "") continue;
852
884
  issues.push({
853
885
  code: "ENV_MISSING",
854
886
  key: entry.name,
@@ -1029,12 +1061,20 @@ function parseProviderPayload(provider, value) {
1029
1061
  }
1030
1062
  async function loadCloudSource(options) {
1031
1063
  const resolvedPath = path6__default.default.resolve(options.filePath);
1032
- const raw = await promises.readFile(resolvedPath, "utf8");
1064
+ let raw;
1065
+ try {
1066
+ raw = await promises.readFile(resolvedPath, "utf8");
1067
+ } catch (err) {
1068
+ if (err instanceof Error && err.code === "ENOENT") {
1069
+ throw new Error(`File not found: ${options.filePath}`);
1070
+ }
1071
+ throw err;
1072
+ }
1033
1073
  const parsed = JSON.parse(raw);
1034
1074
  return parseProviderPayload(options.provider, parsed);
1035
1075
  }
1036
1076
  var SECRET_KEY_RE = /(SECRET|TOKEN|PASSWORD|PRIVATE|API_KEY|ACCESS_KEY|CLIENT_SECRET)/i;
1037
- 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"];
1038
1078
  function isRecord2(value) {
1039
1079
  return typeof value === "object" && value !== null && !Array.isArray(value);
1040
1080
  }
@@ -1158,6 +1198,12 @@ async function loadValidationContract(options) {
1158
1198
  const discoveredContractPath = findDefaultContractPath(cwd);
1159
1199
  const resolvedContractPath = contractPath === void 0 ? discoveredContractPath : path6__default.default.resolve(cwd, contractPath);
1160
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
+ }
1161
1207
  const moduleUrl = url.pathToFileURL(resolvedContractPath).href;
1162
1208
  const moduleValue = await import(moduleUrl);
1163
1209
  const candidate = moduleValue.default ?? moduleValue.contract;
@@ -1192,6 +1238,9 @@ function isPlugin(value) {
1192
1238
  }
1193
1239
  async function loadPluginFromPath(pluginPath, cwd) {
1194
1240
  const resolvedPath = path6__default.default.resolve(cwd, pluginPath);
1241
+ if (!fs.existsSync(resolvedPath)) {
1242
+ throw new Error(`Plugin not found: ${pluginPath}`);
1243
+ }
1195
1244
  const moduleValue = await import(url.pathToFileURL(resolvedPath).href);
1196
1245
  const candidate = moduleValue.default ?? moduleValue.plugin;
1197
1246
  if (isPlugin(candidate)) return candidate;
@@ -1408,7 +1457,7 @@ function isClientSecret(variable, key) {
1408
1457
  function checkContractVariable(key, variable, context) {
1409
1458
  const { options, issues } = context;
1410
1459
  const value = options.values[key];
1411
- const hasValue = value !== void 0 && value.trim().length > 0;
1460
+ const hasValue = value !== void 0 && (value !== "" || !variable.required);
1412
1461
  if (variable.required && !hasValue) {
1413
1462
  issues.push(
1414
1463
  createIssue({
@@ -1510,7 +1559,11 @@ function diffTypeConflicts(key, present, context) {
1510
1559
  for (const entry of present) {
1511
1560
  typeBySource.set(entry.sourceName, detectReceivedType(entry.value ?? ""));
1512
1561
  }
1513
- 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;
1514
1567
  for (const [sourceName, detectedType] of typeBySource.entries()) {
1515
1568
  issues.push(
1516
1569
  createIssue({
@@ -1589,12 +1642,8 @@ function diffEnvironmentSources(options) {
1589
1642
  sourceName,
1590
1643
  value: options.sources[sourceName]?.[key]
1591
1644
  }));
1592
- const present = valuesBySource.filter(
1593
- (entry) => entry.value !== void 0 && entry.value !== ""
1594
- );
1595
- const missing = valuesBySource.filter(
1596
- (entry) => entry.value === void 0 || entry.value === ""
1597
- );
1645
+ const present = valuesBySource.filter((entry) => entry.value !== void 0);
1646
+ const missing = valuesBySource.filter((entry) => entry.value === void 0);
1598
1647
  if (present.length === 0 && variable?.required === true) {
1599
1648
  for (const entry of missing) {
1600
1649
  issues.push(
@@ -1681,6 +1730,7 @@ async function loadEnvSource(options) {
1681
1730
  return parseEnvSourceContent(content);
1682
1731
  } catch (error_) {
1683
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`);
1684
1734
  return {};
1685
1735
  }
1686
1736
  throw error_;
@@ -1847,6 +1897,9 @@ async function loadCommandConfig(configPath) {
1847
1897
  return loadConfig(process.cwd());
1848
1898
  }
1849
1899
  const resolvedPath = path6__default.default.resolve(configPath);
1900
+ if (!fs.existsSync(resolvedPath)) {
1901
+ throw new Error(`Config file not found: ${configPath}`);
1902
+ }
1850
1903
  const configDir = path6__default.default.dirname(resolvedPath);
1851
1904
  const moduleValue = await import(url.pathToFileURL(resolvedPath).href);
1852
1905
  if (moduleValue.default === void 0) return void 0;