@romaintaillandier1978/dotenv-never-lies 1.3.0 → 1.5.0

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.
Files changed (247) hide show
  1. package/README.md +7 -5
  2. package/dist/cli/commands/assert.d.ts +5 -1
  3. package/dist/cli/commands/assert.d.ts.map +1 -1
  4. package/dist/cli/commands/assert.js +5 -2
  5. package/dist/cli/commands/export.d.ts +1 -0
  6. package/dist/cli/commands/export.d.ts.map +1 -1
  7. package/dist/cli/commands/export.js +32 -10
  8. package/dist/cli/commands/infer.d.ts +4 -1
  9. package/dist/cli/commands/infer.d.ts.map +1 -1
  10. package/dist/cli/commands/infer.js +57 -19
  11. package/dist/cli/index.d.ts +2 -1
  12. package/dist/cli/index.d.ts.map +1 -1
  13. package/dist/cli/index.js +36 -11
  14. package/dist/cli/utils/infer-rule-engine.d.ts +4 -0
  15. package/dist/cli/utils/infer-rule-engine.d.ts.map +1 -0
  16. package/dist/cli/utils/infer-rule-engine.js +30 -0
  17. package/dist/cli/utils/resolve-schema.d.ts.map +1 -1
  18. package/dist/cli/utils/resolve-schema.js +17 -16
  19. package/dist/core.d.ts +8 -3
  20. package/dist/core.d.ts.map +1 -1
  21. package/dist/core.js +29 -1
  22. package/dist/dnl-config.d.ts +10 -0
  23. package/dist/dnl-config.d.ts.map +1 -0
  24. package/dist/dnl-config.js +4 -0
  25. package/dist/infer/__tests__/rules/key-value.test.d.ts +2 -0
  26. package/dist/infer/__tests__/rules/key-value.test.d.ts.map +1 -0
  27. package/dist/infer/__tests__/rules/key-value.test.js +56 -0
  28. package/dist/infer/__tests__/rules/list.test.js +12 -45
  29. package/dist/infer/cross-rules/secret-non-string.d.ts +3 -0
  30. package/dist/infer/cross-rules/secret-non-string.d.ts.map +1 -0
  31. package/dist/infer/cross-rules/secret-non-string.js +5 -0
  32. package/dist/infer/generated/basic.d.ts +7 -7
  33. package/dist/infer/generated/basic.d.ts.map +1 -1
  34. package/dist/infer/generated/basic.js +12 -6
  35. package/dist/infer/generated/boolean.d.ts +2 -2
  36. package/dist/infer/generated/boolean.d.ts.map +1 -1
  37. package/dist/infer/generated/boolean.js +3 -2
  38. package/dist/infer/generated/duration.d.ts +2 -2
  39. package/dist/infer/generated/duration.d.ts.map +1 -1
  40. package/dist/infer/generated/duration.js +1 -0
  41. package/dist/infer/generated/ip.d.ts +2 -2
  42. package/dist/infer/generated/ip.d.ts.map +1 -1
  43. package/dist/infer/generated/ip.js +1 -0
  44. package/dist/infer/generated/json.d.ts +3 -3
  45. package/dist/infer/generated/json.d.ts.map +1 -1
  46. package/dist/infer/generated/json.js +1 -0
  47. package/dist/infer/generated/key-value.d.ts +4 -0
  48. package/dist/infer/generated/key-value.d.ts.map +1 -0
  49. package/dist/infer/generated/key-value.js +11 -0
  50. package/dist/infer/generated/list.d.ts +5 -7
  51. package/dist/infer/generated/list.d.ts.map +1 -1
  52. package/dist/infer/generated/list.js +13 -21
  53. package/dist/infer/generated/port.d.ts +3 -3
  54. package/dist/infer/generated/port.d.ts.map +1 -1
  55. package/dist/infer/generated/port.js +1 -0
  56. package/dist/infer/generated/url.d.ts +13 -13
  57. package/dist/infer/generated/url.d.ts.map +1 -1
  58. package/dist/infer/generated/url.js +6 -0
  59. package/dist/infer/generated/version.d.ts +3 -3
  60. package/dist/infer/generated/version.d.ts.map +1 -1
  61. package/dist/infer/generated/version.js +1 -0
  62. package/dist/infer/helpers.d.ts +1 -2
  63. package/dist/infer/helpers.d.ts.map +1 -1
  64. package/dist/infer/helpers.js +5 -16
  65. package/dist/infer/index.d.ts +2 -2
  66. package/dist/infer/index.d.ts.map +1 -1
  67. package/dist/infer/index.js +7 -11
  68. package/dist/infer/official-preset-registry.d.ts +3 -0
  69. package/dist/infer/official-preset-registry.d.ts.map +1 -0
  70. package/dist/infer/official-preset-registry.js +67 -0
  71. package/dist/infer/presets/agenda.d.ts +3 -0
  72. package/dist/infer/presets/agenda.d.ts.map +1 -0
  73. package/dist/infer/presets/agenda.js +15 -0
  74. package/dist/infer/presets/amqplib.d.ts +3 -0
  75. package/dist/infer/presets/amqplib.d.ts.map +1 -0
  76. package/dist/infer/presets/amqplib.js +15 -0
  77. package/dist/infer/presets/aws-sdk.d.ts +3 -0
  78. package/dist/infer/presets/aws-sdk.d.ts.map +1 -0
  79. package/dist/infer/presets/aws-sdk.js +33 -0
  80. package/dist/infer/presets/aws-sdk_client-s3.d.ts +3 -0
  81. package/dist/infer/presets/aws-sdk_client-s3.d.ts.map +1 -0
  82. package/dist/infer/presets/aws-sdk_client-s3.js +15 -0
  83. package/dist/infer/presets/bcrypt.d.ts +3 -0
  84. package/dist/infer/presets/bcrypt.d.ts.map +1 -0
  85. package/dist/infer/presets/bcrypt.js +15 -0
  86. package/dist/infer/presets/bull.d.ts +3 -0
  87. package/dist/infer/presets/bull.d.ts.map +1 -0
  88. package/dist/infer/presets/bull.js +116 -0
  89. package/dist/infer/presets/cookie-parser.d.ts +3 -0
  90. package/dist/infer/presets/cookie-parser.d.ts.map +1 -0
  91. package/dist/infer/presets/cookie-parser.js +15 -0
  92. package/dist/infer/presets/cron.d.ts +3 -0
  93. package/dist/infer/presets/cron.d.ts.map +1 -0
  94. package/dist/infer/presets/cron.js +22 -0
  95. package/dist/infer/presets/dotenv.d.ts +3 -0
  96. package/dist/infer/presets/dotenv.d.ts.map +1 -0
  97. package/dist/infer/presets/dotenv.js +15 -0
  98. package/dist/infer/presets/express-session.d.ts +3 -0
  99. package/dist/infer/presets/express-session.d.ts.map +1 -0
  100. package/dist/infer/presets/express-session.js +40 -0
  101. package/dist/infer/presets/google-cloud_storage.d.ts +3 -0
  102. package/dist/infer/presets/google-cloud_storage.d.ts.map +1 -0
  103. package/dist/infer/presets/google-cloud_storage.js +15 -0
  104. package/dist/infer/presets/google-maps.d.ts +3 -0
  105. package/dist/infer/presets/google-maps.d.ts.map +1 -0
  106. package/dist/infer/presets/google-maps.js +15 -0
  107. package/dist/infer/presets/ioredis.d.ts +3 -0
  108. package/dist/infer/presets/ioredis.d.ts.map +1 -0
  109. package/dist/infer/presets/ioredis.js +15 -0
  110. package/dist/infer/presets/jsonwebtoken.d.ts +3 -0
  111. package/dist/infer/presets/jsonwebtoken.d.ts.map +1 -0
  112. package/dist/infer/presets/jsonwebtoken.js +40 -0
  113. package/dist/infer/presets/mongoose.d.ts +3 -0
  114. package/dist/infer/presets/mongoose.d.ts.map +1 -0
  115. package/dist/infer/presets/mongoose.js +15 -0
  116. package/dist/infer/presets/multer.d.ts +3 -0
  117. package/dist/infer/presets/multer.d.ts.map +1 -0
  118. package/dist/infer/presets/multer.js +15 -0
  119. package/dist/infer/presets/mysql2.d.ts +3 -0
  120. package/dist/infer/presets/mysql2.d.ts.map +1 -0
  121. package/dist/infer/presets/mysql2.js +15 -0
  122. package/dist/infer/presets/newrelic.d.ts +3 -0
  123. package/dist/infer/presets/newrelic.d.ts.map +1 -0
  124. package/dist/infer/presets/newrelic.js +15 -0
  125. package/dist/infer/presets/node.d.ts +3 -0
  126. package/dist/infer/presets/node.d.ts.map +1 -0
  127. package/dist/infer/presets/node.js +47 -0
  128. package/dist/infer/presets/nodemailer.d.ts +3 -0
  129. package/dist/infer/presets/nodemailer.d.ts.map +1 -0
  130. package/dist/infer/presets/nodemailer.js +50 -0
  131. package/dist/infer/presets/passport-github2.d.ts +3 -0
  132. package/dist/infer/presets/passport-github2.d.ts.map +1 -0
  133. package/dist/infer/presets/passport-github2.js +24 -0
  134. package/dist/infer/presets/passport-google-oauth20.d.ts +3 -0
  135. package/dist/infer/presets/passport-google-oauth20.d.ts.map +1 -0
  136. package/dist/infer/presets/passport-google-oauth20.js +24 -0
  137. package/dist/infer/presets/passport-jwt.d.ts +3 -0
  138. package/dist/infer/presets/passport-jwt.d.ts.map +1 -0
  139. package/dist/infer/presets/passport-jwt.js +15 -0
  140. package/dist/infer/presets/passport.d.ts +3 -0
  141. package/dist/infer/presets/passport.d.ts.map +1 -0
  142. package/dist/infer/presets/passport.js +15 -0
  143. package/dist/infer/presets/pg.d.ts +3 -0
  144. package/dist/infer/presets/pg.d.ts.map +1 -0
  145. package/dist/infer/presets/pg.js +15 -0
  146. package/dist/infer/presets/pino.d.ts +3 -0
  147. package/dist/infer/presets/pino.d.ts.map +1 -0
  148. package/dist/infer/presets/pino.js +15 -0
  149. package/dist/infer/presets/prisma.d.ts +3 -0
  150. package/dist/infer/presets/prisma.d.ts.map +1 -0
  151. package/dist/infer/presets/prisma.js +24 -0
  152. package/dist/infer/presets/sentry.d.ts +3 -0
  153. package/dist/infer/presets/sentry.d.ts.map +1 -0
  154. package/dist/infer/presets/sentry.js +15 -0
  155. package/dist/infer/presets/something.d.ts +4 -0
  156. package/dist/infer/presets/something.d.ts.map +1 -0
  157. package/dist/infer/presets/something.js +29 -0
  158. package/dist/infer/presets/stripe.d.ts +3 -0
  159. package/dist/infer/presets/stripe.d.ts.map +1 -0
  160. package/dist/infer/presets/stripe.js +33 -0
  161. package/dist/infer/presets/typeorm.d.ts +3 -0
  162. package/dist/infer/presets/typeorm.d.ts.map +1 -0
  163. package/dist/infer/presets/typeorm.js +57 -0
  164. package/dist/infer/presets/vitest.d.ts +3 -0
  165. package/dist/infer/presets/vitest.d.ts.map +1 -0
  166. package/dist/infer/presets/vitest.js +13 -0
  167. package/dist/infer/presets/winston.d.ts +3 -0
  168. package/dist/infer/presets/winston.d.ts.map +1 -0
  169. package/dist/infer/presets/winston.js +15 -0
  170. package/dist/infer/presets.d.ts +6 -0
  171. package/dist/infer/presets.d.ts.map +1 -0
  172. package/dist/infer/presets.js +92 -0
  173. package/dist/infer/presets.types.d.ts +10 -0
  174. package/dist/infer/presets.types.d.ts.map +1 -0
  175. package/dist/infer/presets.types.js +1 -0
  176. package/dist/infer/rules/basic.d.ts +2 -2
  177. package/dist/infer/rules/basic.d.ts.map +1 -1
  178. package/dist/infer/rules/basic.js +7 -7
  179. package/dist/infer/rules/boolean.d.ts +2 -2
  180. package/dist/infer/rules/boolean.d.ts.map +1 -1
  181. package/dist/infer/rules/boolean.js +1 -1
  182. package/dist/infer/rules/duration.d.ts +2 -2
  183. package/dist/infer/rules/duration.d.ts.map +1 -1
  184. package/dist/infer/rules/duration.js +1 -1
  185. package/dist/infer/rules/ip.d.ts +2 -2
  186. package/dist/infer/rules/ip.d.ts.map +1 -1
  187. package/dist/infer/rules/ip.js +1 -1
  188. package/dist/infer/rules/json.d.ts +2 -2
  189. package/dist/infer/rules/json.d.ts.map +1 -1
  190. package/dist/infer/rules/json.js +2 -2
  191. package/dist/infer/rules/key-value.d.ts +3 -0
  192. package/dist/infer/rules/key-value.d.ts.map +1 -0
  193. package/dist/infer/rules/key-value.js +54 -0
  194. package/dist/infer/rules/list.d.ts +2 -4
  195. package/dist/infer/rules/list.d.ts.map +1 -1
  196. package/dist/infer/rules/list.js +52 -163
  197. package/dist/infer/rules/port.d.ts +2 -2
  198. package/dist/infer/rules/port.d.ts.map +1 -1
  199. package/dist/infer/rules/port.js +2 -2
  200. package/dist/infer/rules/url.d.ts +2 -2
  201. package/dist/infer/rules/url.d.ts.map +1 -1
  202. package/dist/infer/rules/url.js +15 -56
  203. package/dist/infer/rules/version.d.ts +2 -2
  204. package/dist/infer/rules/version.d.ts.map +1 -1
  205. package/dist/infer/rules/version.js +1 -1
  206. package/dist/infer/rules.d.ts +5 -0
  207. package/dist/infer/rules.d.ts.map +1 -0
  208. package/dist/infer/rules.js +31 -0
  209. package/dist/infer/rules.types.d.ts +144 -0
  210. package/dist/infer/rules.types.d.ts.map +1 -0
  211. package/dist/infer/rules.types.js +17 -0
  212. package/dist/infer/scripts/official-preset-registry.gen.d.ts +13 -0
  213. package/dist/infer/scripts/official-preset-registry.gen.d.ts.map +1 -0
  214. package/dist/infer/scripts/official-preset-registry.gen.js +154 -0
  215. package/dist/infer/types.d.ts +60 -7
  216. package/dist/infer/types.d.ts.map +1 -1
  217. package/dist/infer/types.js +16 -0
  218. package/dist/sample/env.dnl.d.ts +13 -385
  219. package/dist/sample/env.dnl.d.ts.map +1 -1
  220. package/dist/sample/env.dnl.js +15 -392
  221. package/dist/sample/main.d.ts +2 -0
  222. package/dist/sample/main.d.ts.map +1 -0
  223. package/dist/sample/main.js +17 -0
  224. package/dist/schemas/boolean.d.ts +1 -1
  225. package/dist/schemas/boolean.d.ts.map +1 -1
  226. package/dist/schemas/boolean.js +1 -1
  227. package/dist/schemas/common.d.ts +39 -0
  228. package/dist/schemas/common.d.ts.map +1 -0
  229. package/dist/schemas/common.js +1 -0
  230. package/dist/schemas/key-value.d.ts +10 -0
  231. package/dist/schemas/key-value.d.ts.map +1 -0
  232. package/dist/schemas/key-value.js +33 -0
  233. package/dist/schemas/list.d.ts +4 -46
  234. package/dist/schemas/list.d.ts.map +1 -1
  235. package/dist/schemas/list.js +1 -36
  236. package/dist/schemas/path.d.ts +2 -2
  237. package/dist/schemas/path.d.ts.map +1 -1
  238. package/dist/schemas/path.js +2 -2
  239. package/dist/schemas/urls.d.ts.map +1 -1
  240. package/dist/schemas/urls.js +10 -1
  241. package/dist/scripts/script-desc.d.ts +6 -0
  242. package/dist/scripts/script-desc.d.ts.map +1 -0
  243. package/dist/scripts/script-desc.js +26 -0
  244. package/dist/utils/read-user-package-json.d.ts +5 -0
  245. package/dist/utils/read-user-package-json.d.ts.map +1 -0
  246. package/dist/utils/read-user-package-json.js +26 -0
  247. package/package.json +26 -3
