@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/CHANGELOG.md +12 -0
- package/README.md +12 -0
- package/dist/cli.js +88 -28
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +79 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +75 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
595
|
+
console.warn(yellow(`\u26A0 ${message}`));
|
|
569
596
|
}
|
|
570
597
|
function error(message) {
|
|
571
|
-
console.error(
|
|
598
|
+
console.error(red(`\u2716 ${message}`));
|
|
572
599
|
}
|
|
573
600
|
function success(message) {
|
|
574
|
-
console.log(
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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;
|