@xlameiro/env-typegen 0.1.3 → 0.1.4
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 +67 -105
- package/dist/cli.js +424 -329
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +419 -334
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +419 -334
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -149,7 +149,9 @@ var inferenceRules = [
|
|
|
149
149
|
{
|
|
150
150
|
id: "P8_numeric_literal",
|
|
151
151
|
priority: 8,
|
|
152
|
-
|
|
152
|
+
// Non-capturing group with \d keeps the dot/digit boundary unambiguous,
|
|
153
|
+
// eliminating super-linear backtracking (ReDoS-safe).
|
|
154
|
+
match: (_key, value) => /^\d+(?:\.\d+)?$/.test(value),
|
|
153
155
|
type: "number"
|
|
154
156
|
},
|
|
155
157
|
{
|
|
@@ -167,7 +169,9 @@ var inferenceRules = [
|
|
|
167
169
|
{
|
|
168
170
|
id: "P11_email_literal",
|
|
169
171
|
priority: 11,
|
|
170
|
-
|
|
172
|
+
// Dots are excluded from each domain-segment character class so that the
|
|
173
|
+
// literal \. separators are unambiguous, preventing super-linear backtracking.
|
|
174
|
+
match: (_key, value) => /^[^@\s]+@[^@\s.]+(?:\.[^@\s.]+)+$/.test(value),
|
|
171
175
|
type: "email"
|
|
172
176
|
},
|
|
173
177
|
{
|
|
@@ -201,7 +205,50 @@ function inferTypesFromParsedVars(parsed, options) {
|
|
|
201
205
|
return parsed.vars.map((item) => inferType(item.key, item.rawValue, options));
|
|
202
206
|
}
|
|
203
207
|
var ENV_VAR_RE = /^([A-Z_][A-Z0-9_]*)=(.*)$/;
|
|
204
|
-
var SECTION_HEADER_RE = /^#\s+[-=]{3,}\s+(
|
|
208
|
+
var SECTION_HEADER_RE = /^#\s+[-=]{3,}\s+(\S+(?:\s+\S+)*)\s+[-=]{3,}\s*$/;
|
|
209
|
+
function buildParsedVar(params, commentBlock, options) {
|
|
210
|
+
const annotations = parseCommentBlock(commentBlock);
|
|
211
|
+
const extraRules = options?.inferenceRules;
|
|
212
|
+
const inferredType = inferType(
|
|
213
|
+
params.key,
|
|
214
|
+
params.rawValue,
|
|
215
|
+
...extraRules === void 0 ? [] : [{ extraRules }]
|
|
216
|
+
);
|
|
217
|
+
const isRequired = params.rawValue.length > 0 || annotations.isRequired;
|
|
218
|
+
const isOptional = params.rawValue.length === 0 && !annotations.isRequired;
|
|
219
|
+
const isClientSide = params.key.startsWith("NEXT_PUBLIC_");
|
|
220
|
+
const parsedVar = {
|
|
221
|
+
key: params.key,
|
|
222
|
+
rawValue: params.rawValue,
|
|
223
|
+
inferredType,
|
|
224
|
+
isRequired,
|
|
225
|
+
isOptional,
|
|
226
|
+
isClientSide,
|
|
227
|
+
lineNumber: params.lineNumber
|
|
228
|
+
};
|
|
229
|
+
if (annotations.annotatedType !== void 0) {
|
|
230
|
+
parsedVar.annotatedType = annotations.annotatedType;
|
|
231
|
+
}
|
|
232
|
+
if (annotations.description !== void 0) {
|
|
233
|
+
parsedVar.description = annotations.description;
|
|
234
|
+
}
|
|
235
|
+
if (params.currentGroup !== void 0) {
|
|
236
|
+
parsedVar.group = params.currentGroup;
|
|
237
|
+
}
|
|
238
|
+
if (annotations.enumValues !== void 0) {
|
|
239
|
+
parsedVar.enumValues = annotations.enumValues;
|
|
240
|
+
}
|
|
241
|
+
if (annotations.constraints !== void 0) {
|
|
242
|
+
parsedVar.constraints = annotations.constraints;
|
|
243
|
+
}
|
|
244
|
+
if (annotations.runtime !== void 0) {
|
|
245
|
+
parsedVar.runtime = annotations.runtime;
|
|
246
|
+
}
|
|
247
|
+
if (annotations.isSecret !== void 0) {
|
|
248
|
+
parsedVar.isSecret = annotations.isSecret;
|
|
249
|
+
}
|
|
250
|
+
return parsedVar;
|
|
251
|
+
}
|
|
205
252
|
function parseEnvFileContent(content, filePath, options) {
|
|
206
253
|
const lines = content.split("\n");
|
|
207
254
|
const vars = [];
|
|
@@ -231,53 +278,18 @@ function parseEnvFileContent(content, filePath, options) {
|
|
|
231
278
|
continue;
|
|
232
279
|
}
|
|
233
280
|
const envMatch = ENV_VAR_RE.exec(trimmed);
|
|
234
|
-
if (envMatch
|
|
235
|
-
const key = envMatch[1] ?? "";
|
|
236
|
-
const rawValue = envMatch[2] ?? "";
|
|
237
|
-
const annotations = parseCommentBlock(commentBlock);
|
|
238
|
-
const inferredType = inferType(
|
|
239
|
-
key,
|
|
240
|
-
rawValue,
|
|
241
|
-
...options?.inferenceRules !== void 0 ? [{ extraRules: options.inferenceRules }] : []
|
|
242
|
-
);
|
|
243
|
-
const isRequired = rawValue.length > 0 || annotations.isRequired;
|
|
244
|
-
const isOptional = rawValue.length === 0 && !annotations.isRequired;
|
|
245
|
-
const isClientSide = key.startsWith("NEXT_PUBLIC_");
|
|
246
|
-
const parsedVar = {
|
|
247
|
-
key,
|
|
248
|
-
rawValue,
|
|
249
|
-
inferredType,
|
|
250
|
-
isRequired,
|
|
251
|
-
isOptional,
|
|
252
|
-
isClientSide,
|
|
253
|
-
lineNumber
|
|
254
|
-
};
|
|
255
|
-
if (annotations.annotatedType !== void 0) {
|
|
256
|
-
parsedVar.annotatedType = annotations.annotatedType;
|
|
257
|
-
}
|
|
258
|
-
if (annotations.description !== void 0) {
|
|
259
|
-
parsedVar.description = annotations.description;
|
|
260
|
-
}
|
|
261
|
-
if (currentGroup !== void 0) {
|
|
262
|
-
parsedVar.group = currentGroup;
|
|
263
|
-
}
|
|
264
|
-
if (annotations.enumValues !== void 0) {
|
|
265
|
-
parsedVar.enumValues = annotations.enumValues;
|
|
266
|
-
}
|
|
267
|
-
if (annotations.constraints !== void 0) {
|
|
268
|
-
parsedVar.constraints = annotations.constraints;
|
|
269
|
-
}
|
|
270
|
-
if (annotations.runtime !== void 0) {
|
|
271
|
-
parsedVar.runtime = annotations.runtime;
|
|
272
|
-
}
|
|
273
|
-
if (annotations.isSecret !== void 0) {
|
|
274
|
-
parsedVar.isSecret = annotations.isSecret;
|
|
275
|
-
}
|
|
276
|
-
vars.push(parsedVar);
|
|
277
|
-
commentBlock = [];
|
|
278
|
-
} else {
|
|
281
|
+
if (envMatch === null) {
|
|
279
282
|
commentBlock = [];
|
|
283
|
+
continue;
|
|
280
284
|
}
|
|
285
|
+
vars.push(
|
|
286
|
+
buildParsedVar(
|
|
287
|
+
{ key: envMatch[1] ?? "", rawValue: envMatch[2] ?? "", lineNumber, currentGroup },
|
|
288
|
+
commentBlock,
|
|
289
|
+
options
|
|
290
|
+
)
|
|
291
|
+
);
|
|
292
|
+
commentBlock = [];
|
|
281
293
|
}
|
|
282
294
|
return { filePath, vars, groups };
|
|
283
295
|
}
|
|
@@ -296,12 +308,14 @@ function generateTypeScriptTypes(parsed) {
|
|
|
296
308
|
const fileName = path6__default.default.basename(parsed.filePath);
|
|
297
309
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
298
310
|
const lines = [];
|
|
299
|
-
lines.push(
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
311
|
+
lines.push(
|
|
312
|
+
"// Generated by env-typegen \u2014 do not edit manually",
|
|
313
|
+
`// Source: ${fileName}`,
|
|
314
|
+
`// Generated at: ${timestamp}`,
|
|
315
|
+
"",
|
|
316
|
+
"declare namespace NodeJS {",
|
|
317
|
+
" interface ProcessEnv {"
|
|
318
|
+
);
|
|
305
319
|
for (const variable of parsed.vars) {
|
|
306
320
|
const effectiveType = variable.annotatedType ?? variable.inferredType;
|
|
307
321
|
const optional = variable.isOptional ? "?" : "";
|
|
@@ -316,10 +330,7 @@ function generateTypeScriptTypes(parsed) {
|
|
|
316
330
|
}
|
|
317
331
|
lines.push(propLine);
|
|
318
332
|
}
|
|
319
|
-
lines.push(" }");
|
|
320
|
-
lines.push("}");
|
|
321
|
-
lines.push("");
|
|
322
|
-
lines.push("export type EnvVars = {");
|
|
333
|
+
lines.push(" }", "}", "", "export type EnvVars = {");
|
|
323
334
|
for (const variable of parsed.vars) {
|
|
324
335
|
const effectiveType = variable.annotatedType ?? variable.inferredType;
|
|
325
336
|
const tsType = toTsType(effectiveType);
|
|
@@ -329,28 +340,34 @@ function generateTypeScriptTypes(parsed) {
|
|
|
329
340
|
lines.push("};");
|
|
330
341
|
if (hasClientVars) {
|
|
331
342
|
const clientKeyUnion = clientVars.map((v) => `"${v.key}"`).join(" | ");
|
|
332
|
-
lines.push(
|
|
333
|
-
|
|
334
|
-
|
|
343
|
+
lines.push(
|
|
344
|
+
"",
|
|
345
|
+
`export type ServerEnvVars = Omit<EnvVars, ${clientKeyUnion}>;`,
|
|
346
|
+
`export type ClientEnvVars = Pick<EnvVars, ${clientKeyUnion}>;`
|
|
347
|
+
);
|
|
335
348
|
}
|
|
336
349
|
return lines.join("\n") + "\n";
|
|
337
350
|
}
|
|
338
351
|
function generateEnvValidation(parsed) {
|
|
339
352
|
const required = parsed.vars.filter((v) => v.isRequired).map((v) => v.key);
|
|
340
353
|
const lines = [];
|
|
341
|
-
lines.push(
|
|
342
|
-
|
|
343
|
-
|
|
354
|
+
lines.push(
|
|
355
|
+
"// Generated by env-typegen \u2014 do not edit manually",
|
|
356
|
+
"",
|
|
357
|
+
"export function validateEnv(): void {"
|
|
358
|
+
);
|
|
344
359
|
if (required.length === 0) {
|
|
345
360
|
lines.push(" // No required environment variables defined");
|
|
346
361
|
} else {
|
|
347
362
|
const keyList = required.map((k) => `"${k}"`).join(", ");
|
|
348
|
-
lines.push(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
363
|
+
lines.push(
|
|
364
|
+
` const required = [${keyList}];`,
|
|
365
|
+
" for (const key of required) {",
|
|
366
|
+
" if (!process.env[key]) {",
|
|
367
|
+
" throw new Error(`Missing required environment variable: ${key}`);",
|
|
368
|
+
" }",
|
|
369
|
+
" }"
|
|
370
|
+
);
|
|
354
371
|
}
|
|
355
372
|
lines.push("}");
|
|
356
373
|
return lines.join("\n") + "\n";
|
|
@@ -368,37 +385,40 @@ function generateZodSchema(parsed) {
|
|
|
368
385
|
const serverVars = parsed.vars.filter((v) => !v.isClientSide);
|
|
369
386
|
const clientVars = parsed.vars.filter((v) => v.isClientSide);
|
|
370
387
|
const lines = [];
|
|
371
|
-
lines.push(
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
388
|
+
lines.push(
|
|
389
|
+
"// Generated by env-typegen \u2014 do not edit manually",
|
|
390
|
+
'import { z } from "zod";',
|
|
391
|
+
"",
|
|
392
|
+
"export const serverEnvSchema = z.object({"
|
|
393
|
+
);
|
|
375
394
|
for (const variable of serverVars) {
|
|
376
395
|
const effectiveType = variable.annotatedType ?? variable.inferredType;
|
|
377
396
|
const zodExpr = variable.isOptional ? `${toZodType(effectiveType)}.optional()` : toZodType(effectiveType);
|
|
378
397
|
lines.push(` ${variable.key}: ${zodExpr},`);
|
|
379
398
|
}
|
|
380
|
-
lines.push("});");
|
|
381
|
-
lines.push("");
|
|
382
|
-
lines.push("export const clientEnvSchema = z.object({");
|
|
399
|
+
lines.push("});", "", "export const clientEnvSchema = z.object({");
|
|
383
400
|
for (const variable of clientVars) {
|
|
384
401
|
const effectiveType = variable.annotatedType ?? variable.inferredType;
|
|
385
402
|
const zodExpr = variable.isOptional ? `${toZodType(effectiveType)}.optional()` : toZodType(effectiveType);
|
|
386
403
|
lines.push(` ${variable.key}: ${zodExpr},`);
|
|
387
404
|
}
|
|
388
|
-
lines.push(
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
405
|
+
lines.push(
|
|
406
|
+
"});",
|
|
407
|
+
"",
|
|
408
|
+
"export const envSchema = serverEnvSchema.merge(clientEnvSchema);",
|
|
409
|
+
"export type Env = z.infer<typeof envSchema>;"
|
|
410
|
+
);
|
|
392
411
|
return lines.join("\n") + "\n";
|
|
393
412
|
}
|
|
394
413
|
function generateDeclaration(parsed) {
|
|
395
414
|
const fileName = path6__default.default.basename(parsed.filePath);
|
|
396
|
-
const lines = [
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
415
|
+
const lines = [
|
|
416
|
+
"// Generated by env-typegen \u2014 do not edit manually",
|
|
417
|
+
`// Source: ${fileName}`,
|
|
418
|
+
"",
|
|
419
|
+
"declare namespace NodeJS {",
|
|
420
|
+
" interface ProcessEnv {"
|
|
421
|
+
];
|
|
402
422
|
for (const variable of parsed.vars) {
|
|
403
423
|
const effectiveType = variable.annotatedType ?? variable.inferredType;
|
|
404
424
|
const optional = variable.isOptional ? "?" : "";
|
|
@@ -413,12 +433,14 @@ function generateDeclaration(parsed) {
|
|
|
413
433
|
}
|
|
414
434
|
lines.push(propLine);
|
|
415
435
|
}
|
|
416
|
-
lines.push(" }");
|
|
417
|
-
lines.push("}");
|
|
436
|
+
lines.push(" }", "}");
|
|
418
437
|
return lines.join("\n") + "\n";
|
|
419
438
|
}
|
|
420
439
|
|
|
421
440
|
// src/generators/t3-generator.ts
|
|
441
|
+
function escapeJsStringLiteral(value) {
|
|
442
|
+
return value.replaceAll("\\", String.raw`\\`).replaceAll('"', String.raw`\"`);
|
|
443
|
+
}
|
|
422
444
|
function toT3ZodType(envVarType) {
|
|
423
445
|
if (envVarType === "number") return "z.coerce.number()";
|
|
424
446
|
if (envVarType === "boolean") return "z.coerce.boolean()";
|
|
@@ -426,51 +448,47 @@ function toT3ZodType(envVarType) {
|
|
|
426
448
|
if (envVarType === "email") return "z.string().email()";
|
|
427
449
|
return "z.string()";
|
|
428
450
|
}
|
|
451
|
+
function buildZodExpr(variable) {
|
|
452
|
+
const effectiveType = variable.annotatedType ?? variable.inferredType;
|
|
453
|
+
let zodExpr = toT3ZodType(effectiveType);
|
|
454
|
+
if (variable.description !== void 0) {
|
|
455
|
+
zodExpr += `.describe("${escapeJsStringLiteral(variable.description)}")`;
|
|
456
|
+
}
|
|
457
|
+
if (variable.isOptional) {
|
|
458
|
+
zodExpr += ".optional()";
|
|
459
|
+
}
|
|
460
|
+
return zodExpr;
|
|
461
|
+
}
|
|
429
462
|
function generateT3Env(parsed) {
|
|
430
463
|
const serverVars = parsed.vars.filter((v) => !v.isClientSide);
|
|
431
464
|
const clientVars = parsed.vars.filter((v) => v.isClientSide);
|
|
432
|
-
const lines = [
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
465
|
+
const lines = [
|
|
466
|
+
"// Generated by env-typegen \u2014 do not edit manually",
|
|
467
|
+
'import { createEnv } from "@t3-oss/env-nextjs";',
|
|
468
|
+
'import { z } from "zod";',
|
|
469
|
+
"",
|
|
470
|
+
"export const env = createEnv({"
|
|
471
|
+
];
|
|
438
472
|
if (serverVars.length > 0) {
|
|
439
|
-
lines.push(
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
zodExpr += `.describe("${variable.description.replace(/"/g, '\\"')}")`;
|
|
445
|
-
}
|
|
446
|
-
if (variable.isOptional) {
|
|
447
|
-
zodExpr += ".optional()";
|
|
448
|
-
}
|
|
449
|
-
lines.push(` ${variable.key}: ${zodExpr},`);
|
|
450
|
-
}
|
|
451
|
-
lines.push(" },");
|
|
473
|
+
lines.push(
|
|
474
|
+
" server: {",
|
|
475
|
+
...serverVars.map((v) => ` ${v.key}: ${buildZodExpr(v)},`),
|
|
476
|
+
" },"
|
|
477
|
+
);
|
|
452
478
|
}
|
|
453
479
|
if (clientVars.length > 0) {
|
|
454
|
-
lines.push(
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
zodExpr += `.describe("${variable.description.replace(/"/g, '\\"')}")`;
|
|
460
|
-
}
|
|
461
|
-
if (variable.isOptional) {
|
|
462
|
-
zodExpr += ".optional()";
|
|
463
|
-
}
|
|
464
|
-
lines.push(` ${variable.key}: ${zodExpr},`);
|
|
465
|
-
}
|
|
466
|
-
lines.push(" },");
|
|
467
|
-
}
|
|
468
|
-
lines.push(" runtimeEnv: {");
|
|
469
|
-
for (const variable of parsed.vars) {
|
|
470
|
-
lines.push(` ${variable.key}: process.env.${variable.key},`);
|
|
480
|
+
lines.push(
|
|
481
|
+
" client: {",
|
|
482
|
+
...clientVars.map((v) => ` ${v.key}: ${buildZodExpr(v)},`),
|
|
483
|
+
" },"
|
|
484
|
+
);
|
|
471
485
|
}
|
|
472
|
-
lines.push(
|
|
473
|
-
|
|
486
|
+
lines.push(
|
|
487
|
+
" runtimeEnv: {",
|
|
488
|
+
...parsed.vars.map((v) => ` ${v.key}: process.env.${v.key},`),
|
|
489
|
+
" },",
|
|
490
|
+
"});"
|
|
491
|
+
);
|
|
474
492
|
return lines.join("\n") + "\n";
|
|
475
493
|
}
|
|
476
494
|
var CONTRACT_FILE_NAMES = [
|
|
@@ -493,9 +511,9 @@ async function loadContract(cwd = process.cwd()) {
|
|
|
493
511
|
return void 0;
|
|
494
512
|
}
|
|
495
513
|
var CONFIG_FILE_NAMES = [
|
|
496
|
-
"env-typegen.config.ts",
|
|
497
514
|
"env-typegen.config.mjs",
|
|
498
|
-
"env-typegen.config.js"
|
|
515
|
+
"env-typegen.config.js",
|
|
516
|
+
"env-typegen.config.ts"
|
|
499
517
|
];
|
|
500
518
|
function defineConfig(config) {
|
|
501
519
|
return config;
|
|
@@ -504,6 +522,19 @@ async function loadConfig(cwd = process.cwd()) {
|
|
|
504
522
|
for (const name of CONFIG_FILE_NAMES) {
|
|
505
523
|
const filePath = path6__default.default.resolve(cwd, name);
|
|
506
524
|
if (fs.existsSync(filePath)) {
|
|
525
|
+
if (filePath.endsWith(".ts")) {
|
|
526
|
+
throw new Error(
|
|
527
|
+
`Config file "${name}" was found but TypeScript files cannot be loaded directly at runtime.
|
|
528
|
+
Rename it to "env-typegen.config.mjs" and use ESM export syntax:
|
|
529
|
+
|
|
530
|
+
// env-typegen.config.mjs
|
|
531
|
+
import { defineConfig } from "@xlameiro/env-typegen";
|
|
532
|
+
export default defineConfig({ input: ".env.example" });
|
|
533
|
+
|
|
534
|
+
Tip: keep env-typegen.config.ts for IDE autocompletion and create a sibling
|
|
535
|
+
env-typegen.config.mjs for runtime loading.`
|
|
536
|
+
);
|
|
537
|
+
}
|
|
507
538
|
const fileUrl = url.pathToFileURL(filePath).href;
|
|
508
539
|
const mod = await import(fileUrl);
|
|
509
540
|
return mod.default;
|
|
@@ -616,7 +647,7 @@ async function runGenerate(options) {
|
|
|
616
647
|
const parsed = parseEnvFileContent(
|
|
617
648
|
content,
|
|
618
649
|
inputPath,
|
|
619
|
-
inferenceRules2
|
|
650
|
+
inferenceRules2 === void 0 ? void 0 : { inferenceRules: inferenceRules2 }
|
|
620
651
|
);
|
|
621
652
|
for (const generator of generators) {
|
|
622
653
|
let generated = buildOutput(generator, parsed);
|
|
@@ -944,8 +975,13 @@ function readEntryValue(entry, keys) {
|
|
|
944
975
|
}
|
|
945
976
|
return void 0;
|
|
946
977
|
}
|
|
978
|
+
function getVercelEntries(value) {
|
|
979
|
+
if (Array.isArray(value)) return value;
|
|
980
|
+
if (isRecord(value) && Array.isArray(value.envs)) return value.envs;
|
|
981
|
+
return [];
|
|
982
|
+
}
|
|
947
983
|
function parseVercelPayload(value) {
|
|
948
|
-
const entries =
|
|
984
|
+
const entries = getVercelEntries(value);
|
|
949
985
|
const result = {};
|
|
950
986
|
for (const entry of entries) {
|
|
951
987
|
if (!isRecord(entry)) continue;
|
|
@@ -956,8 +992,13 @@ function parseVercelPayload(value) {
|
|
|
956
992
|
}
|
|
957
993
|
return result;
|
|
958
994
|
}
|
|
995
|
+
function getCloudflareEntries(value) {
|
|
996
|
+
if (Array.isArray(value)) return value;
|
|
997
|
+
if (isRecord(value) && Array.isArray(value.result)) return value.result;
|
|
998
|
+
return [];
|
|
999
|
+
}
|
|
959
1000
|
function parseCloudflarePayload(value) {
|
|
960
|
-
const entries =
|
|
1001
|
+
const entries = getCloudflareEntries(value);
|
|
961
1002
|
const result = {};
|
|
962
1003
|
for (const entry of entries) {
|
|
963
1004
|
if (!isRecord(entry)) continue;
|
|
@@ -975,7 +1016,7 @@ function parseAwsPayload(value) {
|
|
|
975
1016
|
if (!isRecord(entry)) continue;
|
|
976
1017
|
const name = readEntryValue(entry, ["Name", "name"]);
|
|
977
1018
|
if (name === void 0) continue;
|
|
978
|
-
const key = name.split("/").
|
|
1019
|
+
const key = name.split("/").findLast((part) => part.length > 0) ?? name;
|
|
979
1020
|
const envValue = readEntryValue(entry, ["Value", "value"]) ?? "";
|
|
980
1021
|
result[key] = envValue;
|
|
981
1022
|
}
|
|
@@ -1115,7 +1156,7 @@ function findDefaultContractPath(cwd) {
|
|
|
1115
1156
|
async function loadValidationContract(options) {
|
|
1116
1157
|
const { fallbackExamplePath, contractPath, cwd = process.cwd() } = options;
|
|
1117
1158
|
const discoveredContractPath = findDefaultContractPath(cwd);
|
|
1118
|
-
const resolvedContractPath = contractPath
|
|
1159
|
+
const resolvedContractPath = contractPath === void 0 ? discoveredContractPath : path6__default.default.resolve(cwd, contractPath);
|
|
1119
1160
|
if (resolvedContractPath !== void 0 && fs.existsSync(resolvedContractPath)) {
|
|
1120
1161
|
const moduleUrl = url.pathToFileURL(resolvedContractPath).href;
|
|
1121
1162
|
const moduleValue = await import(moduleUrl);
|
|
@@ -1154,7 +1195,14 @@ async function loadPluginFromPath(pluginPath, cwd) {
|
|
|
1154
1195
|
const moduleValue = await import(url.pathToFileURL(resolvedPath).href);
|
|
1155
1196
|
const candidate = moduleValue.default ?? moduleValue.plugin;
|
|
1156
1197
|
if (isPlugin(candidate)) return candidate;
|
|
1157
|
-
throw new Error(
|
|
1198
|
+
throw new Error(
|
|
1199
|
+
`Invalid plugin at ${resolvedPath}.
|
|
1200
|
+
Expected a default export matching:
|
|
1201
|
+
{ name: string,
|
|
1202
|
+
transformSource?(ctx: { environment: string; values: Record<string, string> }): Record<string, string>,
|
|
1203
|
+
transformReport?(report: ValidationReport): ValidationReport,
|
|
1204
|
+
transformContract?(contract: EnvContract): EnvContract }`
|
|
1205
|
+
);
|
|
1158
1206
|
}
|
|
1159
1207
|
async function loadPlugins(options) {
|
|
1160
1208
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -1199,8 +1247,8 @@ function applyReportPlugins(report, plugins) {
|
|
|
1199
1247
|
}
|
|
1200
1248
|
|
|
1201
1249
|
// src/validation/engine.ts
|
|
1202
|
-
var EMAIL_RE = /^[^@\s]+@[^@\s]
|
|
1203
|
-
var SEMVER_RE2 = /^\d+\.\d+\.\d+(?:-[
|
|
1250
|
+
var EMAIL_RE = /^[^@\s]+@[^@\s.]+(?:\.[^@\s.]+)+$/;
|
|
1251
|
+
var SEMVER_RE2 = /^\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/;
|
|
1204
1252
|
function detectReceivedType(value) {
|
|
1205
1253
|
const normalized = value.trim();
|
|
1206
1254
|
if (normalized.length === 0) return "unknown";
|
|
@@ -1220,65 +1268,72 @@ function detectReceivedType(value) {
|
|
|
1220
1268
|
}
|
|
1221
1269
|
return "string";
|
|
1222
1270
|
}
|
|
1223
|
-
function
|
|
1224
|
-
const
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
if (expected.type === "string") return { isValid: true, receivedType };
|
|
1228
|
-
if (expected.type === "number") {
|
|
1229
|
-
const parsed = Number(normalized);
|
|
1230
|
-
if (Number.isNaN(parsed) || !Number.isFinite(parsed)) {
|
|
1231
|
-
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1232
|
-
}
|
|
1233
|
-
if (expected.min !== void 0 && parsed < expected.min) {
|
|
1234
|
-
return { isValid: false, receivedType, issueType: "invalid_value" };
|
|
1235
|
-
}
|
|
1236
|
-
if (expected.max !== void 0 && parsed > expected.max) {
|
|
1237
|
-
return { isValid: false, receivedType, issueType: "invalid_value" };
|
|
1238
|
-
}
|
|
1239
|
-
return { isValid: true, receivedType };
|
|
1271
|
+
function validateNumber(expected, normalized, receivedType) {
|
|
1272
|
+
const parsed = Number(normalized);
|
|
1273
|
+
if (Number.isNaN(parsed) || !Number.isFinite(parsed)) {
|
|
1274
|
+
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1240
1275
|
}
|
|
1241
|
-
if (expected.
|
|
1242
|
-
|
|
1243
|
-
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1244
|
-
}
|
|
1245
|
-
return { isValid: true, receivedType };
|
|
1276
|
+
if (expected.min !== void 0 && parsed < expected.min) {
|
|
1277
|
+
return { isValid: false, receivedType, issueType: "invalid_value" };
|
|
1246
1278
|
}
|
|
1247
|
-
if (expected.
|
|
1248
|
-
|
|
1249
|
-
return { isValid: false, receivedType, issueType: "invalid_value" };
|
|
1250
|
-
}
|
|
1251
|
-
return { isValid: true, receivedType };
|
|
1279
|
+
if (expected.max !== void 0 && parsed > expected.max) {
|
|
1280
|
+
return { isValid: false, receivedType, issueType: "invalid_value" };
|
|
1252
1281
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1282
|
+
return { isValid: true, receivedType };
|
|
1283
|
+
}
|
|
1284
|
+
function validateBoolean(normalized, receivedType) {
|
|
1285
|
+
if (!["true", "false", "1", "0", "yes", "no"].includes(normalized.toLowerCase())) {
|
|
1286
|
+
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1287
|
+
}
|
|
1288
|
+
return { isValid: true, receivedType };
|
|
1289
|
+
}
|
|
1290
|
+
function validateEnum(expected, normalized, receivedType) {
|
|
1291
|
+
if (!expected.values.includes(normalized)) {
|
|
1292
|
+
return { isValid: false, receivedType, issueType: "invalid_value" };
|
|
1262
1293
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1294
|
+
return { isValid: true, receivedType };
|
|
1295
|
+
}
|
|
1296
|
+
function validateUrl(normalized, receivedType) {
|
|
1297
|
+
try {
|
|
1298
|
+
const value = new URL(normalized);
|
|
1299
|
+
if (value.protocol.length === 0)
|
|
1265
1300
|
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1266
1301
|
return { isValid: true, receivedType };
|
|
1302
|
+
} catch {
|
|
1303
|
+
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1267
1304
|
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1305
|
+
}
|
|
1306
|
+
function validateEmail(normalized, receivedType) {
|
|
1307
|
+
if (!EMAIL_RE.test(normalized))
|
|
1308
|
+
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1309
|
+
return { isValid: true, receivedType };
|
|
1310
|
+
}
|
|
1311
|
+
function validateJson(normalized, receivedType) {
|
|
1312
|
+
try {
|
|
1313
|
+
const parsed = JSON.parse(normalized);
|
|
1314
|
+
if (typeof parsed === "object" && parsed !== null) return { isValid: true, receivedType };
|
|
1315
|
+
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1316
|
+
} catch {
|
|
1317
|
+
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1276
1318
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1319
|
+
}
|
|
1320
|
+
function validateSemver(normalized, receivedType) {
|
|
1321
|
+
if (!SEMVER_RE2.test(normalized))
|
|
1322
|
+
return { isValid: false, receivedType, issueType: "invalid_value" };
|
|
1323
|
+
return { isValid: true, receivedType };
|
|
1324
|
+
}
|
|
1325
|
+
function validateValueAgainstExpected(expected, rawValue) {
|
|
1326
|
+
const normalized = rawValue.trim();
|
|
1327
|
+
const receivedType = detectReceivedType(normalized);
|
|
1328
|
+
if (expected.type === "unknown" || expected.type === "string")
|
|
1280
1329
|
return { isValid: true, receivedType };
|
|
1281
|
-
|
|
1330
|
+
if (expected.type === "number") return validateNumber(expected, normalized, receivedType);
|
|
1331
|
+
if (expected.type === "boolean") return validateBoolean(normalized, receivedType);
|
|
1332
|
+
if (expected.type === "enum") return validateEnum(expected, normalized, receivedType);
|
|
1333
|
+
if (expected.type === "url") return validateUrl(normalized, receivedType);
|
|
1334
|
+
if (expected.type === "email") return validateEmail(normalized, receivedType);
|
|
1335
|
+
if (expected.type === "json") return validateJson(normalized, receivedType);
|
|
1336
|
+
if (expected.type === "semver") return validateSemver(normalized, receivedType);
|
|
1282
1337
|
return { isValid: true, receivedType };
|
|
1283
1338
|
}
|
|
1284
1339
|
function toIssueCode(issueType) {
|
|
@@ -1350,57 +1405,62 @@ function buildReport(env, issues, recommendations) {
|
|
|
1350
1405
|
function isClientSecret(variable, key) {
|
|
1351
1406
|
return variable.secret === true && (variable.clientSide || key.startsWith("NEXT_PUBLIC_"));
|
|
1352
1407
|
}
|
|
1408
|
+
function checkContractVariable(key, variable, context) {
|
|
1409
|
+
const { options, issues } = context;
|
|
1410
|
+
const value = options.values[key];
|
|
1411
|
+
const hasValue = value !== void 0 && value.trim().length > 0;
|
|
1412
|
+
if (variable.required && !hasValue) {
|
|
1413
|
+
issues.push(
|
|
1414
|
+
createIssue({
|
|
1415
|
+
type: "missing",
|
|
1416
|
+
severity: "error",
|
|
1417
|
+
key,
|
|
1418
|
+
environment: options.environment,
|
|
1419
|
+
message: `Required variable ${key} is missing.`,
|
|
1420
|
+
debugValues: options.debugValues,
|
|
1421
|
+
expected: variable.expected
|
|
1422
|
+
})
|
|
1423
|
+
);
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
if (!hasValue) return;
|
|
1427
|
+
const validation = validateValueAgainstExpected(variable.expected, value);
|
|
1428
|
+
if (!validation.isValid) {
|
|
1429
|
+
const message = validation.issueType === "invalid_type" ? `Variable ${key} has invalid type.` : `Variable ${key} has invalid value.`;
|
|
1430
|
+
issues.push(
|
|
1431
|
+
createIssue({
|
|
1432
|
+
type: validation.issueType,
|
|
1433
|
+
severity: "error",
|
|
1434
|
+
key,
|
|
1435
|
+
environment: options.environment,
|
|
1436
|
+
message,
|
|
1437
|
+
value,
|
|
1438
|
+
debugValues: options.debugValues,
|
|
1439
|
+
expected: variable.expected,
|
|
1440
|
+
receivedType: validation.receivedType
|
|
1441
|
+
})
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
if (isClientSecret(variable, key)) {
|
|
1445
|
+
issues.push(
|
|
1446
|
+
createIssue({
|
|
1447
|
+
type: "secret_exposed",
|
|
1448
|
+
severity: "error",
|
|
1449
|
+
key,
|
|
1450
|
+
environment: options.environment,
|
|
1451
|
+
message: `Secret variable ${key} is marked as client-side.`,
|
|
1452
|
+
value,
|
|
1453
|
+
debugValues: options.debugValues,
|
|
1454
|
+
expected: variable.expected
|
|
1455
|
+
})
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1353
1459
|
function validateAgainstContract(options) {
|
|
1354
1460
|
const issues = [];
|
|
1355
1461
|
const contractKeys = new Set(Object.keys(options.contract.variables));
|
|
1356
1462
|
for (const [key, variable] of Object.entries(options.contract.variables)) {
|
|
1357
|
-
|
|
1358
|
-
const hasValue = value !== void 0 && value.trim().length > 0;
|
|
1359
|
-
if (variable.required && !hasValue) {
|
|
1360
|
-
issues.push(
|
|
1361
|
-
createIssue({
|
|
1362
|
-
type: "missing",
|
|
1363
|
-
severity: "error",
|
|
1364
|
-
key,
|
|
1365
|
-
environment: options.environment,
|
|
1366
|
-
message: `Required variable ${key} is missing.`,
|
|
1367
|
-
debugValues: options.debugValues,
|
|
1368
|
-
expected: variable.expected
|
|
1369
|
-
})
|
|
1370
|
-
);
|
|
1371
|
-
continue;
|
|
1372
|
-
}
|
|
1373
|
-
if (!hasValue) continue;
|
|
1374
|
-
const validation = validateValueAgainstExpected(variable.expected, value);
|
|
1375
|
-
if (!validation.isValid) {
|
|
1376
|
-
issues.push(
|
|
1377
|
-
createIssue({
|
|
1378
|
-
type: validation.issueType,
|
|
1379
|
-
severity: "error",
|
|
1380
|
-
key,
|
|
1381
|
-
environment: options.environment,
|
|
1382
|
-
message: validation.issueType === "invalid_type" ? `Variable ${key} has invalid type.` : `Variable ${key} has invalid value.`,
|
|
1383
|
-
value,
|
|
1384
|
-
debugValues: options.debugValues,
|
|
1385
|
-
expected: variable.expected,
|
|
1386
|
-
receivedType: validation.receivedType
|
|
1387
|
-
})
|
|
1388
|
-
);
|
|
1389
|
-
}
|
|
1390
|
-
if (isClientSecret(variable, key)) {
|
|
1391
|
-
issues.push(
|
|
1392
|
-
createIssue({
|
|
1393
|
-
type: "secret_exposed",
|
|
1394
|
-
severity: "error",
|
|
1395
|
-
key,
|
|
1396
|
-
environment: options.environment,
|
|
1397
|
-
message: `Secret variable ${key} is marked as client-side.`,
|
|
1398
|
-
value,
|
|
1399
|
-
debugValues: options.debugValues,
|
|
1400
|
-
expected: variable.expected
|
|
1401
|
-
})
|
|
1402
|
-
);
|
|
1403
|
-
}
|
|
1463
|
+
checkContractVariable(key, variable, { options, issues });
|
|
1404
1464
|
}
|
|
1405
1465
|
for (const [key, value] of Object.entries(options.values)) {
|
|
1406
1466
|
if (contractKeys.has(key)) continue;
|
|
@@ -1428,6 +1488,97 @@ function collectUnionKeys(contract, sources) {
|
|
|
1428
1488
|
}
|
|
1429
1489
|
return union;
|
|
1430
1490
|
}
|
|
1491
|
+
function diffMissingEntries(key, missing, context) {
|
|
1492
|
+
const { variable, options, issues } = context;
|
|
1493
|
+
for (const entry of missing) {
|
|
1494
|
+
issues.push(
|
|
1495
|
+
createIssue({
|
|
1496
|
+
type: "missing",
|
|
1497
|
+
severity: "error",
|
|
1498
|
+
key,
|
|
1499
|
+
environment: entry.sourceName,
|
|
1500
|
+
message: `Variable ${key} is missing in ${entry.sourceName}.`,
|
|
1501
|
+
debugValues: options.debugValues,
|
|
1502
|
+
...variable !== void 0 && { expected: variable.expected }
|
|
1503
|
+
})
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
function diffTypeConflicts(key, present, context) {
|
|
1508
|
+
const { variable, options, issues } = context;
|
|
1509
|
+
const typeBySource = /* @__PURE__ */ new Map();
|
|
1510
|
+
for (const entry of present) {
|
|
1511
|
+
typeBySource.set(entry.sourceName, detectReceivedType(entry.value ?? ""));
|
|
1512
|
+
}
|
|
1513
|
+
if (new Set(typeBySource.values()).size <= 1) return;
|
|
1514
|
+
for (const [sourceName, detectedType] of typeBySource.entries()) {
|
|
1515
|
+
issues.push(
|
|
1516
|
+
createIssue({
|
|
1517
|
+
type: "conflict",
|
|
1518
|
+
severity: "error",
|
|
1519
|
+
key,
|
|
1520
|
+
environment: sourceName,
|
|
1521
|
+
message: `Variable ${key} has conflicting inferred type across environments.`,
|
|
1522
|
+
debugValues: options.debugValues,
|
|
1523
|
+
receivedType: detectedType,
|
|
1524
|
+
...options.sources[sourceName]?.[key] !== void 0 && {
|
|
1525
|
+
value: options.sources[sourceName]?.[key]
|
|
1526
|
+
},
|
|
1527
|
+
...variable !== void 0 && { expected: variable.expected }
|
|
1528
|
+
})
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
function diffPresentEntry(key, entry, context) {
|
|
1533
|
+
const { variable, options, issues } = context;
|
|
1534
|
+
if (entry.value === void 0) return;
|
|
1535
|
+
if (variable === void 0) {
|
|
1536
|
+
const severity = options.strict ? "error" : "warning";
|
|
1537
|
+
issues.push(
|
|
1538
|
+
createIssue({
|
|
1539
|
+
type: "extra",
|
|
1540
|
+
severity,
|
|
1541
|
+
key,
|
|
1542
|
+
environment: entry.sourceName,
|
|
1543
|
+
message: `Variable ${key} is not defined in the contract.`,
|
|
1544
|
+
value: entry.value,
|
|
1545
|
+
debugValues: options.debugValues
|
|
1546
|
+
})
|
|
1547
|
+
);
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
const validation = validateValueAgainstExpected(variable.expected, entry.value);
|
|
1551
|
+
if (!validation.isValid) {
|
|
1552
|
+
const message = validation.issueType === "invalid_type" ? `Variable ${key} has invalid type in ${entry.sourceName}.` : `Variable ${key} has invalid value in ${entry.sourceName}.`;
|
|
1553
|
+
issues.push(
|
|
1554
|
+
createIssue({
|
|
1555
|
+
type: validation.issueType,
|
|
1556
|
+
severity: "error",
|
|
1557
|
+
key,
|
|
1558
|
+
environment: entry.sourceName,
|
|
1559
|
+
message,
|
|
1560
|
+
value: entry.value,
|
|
1561
|
+
debugValues: options.debugValues,
|
|
1562
|
+
expected: variable.expected,
|
|
1563
|
+
receivedType: validation.receivedType
|
|
1564
|
+
})
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
if (isClientSecret(variable, key)) {
|
|
1568
|
+
issues.push(
|
|
1569
|
+
createIssue({
|
|
1570
|
+
type: "secret_exposed",
|
|
1571
|
+
severity: "error",
|
|
1572
|
+
key,
|
|
1573
|
+
environment: entry.sourceName,
|
|
1574
|
+
message: `Secret variable ${key} is marked as client-side.`,
|
|
1575
|
+
value: entry.value,
|
|
1576
|
+
debugValues: options.debugValues,
|
|
1577
|
+
expected: variable.expected
|
|
1578
|
+
})
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1431
1582
|
function diffEnvironmentSources(options) {
|
|
1432
1583
|
const issues = [];
|
|
1433
1584
|
const sourceNames = Object.keys(options.sources);
|
|
@@ -1460,92 +1611,13 @@ function diffEnvironmentSources(options) {
|
|
|
1460
1611
|
}
|
|
1461
1612
|
continue;
|
|
1462
1613
|
}
|
|
1614
|
+
const ctx = { variable, options, issues };
|
|
1463
1615
|
if (present.length > 0) {
|
|
1464
|
-
|
|
1465
|
-
issues.push(
|
|
1466
|
-
createIssue({
|
|
1467
|
-
type: "missing",
|
|
1468
|
-
severity: "error",
|
|
1469
|
-
key,
|
|
1470
|
-
environment: entry.sourceName,
|
|
1471
|
-
message: `Variable ${key} is missing in ${entry.sourceName}.`,
|
|
1472
|
-
debugValues: options.debugValues,
|
|
1473
|
-
...variable !== void 0 && { expected: variable.expected }
|
|
1474
|
-
})
|
|
1475
|
-
);
|
|
1476
|
-
}
|
|
1616
|
+
diffMissingEntries(key, missing, ctx);
|
|
1477
1617
|
}
|
|
1478
|
-
|
|
1618
|
+
diffTypeConflicts(key, present, ctx);
|
|
1479
1619
|
for (const entry of present) {
|
|
1480
|
-
|
|
1481
|
-
typeBySource.set(entry.sourceName, detected);
|
|
1482
|
-
}
|
|
1483
|
-
if (new Set(typeBySource.values()).size > 1) {
|
|
1484
|
-
for (const [sourceName, detectedType] of typeBySource.entries()) {
|
|
1485
|
-
issues.push(
|
|
1486
|
-
createIssue({
|
|
1487
|
-
type: "conflict",
|
|
1488
|
-
severity: "error",
|
|
1489
|
-
key,
|
|
1490
|
-
environment: sourceName,
|
|
1491
|
-
message: `Variable ${key} has conflicting inferred type across environments.`,
|
|
1492
|
-
debugValues: options.debugValues,
|
|
1493
|
-
receivedType: detectedType,
|
|
1494
|
-
...options.sources[sourceName]?.[key] !== void 0 && {
|
|
1495
|
-
value: options.sources[sourceName]?.[key]
|
|
1496
|
-
},
|
|
1497
|
-
...variable !== void 0 && { expected: variable.expected }
|
|
1498
|
-
})
|
|
1499
|
-
);
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
for (const entry of present) {
|
|
1503
|
-
if (entry.value === void 0) continue;
|
|
1504
|
-
if (variable === void 0) {
|
|
1505
|
-
const severity = options.strict ? "error" : "warning";
|
|
1506
|
-
issues.push(
|
|
1507
|
-
createIssue({
|
|
1508
|
-
type: "extra",
|
|
1509
|
-
severity,
|
|
1510
|
-
key,
|
|
1511
|
-
environment: entry.sourceName,
|
|
1512
|
-
message: `Variable ${key} is not defined in the contract.`,
|
|
1513
|
-
value: entry.value,
|
|
1514
|
-
debugValues: options.debugValues
|
|
1515
|
-
})
|
|
1516
|
-
);
|
|
1517
|
-
continue;
|
|
1518
|
-
}
|
|
1519
|
-
const validation = validateValueAgainstExpected(variable.expected, entry.value);
|
|
1520
|
-
if (!validation.isValid) {
|
|
1521
|
-
issues.push(
|
|
1522
|
-
createIssue({
|
|
1523
|
-
type: validation.issueType,
|
|
1524
|
-
severity: "error",
|
|
1525
|
-
key,
|
|
1526
|
-
environment: entry.sourceName,
|
|
1527
|
-
message: validation.issueType === "invalid_type" ? `Variable ${key} has invalid type in ${entry.sourceName}.` : `Variable ${key} has invalid value in ${entry.sourceName}.`,
|
|
1528
|
-
value: entry.value,
|
|
1529
|
-
debugValues: options.debugValues,
|
|
1530
|
-
expected: variable.expected,
|
|
1531
|
-
receivedType: validation.receivedType
|
|
1532
|
-
})
|
|
1533
|
-
);
|
|
1534
|
-
}
|
|
1535
|
-
if (isClientSecret(variable, key)) {
|
|
1536
|
-
issues.push(
|
|
1537
|
-
createIssue({
|
|
1538
|
-
type: "secret_exposed",
|
|
1539
|
-
severity: "error",
|
|
1540
|
-
key,
|
|
1541
|
-
environment: entry.sourceName,
|
|
1542
|
-
message: `Secret variable ${key} is marked as client-side.`,
|
|
1543
|
-
value: entry.value,
|
|
1544
|
-
debugValues: options.debugValues,
|
|
1545
|
-
expected: variable.expected
|
|
1546
|
-
})
|
|
1547
|
-
);
|
|
1548
|
-
}
|
|
1620
|
+
diffPresentEntry(key, entry, ctx);
|
|
1549
1621
|
}
|
|
1550
1622
|
}
|
|
1551
1623
|
return buildReport("diff", issues);
|
|
@@ -1594,7 +1666,7 @@ function parseEnvSourceContent(content) {
|
|
|
1594
1666
|
for (const line of lines) {
|
|
1595
1667
|
const trimmed = line.trim();
|
|
1596
1668
|
if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
|
|
1597
|
-
const match = /^(?:export\s+)?([A-Za-z_]
|
|
1669
|
+
const match = /^(?:export\s+)?([A-Za-z_]\w*)=(.*)$/.exec(trimmed);
|
|
1598
1670
|
if (match === null) continue;
|
|
1599
1671
|
const key = match[1] ?? "";
|
|
1600
1672
|
const rawValue = match[2] ?? "";
|
|
@@ -1607,11 +1679,11 @@ async function loadEnvSource(options) {
|
|
|
1607
1679
|
try {
|
|
1608
1680
|
const content = await promises.readFile(resolvedPath, "utf8");
|
|
1609
1681
|
return parseEnvSourceContent(content);
|
|
1610
|
-
} catch (
|
|
1611
|
-
if (options.allowMissing === true &&
|
|
1682
|
+
} catch (error_) {
|
|
1683
|
+
if (options.allowMissing === true && error_ instanceof Error && "code" in error_ && error_.code === "ENOENT") {
|
|
1612
1684
|
return {};
|
|
1613
1685
|
}
|
|
1614
|
-
throw
|
|
1686
|
+
throw error_;
|
|
1615
1687
|
}
|
|
1616
1688
|
}
|
|
1617
1689
|
function toJsonString(report, mode) {
|
|
@@ -1621,8 +1693,8 @@ function toJsonString(report, mode) {
|
|
|
1621
1693
|
`;
|
|
1622
1694
|
}
|
|
1623
1695
|
function formatIssue(issue) {
|
|
1624
|
-
const expected = issue.expected
|
|
1625
|
-
const received = issue.receivedType
|
|
1696
|
+
const expected = issue.expected === void 0 ? "" : ` expected=${issue.expected.type}`;
|
|
1697
|
+
const received = issue.receivedType === void 0 ? "" : ` received=${issue.receivedType}`;
|
|
1626
1698
|
return `${issue.severity.toUpperCase()} [${issue.code}] ${issue.environment}:${issue.key} ${issue.message}${expected}${received}`;
|
|
1627
1699
|
}
|
|
1628
1700
|
function formatHumanReport(report) {
|
|
@@ -1631,15 +1703,13 @@ function formatHumanReport(report) {
|
|
|
1631
1703
|
`Status: ${report.status.toUpperCase()} (errors=${report.summary.errors}, warnings=${report.summary.warnings}, total=${report.summary.total})`
|
|
1632
1704
|
);
|
|
1633
1705
|
if (report.issues.length > 0) {
|
|
1634
|
-
lines.push("");
|
|
1635
|
-
lines.push("Issues:");
|
|
1706
|
+
lines.push("", "Issues:");
|
|
1636
1707
|
for (const issue of report.issues) {
|
|
1637
1708
|
lines.push(`- ${formatIssue(issue)}`);
|
|
1638
1709
|
}
|
|
1639
1710
|
}
|
|
1640
1711
|
if (report.recommendations !== void 0 && report.recommendations.length > 0) {
|
|
1641
|
-
lines.push("");
|
|
1642
|
-
lines.push("Recommendations:");
|
|
1712
|
+
lines.push("", "Recommendations:");
|
|
1643
1713
|
for (const recommendation of report.recommendations) {
|
|
1644
1714
|
lines.push(`- ${recommendation}`);
|
|
1645
1715
|
}
|
|
@@ -1684,7 +1754,11 @@ var HELP_TEXT = {
|
|
|
1684
1754
|
" --cloud-file <path> Cloud snapshot JSON file",
|
|
1685
1755
|
" --plugin <path> Plugin module path (repeatable)",
|
|
1686
1756
|
" -c, --config <path> Config file path",
|
|
1687
|
-
" -h, --help Show this help"
|
|
1757
|
+
" -h, --help Show this help",
|
|
1758
|
+
"",
|
|
1759
|
+
"Exit codes:",
|
|
1760
|
+
" 0 All checks passed (status: ok or warn)",
|
|
1761
|
+
" 1 One or more checks failed (status: fail) or invalid usage"
|
|
1688
1762
|
].join("\n"),
|
|
1689
1763
|
diff: [
|
|
1690
1764
|
"Usage: env-typegen diff [options]",
|
|
@@ -1703,7 +1777,11 @@ var HELP_TEXT = {
|
|
|
1703
1777
|
" --cloud-file <path> Cloud snapshot JSON file added to diff sources",
|
|
1704
1778
|
" --plugin <path> Plugin module path (repeatable)",
|
|
1705
1779
|
" -c, --config <path> Config file path",
|
|
1706
|
-
" -h, --help Show this help"
|
|
1780
|
+
" -h, --help Show this help",
|
|
1781
|
+
"",
|
|
1782
|
+
"Exit codes:",
|
|
1783
|
+
" 0 All checks passed (status: ok or warn)",
|
|
1784
|
+
" 1 One or more checks failed (status: fail) or invalid usage"
|
|
1707
1785
|
].join("\n"),
|
|
1708
1786
|
doctor: [
|
|
1709
1787
|
"Usage: env-typegen doctor [options]",
|
|
@@ -1723,7 +1801,11 @@ var HELP_TEXT = {
|
|
|
1723
1801
|
" --cloud-file <path> Cloud snapshot JSON file",
|
|
1724
1802
|
" --plugin <path> Plugin module path (repeatable)",
|
|
1725
1803
|
" -c, --config <path> Config file path",
|
|
1726
|
-
" -h, --help Show this help"
|
|
1804
|
+
" -h, --help Show this help",
|
|
1805
|
+
"",
|
|
1806
|
+
"Exit codes:",
|
|
1807
|
+
" 0 All checks passed (status: ok or warn)",
|
|
1808
|
+
" 1 One or more checks failed (status: fail) or invalid usage"
|
|
1727
1809
|
].join("\n")
|
|
1728
1810
|
};
|
|
1729
1811
|
function resolveConfigRelative(value, configDir) {
|
|
@@ -1810,7 +1892,10 @@ function parseValidationArgs(argv) {
|
|
|
1810
1892
|
}
|
|
1811
1893
|
});
|
|
1812
1894
|
const castValues = values;
|
|
1813
|
-
|
|
1895
|
+
let jsonMode = "off";
|
|
1896
|
+
if (castValues.json === true) {
|
|
1897
|
+
jsonMode = assignedMode === "off" ? "compact" : assignedMode;
|
|
1898
|
+
}
|
|
1814
1899
|
return { values: castValues, jsonMode };
|
|
1815
1900
|
}
|
|
1816
1901
|
function resolveStrict(values, fileConfig) {
|
|
@@ -1883,12 +1968,12 @@ async function runCheckCommand(args) {
|
|
|
1883
1968
|
const provider = context.cloudProvider;
|
|
1884
1969
|
let environment = args.values.env?.[0] ?? ".env";
|
|
1885
1970
|
let sourceValues;
|
|
1886
|
-
if (provider
|
|
1971
|
+
if (provider === void 0) {
|
|
1972
|
+
sourceValues = await loadEnvSource({ filePath: environment, allowMissing: true });
|
|
1973
|
+
} else {
|
|
1887
1974
|
const cloudFile = context.cloudFile ?? `${provider}.env.json`;
|
|
1888
1975
|
sourceValues = await loadCloudSource({ provider, filePath: cloudFile });
|
|
1889
1976
|
environment = `cloud:${provider}`;
|
|
1890
|
-
} else {
|
|
1891
|
-
sourceValues = await loadEnvSource({ filePath: environment, allowMissing: true });
|
|
1892
1977
|
}
|
|
1893
1978
|
sourceValues = applySourcePlugins({ environment, values: sourceValues }, context.plugins);
|
|
1894
1979
|
const report = applyReportPlugins(
|