package/README.md CHANGED
@@ -10,12 +10,13 @@ It fails fast, loud, and before production.
10
10
 
11
11
  ## Why?
12
12
 
13
- Because all of this happens all the time:
13
+ ecause all of this happens all the time:
14
14
 
15
15
  - ❌ a missing env variable → runtime crash
16
16
  - ❌ a malformed URL → subtle production bug
17
17
  - ❌ CI was not updated after a new variable → confusing red deployment
18
18
  - ❌ an optimistic `process.env.FOO!` → lying to yourself
19
+ - ❌ dotenv silently overrides duplicate keys
19
20
 
20
21
  And because `.env` files are:
21
22
 
@@ -30,12 +31,13 @@ And because `.env` files are:
30
31
 
31
32
  ## What the library does
32
33
 
33
- - ✅ validates environment variables at startup
34
- powered by zod, enables complex transformations (arrays, parsing, coercion…)
35
- - ✅ provides infer .Env to get a first real life dnl schema
34
+ - ✅ validates environment variables at startup
35
+ powered by Zod, enabling complex transformations (arrays, parsing, coercion…)
36
+ - ✅ prevents duplicate environment variables
37
+ - ✅ infers a first real-world DNL schema from an existing `.env`
36
38
  - ✅ provides reliable TypeScript typings
