@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.js
CHANGED
|
@@ -143,7 +143,9 @@ var inferenceRules = [
|
|
|
143
143
|
{
|
|
144
144
|
id: "P8_numeric_literal",
|
|
145
145
|
priority: 8,
|
|
146
|
-
|
|
146
|
+
// Non-capturing group with \d keeps the dot/digit boundary unambiguous,
|
|
147
|
+
// eliminating super-linear backtracking (ReDoS-safe).
|
|
148
|
+
match: (_key, value) => /^\d+(?:\.\d+)?$/.test(value),
|
|
147
149
|
type: "number"
|
|
148
150
|
},
|
|
149
151
|
{
|
|
@@ -161,7 +163,9 @@ var inferenceRules = [
|
|
|
161
163
|
{
|
|
162
164
|
id: "P11_email_literal",
|
|
163
165
|
priority: 11,
|
|
164
|
-
|
|
166
|
+
// Dots are excluded from each domain-segment character class so that the
|
|
167
|
+
// literal \. separators are unambiguous, preventing super-linear backtracking.
|
|
168
|
+
match: (_key, value) => /^[^@\s]+@[^@\s.]+(?:\.[^@\s.]+)+$/.test(value),
|
|
165
169
|
type: "email"
|
|
166
170
|
},
|
|
167
171
|
{
|
|
@@ -195,7 +199,50 @@ function inferTypesFromParsedVars(parsed, options) {
|
|
|
195
199
|
return parsed.vars.map((item) => inferType(item.key, item.rawValue, options));
|
|
196
200
|
}
|
|
197
201
|
var ENV_VAR_RE = /^([A-Z_][A-Z0-9_]*)=(.*)$/;
|
|
198
|
-
var SECTION_HEADER_RE = /^#\s+[-=]{3,}\s+(
|
|
202
|
+
var SECTION_HEADER_RE = /^#\s+[-=]{3,}\s+(\S+(?:\s+\S+)*)\s+[-=]{3,}\s*$/;
|
|
203
|
+
function buildParsedVar(params, commentBlock, options) {
|
|
204
|
+
const annotations = parseCommentBlock(commentBlock);
|
|
205
|
+
const extraRules = options?.inferenceRules;
|
|
206
|
+
const inferredType = inferType(
|
|
207
|
+
params.key,
|
|
208
|
+
params.rawValue,
|
|
209
|
+
...extraRules === void 0 ? [] : [{ extraRules }]
|
|
210
|
+
);
|
|
211
|
+
const isRequired = params.rawValue.length > 0 || annotations.isRequired;
|
|
212
|
+
const isOptional = params.rawValue.length === 0 && !annotations.isRequired;
|
|
213
|
+
const isClientSide = params.key.startsWith("NEXT_PUBLIC_");
|
|
214
|
+
const parsedVar = {
|
|
215
|
+
key: params.key,
|
|
216
|
+
rawValue: params.rawValue,
|
|
217
|
+
inferredType,
|
|
218
|
+
isRequired,
|
|
219
|
+
isOptional,
|
|
220
|
+
isClientSide,
|
|
221
|
+
lineNumber: params.lineNumber
|
|
222
|
+
};
|
|
223
|
+
if (annotations.annotatedType !== void 0) {
|
|
224
|
+
parsedVar.annotatedType = annotations.annotatedType;
|
|
225
|
+
}
|
|
226
|
+
if (annotations.description !== void 0) {
|
|
227
|
+
parsedVar.description = annotations.description;
|
|
228
|
+
}
|
|
229
|
+
if (params.currentGroup !== void 0) {
|
|
230
|
+
parsedVar.group = params.currentGroup;
|
|
231
|
+
}
|
|
232
|
+
if (annotations.enumValues !== void 0) {
|
|
233
|
+
parsedVar.enumValues = annotations.enumValues;
|
|
234
|
+
}
|
|
235
|
+
if (annotations.constraints !== void 0) {
|
|
236
|
+
parsedVar.constraints = annotations.constraints;
|
|
237
|
+
}
|
|
238
|
+
if (annotations.runtime !== void 0) {
|
|
239
|
+
parsedVar.runtime = annotations.runtime;
|
|
240
|
+
}
|
|
241
|
+
if (annotations.isSecret !== void 0) {
|
|
242
|
+
parsedVar.isSecret = annotations.isSecret;
|
|
243
|
+
}
|
|
244
|
+
return parsedVar;
|
|
245
|
+
}
|
|
199
246
|
function parseEnvFileContent(content, filePath, options) {
|
|
200
247
|
const lines = content.split("\n");
|
|
201
248
|
const vars = [];
|
|
@@ -225,53 +272,18 @@ function parseEnvFileContent(content, filePath, options) {
|
|
|
225
272
|
continue;
|
|
226
273
|
}
|
|
227
274
|
const envMatch = ENV_VAR_RE.exec(trimmed);
|
|
228
|
-
if (envMatch
|
|
229
|
-
const key = envMatch[1] ?? "";
|
|
230
|
-
const rawValue = envMatch[2] ?? "";
|
|
231
|
-
const annotations = parseCommentBlock(commentBlock);
|
|
232
|
-
const inferredType = inferType(
|
|
233
|
-
key,
|
|
234
|
-
rawValue,
|
|
235
|
-
...options?.inferenceRules !== void 0 ? [{ extraRules: options.inferenceRules }] : []
|
|
236
|
-
);
|
|
237
|
-
const isRequired = rawValue.length > 0 || annotations.isRequired;
|
|
238
|
-
const isOptional = rawValue.length === 0 && !annotations.isRequired;
|
|
239
|
-
const isClientSide = key.startsWith("NEXT_PUBLIC_");
|
|
240
|
-
const parsedVar = {
|
|
241
|
-
key,
|
|
242
|
-
rawValue,
|
|
243
|
-
inferredType,
|
|
244
|
-
isRequired,
|
|
245
|
-
isOptional,
|
|
246
|
-
isClientSide,
|
|
247
|
-
lineNumber
|
|
248
|
-
};
|
|
249
|
-
if (annotations.annotatedType !== void 0) {
|
|
250
|
-
parsedVar.annotatedType = annotations.annotatedType;
|
|
251
|
-
}
|
|
252
|
-
if (annotations.description !== void 0) {
|
|
253
|
-
parsedVar.description = annotations.description;
|
|
254
|
-
}
|
|
255
|
-
if (currentGroup !== void 0) {
|
|
256
|
-
parsedVar.group = currentGroup;
|
|
257
|
-
}
|
|
258
|
-
if (annotations.enumValues !== void 0) {
|
|
259
|
-
parsedVar.enumValues = annotations.enumValues;
|
|
260
|
-
}
|
|
261
|
-
if (annotations.constraints !== void 0) {
|
|
262
|
-
parsedVar.constraints = annotations.constraints;
|
|
263
|
-
}
|
|
264
|
-
if (annotations.runtime !== void 0) {
|
|
265
|
-
parsedVar.runtime = annotations.runtime;
|
|
266
|
-
}
|
|
267
|
-
if (annotations.isSecret !== void 0) {
|
|
268
|
-
parsedVar.isSecret = annotations.isSecret;
|
|
269
|
-
}
|
|
270
|
-
vars.push(parsedVar);
|
|
271
|
-
commentBlock = [];
|
|
272
|
-
} else {
|
|
275
|
+
if (envMatch === null) {
|
|
273
276
|
commentBlock = [];
|
|
277
|
+
continue;
|
|
274
278
|
}
|
|
279
|
+
vars.push(
|
|
280
|
+
buildParsedVar(
|
|
281
|
+
{ key: envMatch[1] ?? "", rawValue: envMatch[2] ?? "", lineNumber, currentGroup },
|
|
282
|
+
commentBlock,
|
|
283
|
+
options
|
|
284
|
+
)
|
|
285
|
+
);
|
|
286
|
+
commentBlock = [];
|
|
275
287
|
}
|
|
276
288
|
return { filePath, vars, groups };
|
|
277
289
|
}
|
|
@@ -290,12 +302,14 @@ function generateTypeScriptTypes(parsed) {
|
|
|
290
302
|
const fileName = path6.basename(parsed.filePath);
|
|
291
303
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
292
304
|
const lines = [];
|
|
293
|
-
lines.push(
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
305
|
+
lines.push(
|
|
306
|
+
"// Generated by env-typegen \u2014 do not edit manually",
|
|
307
|
+
`// Source: ${fileName}`,
|
|
308
|
+
`// Generated at: ${timestamp}`,
|
|
309
|
+
"",
|
|
310
|
+
"declare namespace NodeJS {",
|
|
311
|
+
" interface ProcessEnv {"
|
|
312
|
+
);
|
|
299
313
|
for (const variable of parsed.vars) {
|
|
300
314
|
const effectiveType = variable.annotatedType ?? variable.inferredType;
|
|
301
315
|
const optional = variable.isOptional ? "?" : "";
|
|
@@ -310,10 +324,7 @@ function generateTypeScriptTypes(parsed) {
|
|
|
310
324
|
}
|
|
311
325
|
lines.push(propLine);
|
|
312
326
|
}
|
|
313
|
-
lines.push(" }");
|
|
314
|
-
lines.push("}");
|
|
315
|
-
lines.push("");
|
|
316
|
-
lines.push("export type EnvVars = {");
|
|
327
|
+
lines.push(" }", "}", "", "export type EnvVars = {");
|
|
317
328
|
for (const variable of parsed.vars) {
|
|
318
329
|
const effectiveType = variable.annotatedType ?? variable.inferredType;
|
|
319
330
|
const tsType = toTsType(effectiveType);
|
|
@@ -323,28 +334,34 @@ function generateTypeScriptTypes(parsed) {
|
|
|
323
334
|
lines.push("};");
|
|
324
335
|
if (hasClientVars) {
|
|
325
336
|
const clientKeyUnion = clientVars.map((v) => `"${v.key}"`).join(" | ");
|
|
326
|
-
lines.push(
|
|
327
|
-
|
|
328
|
-
|
|
337
|
+
lines.push(
|
|
338
|
+
"",
|
|
339
|
+
`export type ServerEnvVars = Omit<EnvVars, ${clientKeyUnion}>;`,
|
|
340
|
+
`export type ClientEnvVars = Pick<EnvVars, ${clientKeyUnion}>;`
|
|
341
|
+
);
|
|
329
342
|
}
|
|
330
343
|
return lines.join("\n") + "\n";
|
|
331
344
|
}
|
|
332
345
|
function generateEnvValidation(parsed) {
|
|
333
346
|
const required = parsed.vars.filter((v) => v.isRequired).map((v) => v.key);
|
|
334
347
|
const lines = [];
|
|
335
|
-
lines.push(
|
|
336
|
-
|
|
337
|
-
|
|
348
|
+
lines.push(
|
|
349
|
+
"// Generated by env-typegen \u2014 do not edit manually",
|
|
350
|
+
"",
|
|
351
|
+
"export function validateEnv(): void {"
|
|
352
|
+
);
|
|
338
353
|
if (required.length === 0) {
|
|
339
354
|
lines.push(" // No required environment variables defined");
|
|
340
355
|
} else {
|
|
341
356
|
const keyList = required.map((k) => `"${k}"`).join(", ");
|
|
342
|
-
lines.push(
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
357
|
+
lines.push(
|
|
358
|
+
` const required = [${keyList}];`,
|
|
359
|
+
" for (const key of required) {",
|
|
360
|
+
" if (!process.env[key]) {",
|
|
361
|
+
" throw new Error(`Missing required environment variable: ${key}`);",
|
|
362
|
+
" }",
|
|
363
|
+
" }"
|
|
364
|
+
);
|
|
348
365
|
}
|
|
349
366
|
lines.push("}");
|
|
350
367
|
return lines.join("\n") + "\n";
|
|
@@ -362,37 +379,40 @@ function generateZodSchema(parsed) {
|
|
|
362
379
|
const serverVars = parsed.vars.filter((v) => !v.isClientSide);
|
|
363
380
|
const clientVars = parsed.vars.filter((v) => v.isClientSide);
|
|
364
381
|
const lines = [];
|
|
365
|
-
lines.push(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
382
|
+
lines.push(
|
|
383
|
+
"// Generated by env-typegen \u2014 do not edit manually",
|
|
384
|
+
'import { z } from "zod";',
|
|
385
|
+
"",
|
|
386
|
+
"export const serverEnvSchema = z.object({"
|
|
387
|
+
);
|
|
369
388
|
for (const variable of serverVars) {
|
|
370
389
|
const effectiveType = variable.annotatedType ?? variable.inferredType;
|
|
371
390
|
const zodExpr = variable.isOptional ? `${toZodType(effectiveType)}.optional()` : toZodType(effectiveType);
|
|
372
391
|
lines.push(` ${variable.key}: ${zodExpr},`);
|
|
373
392
|
}
|
|
374
|
-
lines.push("});");
|
|
375
|
-
lines.push("");
|
|
376
|
-
lines.push("export const clientEnvSchema = z.object({");
|
|
393
|
+
lines.push("});", "", "export const clientEnvSchema = z.object({");
|
|
377
394
|
for (const variable of clientVars) {
|
|
378
395
|
const effectiveType = variable.annotatedType ?? variable.inferredType;
|
|
379
396
|
const zodExpr = variable.isOptional ? `${toZodType(effectiveType)}.optional()` : toZodType(effectiveType);
|
|
380
397
|
lines.push(` ${variable.key}: ${zodExpr},`);
|
|
381
398
|
}
|
|
382
|
-
lines.push(
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
399
|
+
lines.push(
|
|
400
|
+
"});",
|
|
401
|
+
"",
|
|
402
|
+
"export const envSchema = serverEnvSchema.merge(clientEnvSchema);",
|
|
403
|
+
"export type Env = z.infer<typeof envSchema>;"
|
|
404
|
+
);
|
|
386
405
|
return lines.join("\n") + "\n";
|
|
387
406
|
}
|
|
388
407
|
function generateDeclaration(parsed) {
|
|
389
408
|
const fileName = path6.basename(parsed.filePath);
|
|
390
|
-
const lines = [
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
409
|
+
const lines = [
|
|
410
|
+
"// Generated by env-typegen \u2014 do not edit manually",
|
|
411
|
+
`// Source: ${fileName}`,
|
|
412
|
+
"",
|
|
413
|
+
"declare namespace NodeJS {",
|
|
414
|
+
" interface ProcessEnv {"
|
|
415
|
+
];
|
|
396
416
|
for (const variable of parsed.vars) {
|
|
397
417
|
const effectiveType = variable.annotatedType ?? variable.inferredType;
|
|
398
418
|
const optional = variable.isOptional ? "?" : "";
|
|
@@ -407,12 +427,14 @@ function generateDeclaration(parsed) {
|
|
|
407
427
|
}
|
|
408
428
|
lines.push(propLine);
|
|
409
429
|
}
|
|
410
|
-
lines.push(" }");
|
|
411
|
-
lines.push("}");
|
|
430
|
+
lines.push(" }", "}");
|
|
412
431
|
return lines.join("\n") + "\n";
|
|
413
432
|
}
|
|
414
433
|
|
|
415
434
|
// src/generators/t3-generator.ts
|
|
435
|
+
function escapeJsStringLiteral(value) {
|
|
436
|
+
return value.replaceAll("\\", String.raw`\\`).replaceAll('"', String.raw`\"`);
|
|
437
|
+
}
|
|
416
438
|
function toT3ZodType(envVarType) {
|
|
417
439
|
if (envVarType === "number") return "z.coerce.number()";
|
|
418
440
|
if (envVarType === "boolean") return "z.coerce.boolean()";
|
|
@@ -420,51 +442,47 @@ function toT3ZodType(envVarType) {
|
|
|
420
442
|
if (envVarType === "email") return "z.string().email()";
|
|
421
443
|
return "z.string()";
|
|
422
444
|
}
|
|
445
|
+
function buildZodExpr(variable) {
|
|
446
|
+
const effectiveType = variable.annotatedType ?? variable.inferredType;
|
|
447
|
+
let zodExpr = toT3ZodType(effectiveType);
|
|
448
|
+
if (variable.description !== void 0) {
|
|
449
|
+
zodExpr += `.describe("${escapeJsStringLiteral(variable.description)}")`;
|
|
450
|
+
}
|
|
451
|
+
if (variable.isOptional) {
|
|
452
|
+
zodExpr += ".optional()";
|
|
453
|
+
}
|
|
454
|
+
return zodExpr;
|
|
455
|
+
}
|
|
423
456
|
function generateT3Env(parsed) {
|
|
424
457
|
const serverVars = parsed.vars.filter((v) => !v.isClientSide);
|
|
425
458
|
const clientVars = parsed.vars.filter((v) => v.isClientSide);
|
|
426
|
-
const lines = [
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
459
|
+
const lines = [
|
|
460
|
+
"// Generated by env-typegen \u2014 do not edit manually",
|
|
461
|
+
'import { createEnv } from "@t3-oss/env-nextjs";',
|
|
462
|
+
'import { z } from "zod";',
|
|
463
|
+
"",
|
|
464
|
+
"export const env = createEnv({"
|
|
465
|
+
];
|
|
432
466
|
if (serverVars.length > 0) {
|
|
433
|
-
lines.push(
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
zodExpr += `.describe("${variable.description.replace(/"/g, '\\"')}")`;
|
|
439
|
-
}
|
|
440
|
-
if (variable.isOptional) {
|
|
441
|
-
zodExpr += ".optional()";
|
|
442
|
-
}
|
|
443
|
-
lines.push(` ${variable.key}: ${zodExpr},`);
|
|
444
|
-
}
|
|
445
|
-
lines.push(" },");
|
|
467
|
+
lines.push(
|
|
468
|
+
" server: {",
|
|
469
|
+
...serverVars.map((v) => ` ${v.key}: ${buildZodExpr(v)},`),
|
|
470
|
+
" },"
|
|
471
|
+
);
|
|
446
472
|
}
|
|
447
473
|
if (clientVars.length > 0) {
|
|
448
|
-
lines.push(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
zodExpr += `.describe("${variable.description.replace(/"/g, '\\"')}")`;
|
|
454
|
-
}
|
|
455
|
-
if (variable.isOptional) {
|
|
456
|
-
zodExpr += ".optional()";
|
|
457
|
-
}
|
|
458
|
-
lines.push(` ${variable.key}: ${zodExpr},`);
|
|
459
|
-
}
|
|
460
|
-
lines.push(" },");
|
|
461
|
-
}
|
|
462
|
-
lines.push(" runtimeEnv: {");
|
|
463
|
-
for (const variable of parsed.vars) {
|
|
464
|
-
lines.push(` ${variable.key}: process.env.${variable.key},`);
|
|
474
|
+
lines.push(
|
|
475
|
+
" client: {",
|
|
476
|
+
...clientVars.map((v) => ` ${v.key}: ${buildZodExpr(v)},`),
|
|
477
|
+
" },"
|
|
478
|
+
);
|
|
465
479
|
}
|
|
466
|
-
lines.push(
|
|
467
|
-
|
|
480
|
+
lines.push(
|
|
481
|
+
" runtimeEnv: {",
|
|
482
|
+
...parsed.vars.map((v) => ` ${v.key}: process.env.${v.key},`),
|
|
483
|
+
" },",
|
|
484
|
+
"});"
|
|
485
|
+
);
|
|
468
486
|
return lines.join("\n") + "\n";
|
|
469
487
|
}
|
|
470
488
|
var CONTRACT_FILE_NAMES = [
|
|
@@ -487,9 +505,9 @@ async function loadContract(cwd = process.cwd()) {
|
|
|
487
505
|
return void 0;
|
|
488
506
|
}
|
|
489
507
|
var CONFIG_FILE_NAMES = [
|
|
490
|
-
"env-typegen.config.ts",
|
|
491
508
|
"env-typegen.config.mjs",
|
|
492
|
-
"env-typegen.config.js"
|
|
509
|
+
"env-typegen.config.js",
|
|
510
|
+
"env-typegen.config.ts"
|
|
493
511
|
];
|
|
494
512
|
function defineConfig(config) {
|
|
495
513
|
return config;
|
|
@@ -498,6 +516,19 @@ async function loadConfig(cwd = process.cwd()) {
|
|
|
498
516
|
for (const name of CONFIG_FILE_NAMES) {
|
|
499
517
|
const filePath = path6.resolve(cwd, name);
|
|
500
518
|
if (existsSync(filePath)) {
|
|
519
|
+
if (filePath.endsWith(".ts")) {
|
|
520
|
+
throw new Error(
|
|
521
|
+
`Config file "${name}" was found but TypeScript files cannot be loaded directly at runtime.
|
|
522
|
+
Rename it to "env-typegen.config.mjs" and use ESM export syntax:
|
|
523
|
+
|
|
524
|
+
// env-typegen.config.mjs
|
|
525
|
+
import { defineConfig } from "@xlameiro/env-typegen";
|
|
526
|
+
export default defineConfig({ input: ".env.example" });
|
|
527
|
+
|
|
528
|
+
Tip: keep env-typegen.config.ts for IDE autocompletion and create a sibling
|
|
529
|
+
env-typegen.config.mjs for runtime loading.`
|
|
530
|
+
);
|
|
531
|
+
}
|
|
501
532
|
const fileUrl = pathToFileURL(filePath).href;
|
|
502
533
|
const mod = await import(fileUrl);
|
|
503
534
|
return mod.default;
|
|
@@ -610,7 +641,7 @@ async function runGenerate(options) {
|
|
|
610
641
|
const parsed = parseEnvFileContent(
|
|
611
642
|
content,
|
|
612
643
|
inputPath,
|
|
613
|
-
inferenceRules2
|
|
644
|
+
inferenceRules2 === void 0 ? void 0 : { inferenceRules: inferenceRules2 }
|
|
614
645
|
);
|
|
615
646
|
for (const generator of generators) {
|
|
616
647
|
let generated = buildOutput(generator, parsed);
|
|
@@ -938,8 +969,13 @@ function readEntryValue(entry, keys) {
|
|
|
938
969
|
}
|
|
939
970
|
return void 0;
|
|
940
971
|
}
|
|
972
|
+
function getVercelEntries(value) {
|
|
973
|
+
if (Array.isArray(value)) return value;
|
|
974
|
+
if (isRecord(value) && Array.isArray(value.envs)) return value.envs;
|
|
975
|
+
return [];
|
|
976
|
+
}
|
|
941
977
|
function parseVercelPayload(value) {
|
|
942
|
-
const entries =
|
|
978
|
+
const entries = getVercelEntries(value);
|
|
943
979
|
const result = {};
|
|
944
980
|
for (const entry of entries) {
|
|
945
981
|
if (!isRecord(entry)) continue;
|
|
@@ -950,8 +986,13 @@ function parseVercelPayload(value) {
|
|
|
950
986
|
}
|
|
951
987
|
return result;
|
|
952
988
|
}
|
|
989
|
+
function getCloudflareEntries(value) {
|
|
990
|
+
if (Array.isArray(value)) return value;
|
|
991
|
+
if (isRecord(value) && Array.isArray(value.result)) return value.result;
|
|
992
|
+
return [];
|
|
993
|
+
}
|
|
953
994
|
function parseCloudflarePayload(value) {
|
|
954
|
-
const entries =
|
|
995
|
+
const entries = getCloudflareEntries(value);
|
|
955
996
|
const result = {};
|
|
956
997
|
for (const entry of entries) {
|
|
957
998
|
if (!isRecord(entry)) continue;
|
|
@@ -969,7 +1010,7 @@ function parseAwsPayload(value) {
|
|
|
969
1010
|
if (!isRecord(entry)) continue;
|
|
970
1011
|
const name = readEntryValue(entry, ["Name", "name"]);
|
|
971
1012
|
if (name === void 0) continue;
|
|
972
|
-
const key = name.split("/").
|
|
1013
|
+
const key = name.split("/").findLast((part) => part.length > 0) ?? name;
|
|
973
1014
|
const envValue = readEntryValue(entry, ["Value", "value"]) ?? "";
|
|
974
1015
|
result[key] = envValue;
|
|
975
1016
|
}
|
|
@@ -1109,7 +1150,7 @@ function findDefaultContractPath(cwd) {
|
|
|
1109
1150
|
async function loadValidationContract(options) {
|
|
1110
1151
|
const { fallbackExamplePath, contractPath, cwd = process.cwd() } = options;
|
|
1111
1152
|
const discoveredContractPath = findDefaultContractPath(cwd);
|
|
1112
|
-
const resolvedContractPath = contractPath
|
|
1153
|
+
const resolvedContractPath = contractPath === void 0 ? discoveredContractPath : path6.resolve(cwd, contractPath);
|
|
1113
1154
|
if (resolvedContractPath !== void 0 && existsSync(resolvedContractPath)) {
|
|
1114
1155
|
const moduleUrl = pathToFileURL(resolvedContractPath).href;
|
|
1115
1156
|
const moduleValue = await import(moduleUrl);
|
|
@@ -1148,7 +1189,14 @@ async function loadPluginFromPath(pluginPath, cwd) {
|
|
|
1148
1189
|
const moduleValue = await import(pathToFileURL(resolvedPath).href);
|
|
1149
1190
|
const candidate = moduleValue.default ?? moduleValue.plugin;
|
|
1150
1191
|
if (isPlugin(candidate)) return candidate;
|
|
1151
|
-
throw new Error(
|
|
1192
|
+
throw new Error(
|
|
1193
|
+
`Invalid plugin at ${resolvedPath}.
|
|
1194
|
+
Expected a default export matching:
|
|
1195
|
+
{ name: string,
|
|
1196
|
+
transformSource?(ctx: { environment: string; values: Record<string, string> }): Record<string, string>,
|
|
1197
|
+
transformReport?(report: ValidationReport): ValidationReport,
|
|
1198
|
+
transformContract?(contract: EnvContract): EnvContract }`
|
|
1199
|
+
);
|
|
1152
1200
|
}
|
|
1153
1201
|
async function loadPlugins(options) {
|
|
1154
1202
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -1193,8 +1241,8 @@ function applyReportPlugins(report, plugins) {
|
|
|
1193
1241
|
}
|
|
1194
1242
|
|
|
1195
1243
|
// src/validation/engine.ts
|
|
1196
|
-
var EMAIL_RE = /^[^@\s]+@[^@\s]
|
|
1197
|
-
var SEMVER_RE2 = /^\d+\.\d+\.\d+(?:-[
|
|
1244
|
+
var EMAIL_RE = /^[^@\s]+@[^@\s.]+(?:\.[^@\s.]+)+$/;
|
|
1245
|
+
var SEMVER_RE2 = /^\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/;
|
|
1198
1246
|
function detectReceivedType(value) {
|
|
1199
1247
|
const normalized = value.trim();
|
|
1200
1248
|
if (normalized.length === 0) return "unknown";
|
|
@@ -1214,65 +1262,72 @@ function detectReceivedType(value) {
|
|
|
1214
1262
|
}
|
|
1215
1263
|
return "string";
|
|
1216
1264
|
}
|
|
1217
|
-
function
|
|
1218
|
-
const
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
if (expected.type === "string") return { isValid: true, receivedType };
|
|
1222
|
-
if (expected.type === "number") {
|
|
1223
|
-
const parsed = Number(normalized);
|
|
1224
|
-
if (Number.isNaN(parsed) || !Number.isFinite(parsed)) {
|
|
1225
|
-
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1226
|
-
}
|
|
1227
|
-
if (expected.min !== void 0 && parsed < expected.min) {
|
|
1228
|
-
return { isValid: false, receivedType, issueType: "invalid_value" };
|
|
1229
|
-
}
|
|
1230
|
-
if (expected.max !== void 0 && parsed > expected.max) {
|
|
1231
|
-
return { isValid: false, receivedType, issueType: "invalid_value" };
|
|
1232
|
-
}
|
|
1233
|
-
return { isValid: true, receivedType };
|
|
1265
|
+
function validateNumber(expected, normalized, receivedType) {
|
|
1266
|
+
const parsed = Number(normalized);
|
|
1267
|
+
if (Number.isNaN(parsed) || !Number.isFinite(parsed)) {
|
|
1268
|
+
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1234
1269
|
}
|
|
1235
|
-
if (expected.
|
|
1236
|
-
|
|
1237
|
-
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1238
|
-
}
|
|
1239
|
-
return { isValid: true, receivedType };
|
|
1270
|
+
if (expected.min !== void 0 && parsed < expected.min) {
|
|
1271
|
+
return { isValid: false, receivedType, issueType: "invalid_value" };
|
|
1240
1272
|
}
|
|
1241
|
-
if (expected.
|
|
1242
|
-
|
|
1243
|
-
return { isValid: false, receivedType, issueType: "invalid_value" };
|
|
1244
|
-
}
|
|
1245
|
-
return { isValid: true, receivedType };
|
|
1273
|
+
if (expected.max !== void 0 && parsed > expected.max) {
|
|
1274
|
+
return { isValid: false, receivedType, issueType: "invalid_value" };
|
|
1246
1275
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1276
|
+
return { isValid: true, receivedType };
|
|
1277
|
+
}
|
|
1278
|
+
function validateBoolean(normalized, receivedType) {
|
|
1279
|
+
if (!["true", "false", "1", "0", "yes", "no"].includes(normalized.toLowerCase())) {
|
|
1280
|
+
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1281
|
+
}
|
|
1282
|
+
return { isValid: true, receivedType };
|
|
1283
|
+
}
|
|
1284
|
+
function validateEnum(expected, normalized, receivedType) {
|
|
1285
|
+
if (!expected.values.includes(normalized)) {
|
|
1286
|
+
return { isValid: false, receivedType, issueType: "invalid_value" };
|
|
1256
1287
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1288
|
+
return { isValid: true, receivedType };
|
|
1289
|
+
}
|
|
1290
|
+
function validateUrl(normalized, receivedType) {
|
|
1291
|
+
try {
|
|
1292
|
+
const value = new URL(normalized);
|
|
1293
|
+
if (value.protocol.length === 0)
|
|
1259
1294
|
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1260
1295
|
return { isValid: true, receivedType };
|
|
1296
|
+
} catch {
|
|
1297
|
+
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1261
1298
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1299
|
+
}
|
|
1300
|
+
function validateEmail(normalized, receivedType) {
|
|
1301
|
+
if (!EMAIL_RE.test(normalized))
|
|
1302
|
+
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1303
|
+
return { isValid: true, receivedType };
|
|
1304
|
+
}
|
|
1305
|
+
function validateJson(normalized, receivedType) {
|
|
1306
|
+
try {
|
|
1307
|
+
const parsed = JSON.parse(normalized);
|
|
1308
|
+
if (typeof parsed === "object" && parsed !== null) return { isValid: true, receivedType };
|
|
1309
|
+
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1310
|
+
} catch {
|
|
1311
|
+
return { isValid: false, receivedType, issueType: "invalid_type" };
|
|
1270
1312
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1313
|
+
}
|
|
1314
|
+
function validateSemver(normalized, receivedType) {
|
|
1315
|
+
if (!SEMVER_RE2.test(normalized))
|
|
1316
|
+
return { isValid: false, receivedType, issueType: "invalid_value" };
|
|
1317
|
+
return { isValid: true, receivedType };
|
|
1318
|
+
}
|
|
1319
|
+
function validateValueAgainstExpected(expected, rawValue) {
|
|
1320
|
+
const normalized = rawValue.trim();
|
|
1321
|
+
const receivedType = detectReceivedType(normalized);
|
|
1322
|
+
if (expected.type === "unknown" || expected.type === "string")
|
|
1274
1323
|
return { isValid: true, receivedType };
|
|
1275
|
-
|
|
1324
|
+
if (expected.type === "number") return validateNumber(expected, normalized, receivedType);
|
|
1325
|
+
if (expected.type === "boolean") return validateBoolean(normalized, receivedType);
|
|
1326
|
+
if (expected.type === "enum") return validateEnum(expected, normalized, receivedType);
|
|
1327
|
+
if (expected.type === "url") return validateUrl(normalized, receivedType);
|
|
1328
|
+
if (expected.type === "email") return validateEmail(normalized, receivedType);
|
|
1329
|
+
if (expected.type === "json") return validateJson(normalized, receivedType);
|
|
1330
|
+
if (expected.type === "semver") return validateSemver(normalized, receivedType);
|
|
1276
1331
|
return { isValid: true, receivedType };
|
|
1277
1332
|
}
|
|
1278
1333
|
function toIssueCode(issueType) {
|
|
@@ -1344,57 +1399,62 @@ function buildReport(env, issues, recommendations) {
|
|
|
1344
1399
|
function isClientSecret(variable, key) {
|
|
1345
1400
|
return variable.secret === true && (variable.clientSide || key.startsWith("NEXT_PUBLIC_"));
|
|
1346
1401
|
}
|
|
1402
|
+
function checkContractVariable(key, variable, context) {
|
|
1403
|
+
const { options, issues } = context;
|
|
1404
|
+
const value = options.values[key];
|
|
1405
|
+
const hasValue = value !== void 0 && value.trim().length > 0;
|
|
1406
|
+
if (variable.required && !hasValue) {
|
|
1407
|
+
issues.push(
|
|
1408
|
+
createIssue({
|
|
1409
|
+
type: "missing",
|
|
1410
|
+
severity: "error",
|
|
1411
|
+
key,
|
|
1412
|
+
environment: options.environment,
|
|
1413
|
+
message: `Required variable ${key} is missing.`,
|
|
1414
|
+
debugValues: options.debugValues,
|
|
1415
|
+
expected: variable.expected
|
|
1416
|
+
})
|
|
1417
|
+
);
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
if (!hasValue) return;
|
|
1421
|
+
const validation = validateValueAgainstExpected(variable.expected, value);
|
|
1422
|
+
if (!validation.isValid) {
|
|
1423
|
+
const message = validation.issueType === "invalid_type" ? `Variable ${key} has invalid type.` : `Variable ${key} has invalid value.`;
|
|
1424
|
+
issues.push(
|
|
1425
|
+
createIssue({
|
|
1426
|
+
type: validation.issueType,
|
|
1427
|
+
severity: "error",
|
|
1428
|
+
key,
|
|
1429
|
+
environment: options.environment,
|
|
1430
|
+
message,
|
|
1431
|
+
value,
|
|
1432
|
+
debugValues: options.debugValues,
|
|
1433
|
+
expected: variable.expected,
|
|
1434
|
+
receivedType: validation.receivedType
|
|
1435
|
+
})
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
if (isClientSecret(variable, key)) {
|
|
1439
|
+
issues.push(
|
|
1440
|
+
createIssue({
|
|
1441
|
+
type: "secret_exposed",
|
|
1442
|
+
severity: "error",
|
|
1443
|
+
key,
|
|
1444
|
+
environment: options.environment,
|
|
1445
|
+
message: `Secret variable ${key} is marked as client-side.`,
|
|
1446
|
+
value,
|
|
1447
|
+
debugValues: options.debugValues,
|
|
1448
|
+
expected: variable.expected
|
|
1449
|
+
})
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1347
1453
|
function validateAgainstContract(options) {
|
|
1348
1454
|
const issues = [];
|
|
1349
1455
|
const contractKeys = new Set(Object.keys(options.contract.variables));
|
|
1350
1456
|
for (const [key, variable] of Object.entries(options.contract.variables)) {
|
|
1351
|
-
|
|
1352
|
-
const hasValue = value !== void 0 && value.trim().length > 0;
|
|
1353
|
-
if (variable.required && !hasValue) {
|
|
1354
|
-
issues.push(
|
|
1355
|
-
createIssue({
|
|
1356
|
-
type: "missing",
|
|
1357
|
-
severity: "error",
|
|
1358
|
-
key,
|
|
1359
|
-
environment: options.environment,
|
|
1360
|
-
message: `Required variable ${key} is missing.`,
|
|
1361
|
-
debugValues: options.debugValues,
|
|
1362
|
-
expected: variable.expected
|
|
1363
|
-
})
|
|
1364
|
-
);
|
|
1365
|
-
continue;
|
|
1366
|
-
}
|
|
1367
|
-
if (!hasValue) continue;
|
|
1368
|
-
const validation = validateValueAgainstExpected(variable.expected, value);
|
|
1369
|
-
if (!validation.isValid) {
|
|
1370
|
-
issues.push(
|
|
1371
|
-
createIssue({
|
|
1372
|
-
type: validation.issueType,
|
|
1373
|
-
severity: "error",
|
|
1374
|
-
key,
|
|
1375
|
-
environment: options.environment,
|
|
1376
|
-
message: validation.issueType === "invalid_type" ? `Variable ${key} has invalid type.` : `Variable ${key} has invalid value.`,
|
|
1377
|
-
value,
|
|
1378
|
-
debugValues: options.debugValues,
|
|
1379
|
-
expected: variable.expected,
|
|
1380
|
-
receivedType: validation.receivedType
|
|
1381
|
-
})
|
|
1382
|
-
);
|
|
1383
|
-
}
|
|
1384
|
-
if (isClientSecret(variable, key)) {
|
|
1385
|
-
issues.push(
|
|
1386
|
-
createIssue({
|
|
1387
|
-
type: "secret_exposed",
|
|
1388
|
-
severity: "error",
|
|
1389
|
-
key,
|
|
1390
|
-
environment: options.environment,
|
|
1391
|
-
message: `Secret variable ${key} is marked as client-side.`,
|
|
1392
|
-
value,
|
|
1393
|
-
debugValues: options.debugValues,
|
|
1394
|
-
expected: variable.expected
|
|
1395
|
-
})
|
|
1396
|
-
);
|
|
1397
|
-
}
|
|
1457
|
+
checkContractVariable(key, variable, { options, issues });
|
|
1398
1458
|
}
|
|
1399
1459
|
for (const [key, value] of Object.entries(options.values)) {
|
|
1400
1460
|
if (contractKeys.has(key)) continue;
|
|
@@ -1422,6 +1482,97 @@ function collectUnionKeys(contract, sources) {
|
|
|
1422
1482
|
}
|
|
1423
1483
|
return union;
|
|
1424
1484
|
}
|
|
1485
|
+
function diffMissingEntries(key, missing, context) {
|
|
1486
|
+
const { variable, options, issues } = context;
|
|
1487
|
+
for (const entry of missing) {
|
|
1488
|
+
issues.push(
|
|
1489
|
+
createIssue({
|
|
1490
|
+
type: "missing",
|
|
1491
|
+
severity: "error",
|
|
1492
|
+
key,
|
|
1493
|
+
environment: entry.sourceName,
|
|
1494
|
+
message: `Variable ${key} is missing in ${entry.sourceName}.`,
|
|
1495
|
+
debugValues: options.debugValues,
|
|
1496
|
+
...variable !== void 0 && { expected: variable.expected }
|
|
1497
|
+
})
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
function diffTypeConflicts(key, present, context) {
|
|
1502
|
+
const { variable, options, issues } = context;
|
|
1503
|
+
const typeBySource = /* @__PURE__ */ new Map();
|
|
1504
|
+
for (const entry of present) {
|
|
1505
|
+
typeBySource.set(entry.sourceName, detectReceivedType(entry.value ?? ""));
|
|
1506
|
+
}
|
|
1507
|
+
if (new Set(typeBySource.values()).size <= 1) return;
|
|
1508
|
+
for (const [sourceName, detectedType] of typeBySource.entries()) {
|
|
1509
|
+
issues.push(
|
|
1510
|
+
createIssue({
|
|
1511
|
+
type: "conflict",
|
|
1512
|
+
severity: "error",
|
|
1513
|
+
key,
|
|
1514
|
+
environment: sourceName,
|
|
1515
|
+
message: `Variable ${key} has conflicting inferred type across environments.`,
|
|
1516
|
+
debugValues: options.debugValues,
|
|
1517
|
+
receivedType: detectedType,
|
|
1518
|
+
...options.sources[sourceName]?.[key] !== void 0 && {
|
|
1519
|
+
value: options.sources[sourceName]?.[key]
|
|
1520
|
+
},
|
|
1521
|
+
...variable !== void 0 && { expected: variable.expected }
|
|
1522
|
+
})
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
function diffPresentEntry(key, entry, context) {
|
|
1527
|
+
const { variable, options, issues } = context;
|
|
1528
|
+
if (entry.value === void 0) return;
|
|
1529
|
+
if (variable === void 0) {
|
|
1530
|
+
const severity = options.strict ? "error" : "warning";
|
|
1531
|
+
issues.push(
|
|
1532
|
+
createIssue({
|
|
1533
|
+
type: "extra",
|
|
1534
|
+
severity,
|
|
1535
|
+
key,
|
|
1536
|
+
environment: entry.sourceName,
|
|
1537
|
+
message: `Variable ${key} is not defined in the contract.`,
|
|
1538
|
+
value: entry.value,
|
|
1539
|
+
debugValues: options.debugValues
|
|
1540
|
+
})
|
|
1541
|
+
);
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
const validation = validateValueAgainstExpected(variable.expected, entry.value);
|
|
1545
|
+
if (!validation.isValid) {
|
|
1546
|
+
const message = validation.issueType === "invalid_type" ? `Variable ${key} has invalid type in ${entry.sourceName}.` : `Variable ${key} has invalid value in ${entry.sourceName}.`;
|
|
1547
|
+
issues.push(
|
|
1548
|
+
createIssue({
|
|
1549
|
+
type: validation.issueType,
|
|
1550
|
+
severity: "error",
|
|
1551
|
+
key,
|
|
1552
|
+
environment: entry.sourceName,
|
|
1553
|
+
message,
|
|
1554
|
+
value: entry.value,
|
|
1555
|
+
debugValues: options.debugValues,
|
|
1556
|
+
expected: variable.expected,
|
|
1557
|
+
receivedType: validation.receivedType
|
|
1558
|
+
})
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
if (isClientSecret(variable, key)) {
|
|
1562
|
+
issues.push(
|
|
1563
|
+
createIssue({
|
|
1564
|
+
type: "secret_exposed",
|
|
1565
|
+
severity: "error",
|
|
1566
|
+
key,
|
|
1567
|
+
environment: entry.sourceName,
|
|
1568
|
+
message: `Secret variable ${key} is marked as client-side.`,
|
|
1569
|
+
value: entry.value,
|
|
1570
|
+
debugValues: options.debugValues,
|
|
1571
|
+
expected: variable.expected
|
|
1572
|
+
})
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1425
1576
|
function diffEnvironmentSources(options) {
|
|
1426
1577
|
const issues = [];
|
|
1427
1578
|
const sourceNames = Object.keys(options.sources);
|
|
@@ -1454,92 +1605,13 @@ function diffEnvironmentSources(options) {
|
|
|
1454
1605
|
}
|
|
1455
1606
|
continue;
|
|
1456
1607
|
}
|
|
1608
|
+
const ctx = { variable, options, issues };
|
|
1457
1609
|
if (present.length > 0) {
|
|
1458
|
-
|
|
1459
|
-
issues.push(
|
|
1460
|
-
createIssue({
|
|
1461
|
-
type: "missing",
|
|
1462
|
-
severity: "error",
|
|
1463
|
-
key,
|
|
1464
|
-
environment: entry.sourceName,
|
|
1465
|
-
message: `Variable ${key} is missing in ${entry.sourceName}.`,
|
|
1466
|
-
debugValues: options.debugValues,
|
|
1467
|
-
...variable !== void 0 && { expected: variable.expected }
|
|
1468
|
-
})
|
|
1469
|
-
);
|
|
1470
|
-
}
|
|
1610
|
+
diffMissingEntries(key, missing, ctx);
|
|
1471
1611
|
}
|
|
1472
|
-
|
|
1612
|
+
diffTypeConflicts(key, present, ctx);
|
|
1473
1613
|
for (const entry of present) {
|
|
1474
|
-
|
|
1475
|
-
typeBySource.set(entry.sourceName, detected);
|
|
1476
|
-
}
|
|
1477
|
-
if (new Set(typeBySource.values()).size > 1) {
|
|
1478
|
-
for (const [sourceName, detectedType] of typeBySource.entries()) {
|
|
1479
|
-
issues.push(
|
|
1480
|
-
createIssue({
|
|
1481
|
-
type: "conflict",
|
|
1482
|
-
severity: "error",
|
|
1483
|
-
key,
|
|
1484
|
-
environment: sourceName,
|
|
1485
|
-
message: `Variable ${key} has conflicting inferred type across environments.`,
|
|
1486
|
-
debugValues: options.debugValues,
|
|
1487
|
-
receivedType: detectedType,
|
|
1488
|
-
...options.sources[sourceName]?.[key] !== void 0 && {
|
|
1489
|
-
value: options.sources[sourceName]?.[key]
|
|
1490
|
-
},
|
|
1491
|
-
...variable !== void 0 && { expected: variable.expected }
|
|
1492
|
-
})
|
|
1493
|
-
);
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
for (const entry of present) {
|
|
1497
|
-
if (entry.value === void 0) continue;
|
|
1498
|
-
if (variable === void 0) {
|
|
1499
|
-
const severity = options.strict ? "error" : "warning";
|
|
1500
|
-
issues.push(
|
|
1501
|
-
createIssue({
|
|
1502
|
-
type: "extra",
|
|
1503
|
-
severity,
|
|
1504
|
-
key,
|
|
1505
|
-
environment: entry.sourceName,
|
|
1506
|
-
message: `Variable ${key} is not defined in the contract.`,
|
|
1507
|
-
value: entry.value,
|
|
1508
|
-
debugValues: options.debugValues
|
|
1509
|
-
})
|
|
1510
|
-
);
|
|
1511
|
-
continue;
|
|
1512
|
-
}
|
|
1513
|
-
const validation = validateValueAgainstExpected(variable.expected, entry.value);
|
|
1514
|
-
if (!validation.isValid) {
|
|
1515
|
-
issues.push(
|
|
1516
|
-
createIssue({
|
|
1517
|
-
type: validation.issueType,
|
|
1518
|
-
severity: "error",
|
|
1519
|
-
key,
|
|
1520
|
-
environment: entry.sourceName,
|
|
1521
|
-
message: validation.issueType === "invalid_type" ? `Variable ${key} has invalid type in ${entry.sourceName}.` : `Variable ${key} has invalid value in ${entry.sourceName}.`,
|
|
1522
|
-
value: entry.value,
|
|
1523
|
-
debugValues: options.debugValues,
|
|
1524
|
-
expected: variable.expected,
|
|
1525
|
-
receivedType: validation.receivedType
|
|
1526
|
-
})
|
|
1527
|
-
);
|
|
1528
|
-
}
|
|
1529
|
-
if (isClientSecret(variable, key)) {
|
|
1530
|
-
issues.push(
|
|
1531
|
-
createIssue({
|
|
1532
|
-
type: "secret_exposed",
|
|
1533
|
-
severity: "error",
|
|
1534
|
-
key,
|
|
1535
|
-
environment: entry.sourceName,
|
|
1536
|
-
message: `Secret variable ${key} is marked as client-side.`,
|
|
1537
|
-
value: entry.value,
|
|
1538
|
-
debugValues: options.debugValues,
|
|
1539
|
-
expected: variable.expected
|
|
1540
|
-
})
|
|
1541
|
-
);
|
|
1542
|
-
}
|
|
1614
|
+
diffPresentEntry(key, entry, ctx);
|
|
1543
1615
|
}
|
|
1544
1616
|
}
|
|
1545
1617
|
return buildReport("diff", issues);
|
|
@@ -1588,7 +1660,7 @@ function parseEnvSourceContent(content) {
|
|
|
1588
1660
|
for (const line of lines) {
|
|
1589
1661
|
const trimmed = line.trim();
|
|
1590
1662
|
if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
|
|
1591
|
-
const match = /^(?:export\s+)?([A-Za-z_]
|
|
1663
|
+
const match = /^(?:export\s+)?([A-Za-z_]\w*)=(.*)$/.exec(trimmed);
|
|
1592
1664
|
if (match === null) continue;
|
|
1593
1665
|
const key = match[1] ?? "";
|
|
1594
1666
|
const rawValue = match[2] ?? "";
|
|
@@ -1601,11 +1673,11 @@ async function loadEnvSource(options) {
|
|
|
1601
1673
|
try {
|
|
1602
1674
|
const content = await readFile(resolvedPath, "utf8");
|
|
1603
1675
|
return parseEnvSourceContent(content);
|
|
1604
|
-
} catch (
|
|
1605
|
-
if (options.allowMissing === true &&
|
|
1676
|
+
} catch (error_) {
|
|
1677
|
+
if (options.allowMissing === true && error_ instanceof Error && "code" in error_ && error_.code === "ENOENT") {
|
|
1606
1678
|
return {};
|
|
1607
1679
|
}
|
|
1608
|
-
throw
|
|
1680
|
+
throw error_;
|
|
1609
1681
|
}
|
|
1610
1682
|
}
|
|
1611
1683
|
function toJsonString(report, mode) {
|
|
@@ -1615,8 +1687,8 @@ function toJsonString(report, mode) {
|
|
|
1615
1687
|
`;
|
|
1616
1688
|
}
|
|
1617
1689
|
function formatIssue(issue) {
|
|
1618
|
-
const expected = issue.expected
|
|
1619
|
-
const received = issue.receivedType
|
|
1690
|
+
const expected = issue.expected === void 0 ? "" : ` expected=${issue.expected.type}`;
|
|
1691
|
+
const received = issue.receivedType === void 0 ? "" : ` received=${issue.receivedType}`;
|
|
1620
1692
|
return `${issue.severity.toUpperCase()} [${issue.code}] ${issue.environment}:${issue.key} ${issue.message}${expected}${received}`;
|
|
1621
1693
|
}
|
|
1622
1694
|
function formatHumanReport(report) {
|
|
@@ -1625,15 +1697,13 @@ function formatHumanReport(report) {
|
|
|
1625
1697
|
`Status: ${report.status.toUpperCase()} (errors=${report.summary.errors}, warnings=${report.summary.warnings}, total=${report.summary.total})`
|
|
1626
1698
|
);
|
|
1627
1699
|
if (report.issues.length > 0) {
|
|
1628
|
-
lines.push("");
|
|
1629
|
-
lines.push("Issues:");
|
|
1700
|
+
lines.push("", "Issues:");
|
|
1630
1701
|
for (const issue of report.issues) {
|
|
1631
1702
|
lines.push(`- ${formatIssue(issue)}`);
|
|
1632
1703
|
}
|
|
1633
1704
|
}
|
|
1634
1705
|
if (report.recommendations !== void 0 && report.recommendations.length > 0) {
|
|
1635
|
-
lines.push("");
|
|
1636
|
-
lines.push("Recommendations:");
|
|
1706
|
+
lines.push("", "Recommendations:");
|
|
1637
1707
|
for (const recommendation of report.recommendations) {
|
|
1638
1708
|
lines.push(`- ${recommendation}`);
|
|
1639
1709
|
}
|
|
@@ -1678,7 +1748,11 @@ var HELP_TEXT = {
|
|
|
1678
1748
|
" --cloud-file <path> Cloud snapshot JSON file",
|
|
1679
1749
|
" --plugin <path> Plugin module path (repeatable)",
|
|
1680
1750
|
" -c, --config <path> Config file path",
|
|
1681
|
-
" -h, --help Show this help"
|
|
1751
|
+
" -h, --help Show this help",
|
|
1752
|
+
"",
|
|
1753
|
+
"Exit codes:",
|
|
1754
|
+
" 0 All checks passed (status: ok or warn)",
|
|
1755
|
+
" 1 One or more checks failed (status: fail) or invalid usage"
|
|
1682
1756
|
].join("\n"),
|
|
1683
1757
|
diff: [
|
|
1684
1758
|
"Usage: env-typegen diff [options]",
|
|
@@ -1697,7 +1771,11 @@ var HELP_TEXT = {
|
|
|
1697
1771
|
" --cloud-file <path> Cloud snapshot JSON file added to diff sources",
|
|
1698
1772
|
" --plugin <path> Plugin module path (repeatable)",
|
|
1699
1773
|
" -c, --config <path> Config file path",
|
|
1700
|
-
" -h, --help Show this help"
|
|
1774
|
+
" -h, --help Show this help",
|
|
1775
|
+
"",
|
|
1776
|
+
"Exit codes:",
|
|
1777
|
+
" 0 All checks passed (status: ok or warn)",
|
|
1778
|
+
" 1 One or more checks failed (status: fail) or invalid usage"
|
|
1701
1779
|
].join("\n"),
|
|
1702
1780
|
doctor: [
|
|
1703
1781
|
"Usage: env-typegen doctor [options]",
|
|
@@ -1717,7 +1795,11 @@ var HELP_TEXT = {
|
|
|
1717
1795
|
" --cloud-file <path> Cloud snapshot JSON file",
|
|
1718
1796
|
" --plugin <path> Plugin module path (repeatable)",
|
|
1719
1797
|
" -c, --config <path> Config file path",
|
|
1720
|
-
" -h, --help Show this help"
|
|
1798
|
+
" -h, --help Show this help",
|
|
1799
|
+
"",
|
|
1800
|
+
"Exit codes:",
|
|
1801
|
+
" 0 All checks passed (status: ok or warn)",
|
|
1802
|
+
" 1 One or more checks failed (status: fail) or invalid usage"
|
|
1721
1803
|
].join("\n")
|
|
1722
1804
|
};
|
|
1723
1805
|
function resolveConfigRelative(value, configDir) {
|
|
@@ -1804,7 +1886,10 @@ function parseValidationArgs(argv) {
|
|
|
1804
1886
|
}
|
|
1805
1887
|
});
|
|
1806
1888
|
const castValues = values;
|
|
1807
|
-
|
|
1889
|
+
let jsonMode = "off";
|
|
1890
|
+
if (castValues.json === true) {
|
|
1891
|
+
jsonMode = assignedMode === "off" ? "compact" : assignedMode;
|
|
1892
|
+
}
|
|
1808
1893
|
return { values: castValues, jsonMode };
|
|
1809
1894
|
}
|
|
1810
1895
|
function resolveStrict(values, fileConfig) {
|
|
@@ -1877,12 +1962,12 @@ async function runCheckCommand(args) {
|
|
|
1877
1962
|
const provider = context.cloudProvider;
|
|
1878
1963
|
let environment = args.values.env?.[0] ?? ".env";
|
|
1879
1964
|
let sourceValues;
|
|
1880
|
-
if (provider
|
|
1965
|
+
if (provider === void 0) {
|
|
1966
|
+
sourceValues = await loadEnvSource({ filePath: environment, allowMissing: true });
|
|
1967
|
+
} else {
|
|
1881
1968
|
const cloudFile = context.cloudFile ?? `${provider}.env.json`;
|
|
1882
1969
|
sourceValues = await loadCloudSource({ provider, filePath: cloudFile });
|
|
1883
1970
|
environment = `cloud:${provider}`;
|
|
1884
|
-
} else {
|
|
1885
|
-
sourceValues = await loadEnvSource({ filePath: environment, allowMissing: true });
|
|
1886
1971
|
}
|
|
1887
1972
|
sourceValues = applySourcePlugins({ environment, values: sourceValues }, context.plugins);
|
|
1888
1973
|
const report = applyReportPlugins(
|