@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/CHANGELOG.md +6 -0
- package/README.md +12 -0
- package/dist/cli.js +38 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +47 -10
- 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 +47 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
*
|
|
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.
|
|
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
|
-
*
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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_;
|