37
39
  - ✅ documents each variable
38
- - ✅ exposes a CLI for CI and humans (dnl export)
40
+ - ✅ exposes a CLI for CI and humans (`dnl export`)
39
41
 
40
42
  ---
41
43
 
@@ -1,6 +1,10 @@
1
1
  import { ProgramCliOptions } from "./program.js";
2
2
  export type AssertCliOptions = ProgramCliOptions & {
3
3
  source?: string;
4
+ warnOnDuplicates?: boolean;
4
5
  };
5
- export declare const assertCommand: (opts?: AssertCliOptions | undefined) => Promise<void>;
6
+ export type AssertResult = {
7
+ warnings: string[];
8
+ };
9
+ export declare const assertCommand: (opts?: AssertCliOptions | undefined) => Promise<AssertResult>;
6
10
  //# sourceMappingURL=assert.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"assert.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/assert.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,GAAG;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,aAAa,GAAU,OAAO,gBAAgB,GAAG,SAAS,KAAG,OAAO,CAAC,IAAI,CAsBrF,CAAC"}
1
+ {"version":3,"file":"assert.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/assert.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,GAAG;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AACF,MAAM,MAAM,YAAY,GAAG;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,aAAa,GAAU,OAAO,gBAAgB,GAAG,SAAS,KAAG,OAAO,CAAC,YAAY,CAyB7F,CAAC"}
@@ -7,11 +7,14 @@ import { ValidationError } from "../../errors.js";
7
7
  export const assertCommand = async (opts) => {
8
8
  const schemaPath = resolveSchemaPath(opts?.schema);
9
9
  const envDef = (await loadDef(schemaPath));
10
+ const warnings = [];
10
11
  try {
11
12
  envDef.assert({
12
- source: opts?.source ? dnl.readEnvFile(path.resolve(process.cwd(), opts.source)) : process.env,
13
+ source: opts?.source
14
+ ? dnl.readEnvFile(path.resolve(process.cwd(), opts.source), { onDuplicate: opts?.warnOnDuplicates ? "warn" : "error" }, warnings)
15
+ : process.env,
13
16
  });
14
- console.log("✅ Environment is valid");
17
+ return { warnings };
15
18
  }
16
19
  catch (error) {
17
20
  if (error instanceof z.ZodError) {
@@ -10,6 +10,7 @@ export type ExportResult = {
10
10
  export type ExportCliOptions = ProgramCliOptions & {
11
11
  format: ExportFormat;
12
12
  source?: string | undefined;
13
+ warnOnDuplicates?: boolean;
13
14
  hideSecret?: boolean;
14
15
  excludeSecret?: boolean;
15
16
  includeComments?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/export.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/export.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ import { resolveSchemaPath } from "../utils/resolve-schema.js";
4
4
  import path from "path";
5
5
  import { UsageError } from "../../errors.js";
6
6
  import { isRequired, isTransform, printZodTypeDts } from "../utils/printer.js";
7
+ // import { JsonObject } from "type-fest";
7
8
  export const exportFormatsNames = [
8
9
  "docker-env",
9
10
  "docker-args",
@@ -87,7 +88,9 @@ const getRawValue = (key, source, envDef, options) => {
87
88
  };
88
89
  // this clones the .env
89
90
  export const exportEnv = (envDef, options, warnings) => {
90
- const source = options?.source ? dnl.readEnvFile(path.resolve(process.cwd(), options.source)) : process.env;
91
+ const source = options?.source
92
+ ? dnl.readEnvFile(path.resolve(process.cwd(), options.source), { onDuplicate: options?.warnOnDuplicates ? "warn" : "error" }, warnings)
93
+ : process.env;
91
94
  const values = envDef.assert({ source });
92
95
  const args = [];
93
96
  for (const key of Object.keys(values)) {
@@ -106,7 +109,9 @@ export const exportDockerArgs = (envDef, options, warnings) => {
106
109
  if (options?.includeComments) {
107
110
  warnings.push("The --include-comments option is invalid with the docker-args format");
108
111
  }
109
- const source = options?.source ? dnl.readEnvFile(path.resolve(process.cwd(), options.source)) : process.env;
112
+ const source = options?.source
113
+ ? dnl.readEnvFile(path.resolve(process.cwd(), options.source), { onDuplicate: options?.warnOnDuplicates ? "warn" : "error" }, warnings)
114
+ : process.env;
110
115
  const values = envDef.assert({ source });
111
116
  const args = [];
112
117
  for (const key of Object.keys(values)) {
@@ -119,7 +124,9 @@ export const exportDockerArgs = (envDef, options, warnings) => {
119
124
  return args.join(" ");
120
125
  };
121
126
  export const exportDockerEnv = (envDef, options, warnings) => {
122
- const source = options?.source ? dnl.readEnvFile(path.resolve(process.cwd(), options.source)) : process.env;
127
+ const source = options?.source
128
+ ? dnl.readEnvFile(path.resolve(process.cwd(), options.source), { onDuplicate: options?.warnOnDuplicates ? "warn" : "error" }, warnings)
129
+ : process.env;
123
130
  const values = envDef.assert({ source });
124
131
  const args = [];
125
132
  for (const key of Object.keys(values)) {
@@ -135,7 +142,9 @@ export const exportDockerEnv = (envDef, options, warnings) => {
135
142
  return args.join("\n");
136
143
  };
137
144
  export const exportK8sConfigmap = (envDef, options, warnings) => {
138
- const source = options?.source ? dnl.readEnvFile(path.resolve(process.cwd(), options.source)) : process.env;
145
+ const source = options?.source
146
+ ? dnl.readEnvFile(path.resolve(process.cwd(), options.source), { onDuplicate: options?.warnOnDuplicates ? "warn" : "error" }, warnings)
147
+ : process.env;
139
148
  const values = envDef.assert({ source });
140
149
  const args = [];
141
150
  args.push(`apiVersion: v1`);
@@ -158,7 +167,9 @@ export const exportK8sConfigmap = (envDef, options, warnings) => {
158
167
  return args.join("\n");
159
168
  };
160
169
  export const exportK8sSecret = (envDef, options, warnings) => {
161
- const source = options?.source ? dnl.readEnvFile(path.resolve(process.cwd(), options.source)) : process.env;
170
+ const source = options?.source
171
+ ? dnl.readEnvFile(path.resolve(process.cwd(), options.source), { onDuplicate: options?.warnOnDuplicates ? "warn" : "error" }, warnings)
172
+ : process.env;
162
173
  const values = envDef.assert({ source });
163
174
  const args = [];
164
175
  args.push(`apiVersion: v1`);
@@ -179,7 +190,9 @@ export const exportK8sSecret = (envDef, options, warnings) => {
179
190
  return args.join("\n");
180
191
  };
181
192
  export const exportGithubEnv = (envDef, options, warnings) => {
182
- const source = options?.source ? dnl.readEnvFile(path.resolve(process.cwd(), options.source)) : process.env;
193
+ const source = options?.source
194
+ ? dnl.readEnvFile(path.resolve(process.cwd(), options.source), { onDuplicate: options?.warnOnDuplicates ? "warn" : "error" }, warnings)
195
+ : process.env;
183
196
  const values = envDef.assert({ source });
184
197
  const args = [];
185
198
  for (const key of Object.keys(values)) {
@@ -197,7 +210,9 @@ export const exportGithubSecret = (envDef, options, warnings) => {
197
210
  if (options?.githubOrg && options.githubOrg.includes(" ")) {
198
211
  warnings.push("github-org contains a space; gh command likely invalid");
199
212
  }
200
- const source = options?.source ? dnl.readEnvFile(path.resolve(process.cwd(), options.source)) : process.env;
213
+ const source = options?.source
214
+ ? dnl.readEnvFile(path.resolve(process.cwd(), options.source), { onDuplicate: options?.warnOnDuplicates ? "warn" : "error" }, warnings)
215
+ : process.env;
201
216
  const values = envDef.assert({ source });
202
217
  const scopeFlag = options?.githubOrg ? `--org ${shellEscape(options.githubOrg)}` : "";
203
218
  const args = [];
@@ -231,8 +246,11 @@ export const exportJson = (envDef, options, warnings) => {
231
246
  if (options?.includeComments) {
232
247
  warnings.push("The --include-comments option is ignored for the json format");
233
248
  }
234
- const source = options?.source ? dnl.readEnvFile(path.resolve(process.cwd(), options.source)) : process.env;
249
+ const source = options?.source
250
+ ? dnl.readEnvFile(path.resolve(process.cwd(), options.source), { onDuplicate: options?.warnOnDuplicates ? "warn" : "error" }, warnings)
251
+ : process.env;
235
252
  const values = envDef.assert({ source });
253
+ //const args: JsonObject = {};
236
254
  const args = {};
237
255
  for (const key of Object.keys(values)) {
238
256
  if (options?.excludeSecret && envDef.def[key].secret) {
@@ -243,7 +261,9 @@ export const exportJson = (envDef, options, warnings) => {
243
261
  return JSON.stringify(args, null, 2);
244
262
  };
245
263
  export const exportTs = (envDef, options, warnings) => {
246
- const source = options?.source ? dnl.readEnvFile(path.resolve(process.cwd(), options.source)) : process.env;
264
+ const source = options?.source
265
+ ? dnl.readEnvFile(path.resolve(process.cwd(), options.source), { onDuplicate: options?.warnOnDuplicates ? "warn" : "error" }, warnings)
266
+ : process.env;
247
267
  const values = envDef.assert({ source });
248
268
  const middle = [];
249
269
  for (const key of Object.keys(values)) {
@@ -258,7 +278,9 @@ export const exportTs = (envDef, options, warnings) => {
258
278
  return `export const env = {\n${middle.join("\n")}\n} as const;`;
259
279
  };
260
280
  export const exportJs = (envDef, options, warnings) => {
261
- const source = options?.source ? dnl.readEnvFile(path.resolve(process.cwd(), options.source)) : process.env;
281
+ const source = options?.source
282
+ ? dnl.readEnvFile(path.resolve(process.cwd(), options.source), { onDuplicate: options?.warnOnDuplicates ? "warn" : "error" }, warnings)
283
+ : process.env;
262
284
  const values = envDef.assert({ source });
263
285
  const middle = [];
264
286
  for (const key of Object.keys(values)) {
@@ -2,8 +2,11 @@ export type InferCliOptions = {
2
2
  source?: string;
3
3
  out?: string;
4
4
  force?: boolean;
5
- dontGuessSecret?: boolean;
5
+ guessSecret?: boolean;
6
6
  verbose?: boolean;
7
+ warnOnDuplicates?: boolean;
8
+ presets?: Array<string>;
9
+ discoverPresets?: boolean;
7
10
  };
8
11
  export type InferResult = {
9
12
  content: string;
@@ -1 +1 @@
1
- {"version":3,"file":"infer.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/infer.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,eAAe,GAAG;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC3B,CAAC;AAEF,eAAO,MAAM,YAAY,GAAU,OAAO,eAAe,GAAG,SAAS,KAAG,OAAO,CAAC,WAAW,CA+D1F,CAAC"}
1
+ {"version":3,"file":"infer.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/infer.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,eAAe,GAAG;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC3B,CAAC;AAEF,eAAO,MAAM,YAAY,GAAU,OAAO,eAAe,GAAG,SAAS,KAAG,OAAO,CAAC,WAAW,CA+G1F,CAAC"}
@@ -1,9 +1,10 @@
1
1
  import path from "node:path";
2
2
  import dnl from "../../index.js";
3
3
  import { guessSecret } from "../../infer/helpers.js";
4
- import { infer } from "../utils/infer-schema.js";
4
+ import { crossInfer, infer } from "../utils/infer-rule-engine.js";
5
5
  import fs from "node:fs";
6
6
  import { ExportError } from "../../errors.js";
7
+ import { discoverPresets, findPresetEntry, getPresetsFromNames } from "../../infer/presets.js";
7
8
  export const inferCommand = async (opts) => {
8
9
  const source = path.resolve(process.cwd(), opts?.source ?? ".env");
9
10
  if (!fs.existsSync(source)) {
@@ -14,41 +15,78 @@ export const inferCommand = async (opts) => {
14
15
  if (fs.existsSync(target) && !opts?.force) {
15
16
  throw new ExportError(`${out} already exists. Use --force to overwrite.`);
16
17
  }
17
- const env = dnl.readEnvFile(source);
18
- const lines = [];
19
18
  const warnings = [];
20
- const importedSchemas = [];
19
+ let presets = [];
20
+ // Preset selection strategy:
21
+ // - `--presets ...` -> use ONLY the provided presets (disables discovery)
22
+ // - `--no-discover-presets` -> use no presets at all
23
+ // - default -> discover presets from package.json
24
+ const presetNames = (opts?.presets ?? []).filter((x) => typeof x === "string" && x.length > 0);
25
+ if (presetNames.length > 0) {
26
+ presets = getPresetsFromNames(presetNames);
27
+ }
28
+ else if (opts?.discoverPresets === true) {
29
+ presets = discoverPresets(warnings);
30
+ }
31
+ else {
32
+ presets = [];
33
+ }
34
+ const env = dnl.readEnvFile(source, { onDuplicate: opts?.warnOnDuplicates ? "warn" : "error" }, warnings);
35
+ const lines = [];
36
+ const importedSchemas = [{ name: "define", from: "@romaintaillandier1978/dotenv-never-lies" }, { name: "z", from: "zod" }];
21
37
  const verbose = [];
22
38
  lines.push(`// ⚠️ This file was generated by dotenv-never-lies`);
23
39
  lines.push(`// Review and adjust schemas, descriptions and secrets before using`);
24
40
  lines.push("");
25
- lines.push(`import { z } from "zod";`);
26
- lines.push(`import { define } from "@romaintaillandier1978/dotenv-never-lies";`);
27
41
  lines.push("");
28
42
  lines.push(`export default define({`);
29
- for (const [key, value] of Object.entries(env)) {
30
- const isValidIdentifier = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key);
31
- const safeKey = isValidIdentifier ? key : JSON.stringify(key);
43
+ for (const [name, rawValue] of Object.entries(env)) {
44
+ const isValidIdentifier = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
45
+ const safeKey = isValidIdentifier ? name : JSON.stringify(name);
32
46
  if (!isValidIdentifier) {
33
- warnings.push(`Key ${key} is not a valid identifier. It has been escaped to ${safeKey}.`);
47
+ warnings.push(`Key ${name} is not a valid identifier. It has been escaped to ${safeKey}.`);
34
48
  }
35
49
  lines.push(` ${safeKey}: {`);
50
+ verbose.push(` Infer ${name} : `);
51
+ const result = findPresetEntry(presets, name, warnings);
52
+ if (result) {
53
+ const [origin, presetEntry] = result;
54
+ if (presetEntry.schema.safeParse(rawValue).success) {
55
+ verbose.push(` -> inferred from preset ${origin}`);
56
+ lines.push(` // from @preset ${origin}`);
57
+ lines.push(` description: "${presetEntry.description}",`);
58
+ lines.push(` schema: ${presetEntry.code},`);
59
+ lines.push(` secret: ${presetEntry.secret ? "true" : "false"},`);
60
+ lines.push(` examples: ${presetEntry.examples ? JSON.stringify(presetEntry.examples) : "[]"},`);
61
+ lines.push(` },`);
62
+ importedSchemas.push(...presetEntry.imports);
63
+ continue;
64
+ }
65
+ warnings.push(`Preset "${origin}" ignored for ${name}: value "${rawValue}" does not match preset schema`);
66
+ }
36
67
  lines.push(` description: "TODO",`);
37
- if (value === undefined) {
68
+ const isSecret = (opts?.guessSecret ?? true) && guessSecret(name);
69
+ // NO : if (rawValue === undefined || rawValue === '""' || rawValue === "''") { // WRONG !
70
+ // Note: dotenv normalizes `FOO=`, `FOO=""` and `FOO=''` into an empty string ("").
71
+ // At this stage, we only treat `undefined` as "variable not set".
72
+ // Empty strings are handled by inference rules when relevant (e.g. number coercion).
73
+ if (rawValue === undefined) {
74
+ // undefined means the variable is not set in the .env file
38
75
  lines.push(` schema: z.string().optional(),`);
39
76
  }
40
77
  else {
41
- const localWarnings = [];
42
- const schema = infer(key, value, importedSchemas, verbose, localWarnings);
43
- for (const warning of localWarnings) {
44
- lines.push(" // " + warning);
78
+ const context = { name, rawValue, imports: importedSchemas, reasons: verbose, codeWarnings: [] };
79
+ const schema = infer(context);
80
+ crossInfer({ inferredSchema: schema, isSecret, ...context });
81
+ for (const w of context.codeWarnings) {
82
+ lines.push(" // " + w);
45
83
  }
46
- lines.push(` schema: ${schema},`);
84
+ lines.push(` schema: ${schema.code},`);
47
85
  }
48
- if (!opts?.dontGuessSecret && guessSecret(key)) {
86
+ if (isSecret) {
49
87
  lines.push(` secret: true, // ⚠️ inferred as secret`);
50
88
  verbose.push(` -> inferred as secret`);
51
- warnings.push(`${key} inferred as secret`);
89
+ warnings.push(`${name} inferred as secret`);
52
90
  }
53
91
  lines.push(` },`);
54
92
  }
@@ -82,5 +120,5 @@ const insertImports = (lines, imports) => {
82
120
  }
83
121
  if (importLines.length === 0)
84
122
  return;
85
- lines.splice(5, 0, ...importLines);
123
+ lines.splice(3, 0, ...importLines);
86
124
  };
@@ -1,3 +1,4 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ import { PackageJson } from "type-fest";
3
+ export declare const dnlPackageJson: PackageJson;
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAYA,OAAO,EAAU,WAAW,EAAE,MAAM,WAAW,CAAC;AAGhD,eAAO,MAAM,cAAc,EAAE,WAAgC,CAAC"}
package/dist/cli/index.js CHANGED
@@ -7,9 +7,8 @@ import { explainCommand, printHuman } from "./commands/explain.js";
7
7
  import { exportCommand } from "./commands/export.js";
8
8
  import { toFile } from "./utils/toFile.js";
9
9
  import { DnlError, ExitCodes, ValidationError } from "../errors.js";
10
- import { createRequire } from "node:module";
11
- const require = createRequire(import.meta.url);
12
- const packageJson = require("../../package.json");
10
+ import pkg from "../../package.json" with { type: "json" };
11
+ export const dnlPackageJson = pkg;
13
12
  const exitCodeHelp = {
14
13
  [ExitCodes.success]: "Success (everything is valid, exit OK)",
15
14
  [ExitCodes.usageError]: "Usage error or internal error",
@@ -21,7 +20,7 @@ const exitCodeHelp = {
21
20
  program
22
21
  .name("dnl")
23
22
  //.version("0.3.0")
24
- .version(packageJson.version)
23
+ .version(dnlPackageJson.version ?? "0.0.0")
25
24
  // allows passing positional arguments, before/after options
26
25
  .enablePositionalOptions()
27
26
  .exitOverride()
@@ -71,9 +70,14 @@ program
71
70
  .command("assert")
72
71
  .description("Verifies the runtime environment and exits the process if the schema is not satisfied.")
73
72
  .option("-s, --source <source>", "Variables source (default: process.env)")
73
+ .option("--warn-on-duplicates", "Warn on duplicate environment variables instead of failing")
74
74
  .action(async (opts) => {
75
75
  const globalOpts = program.opts();
76
- await assertCommand({ ...opts, schema: globalOpts.schema });
76
+ const { warnings } = await assertCommand({ ...opts, schema: globalOpts.schema });
77
+ for (const warning of warnings) {
78
+ console.error(`${warning}`);
79
+ }
80
+ console.log("✅ Environment is valid");
77
81
  })
78
82
  .addHelpText("after", `\nExamples:
79
83
 
@@ -112,6 +116,7 @@ program
112
116
  .description("Exports environment variables to a specified format. Variables are exported after being validated against the schema.")
113
117
  .argument("<format>", "Export format. See list and examples at the end")
114
118
  .option("-s, --source <source>", "Variables source (default: process.env if none provided)")
119
+ .option("--warn-on-duplicates", "Warn on duplicate environment variables instead of failing")
115
120
  .option("--hide-secret", 'Mask sensitive variables (replace with "********")')
116
121
  .option("--exclude-secret", "Exclude sensitive variables (do not show them at all)")
117
122
  .option("--include-comments", "Include comments in the export (does not work with the json format)")
@@ -287,14 +292,19 @@ program
287
292
  "By default, the command will try to guess sensitive variables (e.g. SECRET, KEY, TOKEN, PASSWORD) as secrets.\n" +
288
293
  "This detection is intentionally aggressive and may flag variables that are not secrets.\n" +
289
294
  "This is a deliberate design choice to avoid missing sensitive values.\n" +
290
- "Use the --dont-guess-secret option to disable this behavior.\n" +
295
+ "Use the --no-guess-secret option to disable this behavior.\n" +
296
+ "By default, the command will discover presets in package.json.\n" +
297
+ "Use the --no-discover-presets option to disable this behavior, or --presets option to specify presets to use for inference.\n" +
291
298
  "\n" +
292
299
  "Documentation: https://github.com/rtaillandier/dotenv-never-lies/blob/main/docs/commands/infer.md")
293
300
  .option("-s, --source <source>", "Source .env file", ".env")
294
301
  .option("-o, --out <file>", "Output DNL file", "env.dnl.ts")
295
302
  .option("-f, --force", "Overwrite existing file")
296
303
  .option("--verbose", "Verbose mode")
297
- .option("--dont-guess-secret", "Do not try to guess sensitive variables (heuristic)")
304
+ .option("--no-guess-secret", "Do not try to guess sensitive variables (heuristic)")
305
+ .option("--warn-on-duplicates", "Warn on duplicate environment variables instead of failing")
306
+ .option("--presets <presets...>", "Presets to use for inference (no discovery of presets in package.json)")
307
+ .option("--no-discover-presets", "Do not discover presets in package.json")
298
308
  .action(async (opts) => {
299
309
  const { content, out, warnings, verbose } = await inferCommand(opts);
300
310
  if (opts.verbose) {
@@ -314,17 +324,32 @@ program
314
324
  })
315
325
  .addHelpText("after", `\nExamples:
316
326
 
317
- # Generate an env.dnl.ts schema from a .env file, try to guess sensitive variables
318
- dnl infer --guess-secret
327
+ # Generate an env.dnl.ts schema from a .env file, and will show every inference rules applied, with confidence scoring and reasons
328
+ dnl infer --verbose
329
+
330
+ # Generate an env.dnl.ts schema from a .env file, do not try to guess sensitive variables
331
+ dnl infer --no-guess-secret
319
332
 
320
333
  # Generate an env.dnl.ts schema from a .env.local file
321
334
  dnl infer --source .env.local
322
335
 
323
- # Generate a my-dnl.ts schema from a .env file
336
+ # Generate a my-dnl.ts schema from a .env file,
324
337
  dnl infer --out my-dnl.ts
325
338
 
326
- # Generate an env.dnl.ts schema from a .env file and overwrite the existing file
339
+ # Generate an env.dnl.ts schema from a .env file, and overwrite the existing file
327
340
  dnl infer --force
341
+
342
+ # Generate an env.dnl.ts schema from a .env file, and use only specified presets (no package.json scanning)
343
+ dnl infer --presets prisma node
344
+
345
+ # Generate an env.dnl.ts schema from a .env file, do not automatically discover presets in package.json
346
+ dnl infer --no-discover-presets
347
+
348
+ # Generate an env.dnl.ts schema from a .env file, prevent to fail on duplicate keys, warn instead (dnl will never be silent on duplicate keys)
349
+ dnl infer --warn-on-duplicates
350
+
351
+ # Generate an env.dnl.ts schema from a .env file, Minimal inference (no preset discovery, no secret guessing)
352
+ dnl infer --no-discover-presets --no-guess-secret
328
353
 
329
354
  # Full documentation:
330
355
  # https://github.com/romaintaillandier1978/dotenv-never-lies/blob/main/docs/commands/infer.md
@@ -0,0 +1,4 @@
1
+ import { CrossInferContext, GeneratedSchema, InferContext } from "../../infer/rules.types.js";
2
+ export declare const infer: (context: InferContext) => GeneratedSchema;
3
+ export declare const crossInfer: (context: CrossInferContext) => void;
4
+ //# sourceMappingURL=infer-rule-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infer-rule-engine.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/infer-rule-engine.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE9F,eAAO,MAAM,KAAK,GAAI,SAAS,YAAY,KAAG,eA0B7C,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,SAAS,iBAAiB,KAAG,IAIvD,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { zStringGenSchema } from "../../infer/generated/basic.js";
2
+ import { CROSS_RULES, RULES } from "../../infer/rules.js";
3
+ export const infer = (context) => {
4
+ // for each rules,
5
+ for (const rule of RULES) {
6
+ // try to infer a schema for the current context
7
+ const result = rule.tryInfer({ name: context.name, rawValue: context.rawValue });
8
+ if (!result)
9
+ continue;
10
+ if (result.confidence >= rule.threshold) {
11
+ const importedNames = result.generated.imports.map((entry) => entry.name);
12
+ context.reasons.push(` [${importedNames.join(", ")}] confidence: ${result.confidence} / threshold: ${rule.threshold}`);
13
+ context.imports.push(...result.generated.imports);
14
+ if (result.reasons) {
15
+ context.reasons.push(...result.reasons.map((reason) => ` ${reason}`));
16
+ }
17
+ context.reasons.push(` -> selected schema: ${result.generated.code}`);
18
+ if (result.codeWarnings) {
19
+ context.codeWarnings.push(...result.codeWarnings);
20
+ }
21
+ return result.generated;
22
+ }
23
+ }
24
+ return zStringGenSchema;
25
+ };
26
+ export const crossInfer = (context) => {
27
+ for (const rule of CROSS_RULES) {
28
+ rule(context);
29
+ }
30
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-schema.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/resolve-schema.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,KAAG,MA6BpD,CAAC"}
1
+ {"version":3,"file":"resolve-schema.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/resolve-schema.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,KAAG,MAuBpD,CAAC"}
@@ -1,31 +1,32 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { SchemaNotFoundError } from "../../errors.js";
4
+ import { readDnlConfigInUserPackageJson } from "../../utils/read-user-package-json.js";
4
5
  const CANDIDATES = ["env.dnl.ts", "env.dnl.js", "dnl.config.ts", "dnl.config.js"];
6
+ const resolveIfExists = (relativePath) => {
7
+ const full = path.resolve(process.cwd(), relativePath);
8
+ return fs.existsSync(full) ? full : null;
9
+ };
5
10
  export const resolveSchemaPath = (cliPath) => {
6
11
  // 1. --schema
7
12
  if (cliPath) {
8
- const full = path.resolve(process.cwd(), cliPath);
9
- if (!fs.existsSync(full)) {
10
- throw new SchemaNotFoundError(`Schema file not found: ${cliPath}`);
11
- }
12
- return full;
13
+ const resolved = resolveIfExists(cliPath);
14
+ if (resolved)
15
+ return resolved;
16
+ throw new SchemaNotFoundError(`Schema file not found: ${cliPath}`);
13
17
  }
14
18
  // 2. package.json
15
- const pkgPath = path.resolve(process.cwd(), "package.json");
16
- if (fs.existsSync(pkgPath)) {
17
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
18
- const schema = pkg?.["dotenv-never-lies"]?.schema;
19
- if (schema) {
20
- return path.resolve(process.cwd(), schema);
21
- }
19
+ const dnlConfig = readDnlConfigInUserPackageJson();
20
+ if (dnlConfig !== null) {
21
+ const resolved = resolveIfExists(dnlConfig.schema);
22
+ if (resolved)
23
+ return resolved;
22
24
  }
23
25
  // 3. convention
24
26
  for (const file of CANDIDATES) {
25
- const full = path.resolve(process.cwd(), file);
26
- if (fs.existsSync(full)) {
27
- return full;
28
- }
27
+ const resolved = resolveIfExists(file);
28
+ if (resolved)
29
+ return resolved;
29
30
  }
30
31
  throw new SchemaNotFoundError("No env schema found. Use --schema, define dotenv-never-lies.schema in package.json, or add env.dnl.ts");
31
32
  };
package/dist/core.d.ts CHANGED
@@ -26,8 +26,8 @@ export interface EnvVarDefinition<T extends z.ZodType = z.ZodType> {
26
26
  }
27
27
  /**
28
28
  * An object containing the defined environment variables.
29
- */
30
- export type EnvDefinition = Record<string, EnvVarDefinition<any>>;
29
+ */
30
+ export type EnvDefinition = Record<string, EnvVarDefinition<z.ZodType>>;
31
31
  /**
32
32
  * The Zod shape of the environment schema.
33
33
  */
@@ -91,6 +91,9 @@ export type EnvDefinitionHelper<T extends EnvDefinition> = {
91
91
  * @returns An object exposing functions to check and assert environment variables.
92
92
  */
93
93
  export declare const define: <T extends EnvDefinition>(def: T) => EnvDefinitionHelper<T>;
94
+ export type ReadEnvFileOptions = {
95
+ onDuplicate: "warn" | "error";
96
+ };
94
97
  /**
95
98
  * Reads a .env file and returns environment variables as an object.
96
99
  * Uses dotenv and dotenv-expand.
@@ -99,9 +102,11 @@ export declare const define: <T extends EnvDefinition>(def: T) => EnvDefinitionH
99
102
  * const ENV = envDefinition.load({ source: readEnvFile(".env") });
100
103
  * ```
101
104
  * @param path - The path to the .env file.
105
+ * @param options - The options for reading the .env file.
106
+ * @param warnings - The warnings to add to.
102
107
  * @returns The environment variables as an object.
103
108
  * @throws If the .env file does not exist, or is not valid.
104
109
  */
105
- export declare const readEnvFile: (path: string) => EnvSource;
110
+ export declare const readEnvFile: (path: string, options?: ReadEnvFileOptions, warnings?: string[]) => EnvSource;
106
111
  export {};
107
112
  //# sourceMappingURL=core.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO;IAE7D;;OAEG;IACH,MAAM,EAAE,CAAC,CAAC;IACV;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CAKvB;AAID;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC;AAElE;;GAEG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,aAAa,IAAI;KAClD,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;CAC1C,CAAC;AACF;;GAEG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,aAAa,IAAI;KAC3C,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CACnD,CAAC;AAEF,KAAK,OAAO,GAAG,CAAC,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;CAAE,KAAK,OAAO,CAAC;AACzE,KAAK,QAAQ,CAAC,CAAC,SAAS,aAAa,IAAI,CAAC,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;CAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC;AAEvG;;;GAGG;AACH,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,aAAa,IAAI;IACvD;;OAEG;IACH,GAAG,EAAE,CAAC,CAAC;IACP;;OAEG;IACH,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;IAC7B;;OAEG;IACH,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3C;;;;;;;;OAQG;IACH,KAAK,EAAE,OAAO,CAAC;IACf;;;;;;;;;OASG;IACH,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;CACvB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,MAAM,GAAI,CAAC,SAAS,aAAa,EAAE,KAAK,CAAC,KAAG,mBAAmB,CAAC,CAAC,CAuB7E,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,SAa1C,CAAC"}
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO;IAE7D;;OAEG;IACH,MAAM,EAAE,CAAC,CAAC;IACV;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CAKvB;AAKD;;EAEE;AACF,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;AAExE;;GAEG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,aAAa,IAAI;KAClD,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;CAC1C,CAAC;AACF;;GAEG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,aAAa,IAAI;KAC3C,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CACnD,CAAC;AAEF,KAAK,OAAO,GAAG,CAAC,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;CAAE,KAAK,OAAO,CAAC;AACzE,KAAK,QAAQ,CAAC,CAAC,SAAS,aAAa,IAAI,CAAC,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;CAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC;AAEvG;;;GAGG;AACH,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,aAAa,IAAI;IACvD;;OAEG;IACH,GAAG,EAAE,CAAC,CAAC;IACP;;OAEG;IACH,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;IAC7B;;OAEG;IACH,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3C;;;;;;;;OAQG;IACH,KAAK,EAAE,OAAO,CAAC;IACf;;;;;;;;;OASG;IACH,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;CACvB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,MAAM,GAAI,CAAC,SAAS,aAAa,EAAE,KAAK,CAAC,KAAG,mBAAmB,CAAC,CAAC,CAuB7E,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAEnE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,EAAE,UAAS,kBAA6C,EAAE,WAAW,MAAM,EAAE,KAAG,SAqBvH,CAAC"}