tyfigo 0.2.0-next.a30eb96.0 → 0.2.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 (68) hide show
  1. package/README.md +28 -4
  2. package/dist/bun.d.mts +8 -1
  3. package/dist/bun.mjs +7 -7
  4. package/dist/bun.mjs.map +1 -1
  5. package/dist/effect.mjs +2 -2
  6. package/dist/{env-Bd-1Atoz.mjs → env--pSRlq_k.mjs} +2 -2
  7. package/dist/{env-Bd-1Atoz.mjs.map → env--pSRlq_k.mjs.map} +1 -1
  8. package/dist/env-Dv5OHfEf.mjs +51 -0
  9. package/dist/env-Dv5OHfEf.mjs.map +1 -0
  10. package/dist/{formats-D_dKXoPf.mjs → formats-Bf8-iQw-.mjs} +43 -15
  11. package/dist/{formats-D_dKXoPf.mjs.map → formats-Bf8-iQw-.mjs.map} +1 -1
  12. package/dist/index.mjs +1 -1
  13. package/dist/{load-C-kweoyY.d.mts → load-D1KO5LdK.d.mts} +6 -4
  14. package/dist/{load-DxIjRpX6.mjs → load-yFCdP4to.mjs} +49 -23
  15. package/dist/load-yFCdP4to.mjs.map +1 -0
  16. package/dist/mapper.mjs +28 -11
  17. package/dist/mapper.mjs.map +1 -1
  18. package/dist/plugins/esbuild.d.mts +2 -2
  19. package/dist/plugins/esbuild.mjs +1 -1
  20. package/dist/plugins/jest.d.mts +1 -1
  21. package/dist/plugins/jest.mjs +4 -4
  22. package/dist/plugins/loader.d.mts +1 -1
  23. package/dist/plugins/loader.mjs +3 -2
  24. package/dist/plugins/loader.mjs.map +1 -1
  25. package/dist/plugins/next.d.mts +1 -1
  26. package/dist/plugins/next.mjs +9 -4
  27. package/dist/plugins/next.mjs.map +1 -1
  28. package/dist/plugins/rolldown.d.mts +2 -2
  29. package/dist/plugins/rolldown.mjs +1 -1
  30. package/dist/plugins/rollup.d.mts +2 -2
  31. package/dist/plugins/rollup.mjs +1 -1
  32. package/dist/plugins/rsbuild.d.mts +2 -2
  33. package/dist/plugins/rsbuild.mjs +1 -1
  34. package/dist/plugins/rspack.d.mts +2 -2
  35. package/dist/plugins/rspack.mjs +1 -1
  36. package/dist/plugins/vite.d.mts +2 -2
  37. package/dist/plugins/vite.mjs +1 -1
  38. package/dist/plugins/webpack.d.mts +2 -2
  39. package/dist/plugins/webpack.mjs +1 -1
  40. package/dist/problems-DS_eJB6e.mjs +86 -0
  41. package/dist/problems-DS_eJB6e.mjs.map +1 -0
  42. package/dist/register.mjs +5 -5
  43. package/dist/register.mjs.map +1 -1
  44. package/dist/{resolve-D3geHCGs.mjs → resolve-DQfTdYJN.mjs} +32 -12
  45. package/dist/resolve-DQfTdYJN.mjs.map +1 -0
  46. package/dist/runtime/index.d.mts +13 -4
  47. package/dist/runtime/index.mjs +2 -2
  48. package/dist/{runtime-DeRUtPZ9.mjs → runtime-B3GmIYop.mjs} +13 -9
  49. package/dist/runtime-B3GmIYop.mjs.map +1 -0
  50. package/dist/{shared-CZ44uwQq.d.mts → shared-B6Y3_kdn.d.mts} +6 -5
  51. package/dist/{shared-BnK77Zcp.mjs → shared-DOMPQnVO.mjs} +4 -6
  52. package/dist/shared-DOMPQnVO.mjs.map +1 -0
  53. package/dist/{sync-B-HAK-_E.mjs → sync-mWkXcCOW.mjs} +2 -2
  54. package/dist/{sync-B-HAK-_E.mjs.map → sync-mWkXcCOW.mjs.map} +1 -1
  55. package/dist/unplugin-BYfqHkIE.d.mts +2 -0
  56. package/dist/{unplugin-BQGm_bhA.mjs → unplugin-Bkc569Qw.mjs} +10 -8
  57. package/dist/unplugin-Bkc569Qw.mjs.map +1 -0
  58. package/package.json +1 -1
  59. package/dist/env-R3_XPW_u.mjs +0 -78
  60. package/dist/env-R3_XPW_u.mjs.map +0 -1
  61. package/dist/load-DxIjRpX6.mjs.map +0 -1
  62. package/dist/problems-BNAqmOzL.mjs +0 -23
  63. package/dist/problems-BNAqmOzL.mjs.map +0 -1
  64. package/dist/resolve-D3geHCGs.mjs.map +0 -1
  65. package/dist/runtime-DeRUtPZ9.mjs.map +0 -1
  66. package/dist/shared-BnK77Zcp.mjs.map +0 -1
  67. package/dist/unplugin-AC_HM_T2.d.mts +0 -2
  68. package/dist/unplugin-BQGm_bhA.mjs.map +0 -1
package/README.md CHANGED
@@ -121,6 +121,11 @@ config/app.production.config.yaml(7,20): error TS2322: Type 'number' is not assi
121
121
  src/main.ts(3,7): error TS2322: Type 'number' is not assignable to type 'string'.
122
122
  ```
123
123
 
124
+ Without a schema, the types are strict and widened: an environment file can't add a key (so there are no optional sections),
125
+ `logLevel: info` is `string` rather than `'info' | 'debug'`, a placeholder without a default is `string`, and a `loadConfig`
126
+ mismatch is reported at the call rather than on the YAML line. A schema handles each of these; see
127
+ [What tsc checks without a schema](https://tyfigo.vercel.app/docs/type-checking#what-tsc-checks-without-a-schema).
128
+
124
129
  `database.url` has no default, so set `DATABASE_URL` (in `.env` or the environment) before you run the example. Without it, the import fails
125
130
  with `Environment variable 'DATABASE_URL' is not set (missing-variable)`.
126
131
 
@@ -173,9 +178,12 @@ const env = loadEnv(z.object({ NODE_ENV: z.enum(['development', 'production', 't
173
178
  | Jest | `'tyfigo/jest'` as the `transform` for config files, with their extensions in `moduleFileExtensions` | [jest](https://github.com/imranbarbhuiya/tyfigo/tree/main/examples/jest) |
174
179
 
175
180
  Browser builds get the base and environment files inlined, and a `${VAR}` placeholder or an import of `loadEnv` in a client bundle
176
- fails the build. Server bundles fix the environment when they build: they inline the base, environment and local files, ignore
177
- `NODE_ENV` at run time for picking layers, and read `.env` files and environment variables when they start. See
178
- [Browser builds](https://tyfigo.vercel.app/docs/browser-builds).
181
+ fails the build. Server bundles are built once and deployed anywhere: they inline the base, local and every environment's
182
+ files, pick the layers by `NODE_ENV` when they start (unless the plugin's `environment` option fixes it), and read `.env` files
183
+ and environment variables then. See [Server bundles](https://tyfigo.vercel.app/docs/browser-builds#server-bundles).
184
+
185
+ `next build` can't pass `--runExternalCode` to its type check, so `withTyfigo` turns Next's check off and runs
186
+ `tsc --noEmit --runExternalCode` itself after the compile. See [Next.js](https://tyfigo.vercel.app/docs/bundlers/next).
179
187
 
180
188
  ## Formats
181
189
 
@@ -191,10 +199,26 @@ tyfigo reads seven formats under eight suffixes, since YAML takes both `.config.
191
199
  | `.config.properties` | `.properties` |
192
200
  | `.config.xml` | XML |
193
201
 
202
+ Plain `.json` isn't supported: it's a built-in TypeScript extension, so no content mapper can claim `.json`, `.config.json` or
203
+ `.config/<name>.json` (tsc reports `TS18066`). Rename the file to `.config.jsonc`; every JSON file is valid JSONC.
204
+
205
+ ### A `.config` directory
206
+
207
+ Config files can also live in a `.config` folder with plain extensions: `.config/app.yaml` is a base file and
208
+ `.config/app.production.yaml`, `.config/app.local.yaml` and `.config/app.production.local.yaml` are its layers. Every runtime and
209
+ bundler loads them by path with no setup, and `.yaml` files elsewhere are left alone. `.env` files are read from the folder
210
+ that contains `.config`.
211
+
212
+ Type-checking them is opt-in: add the plain extensions you use to tyfigo's `extensions` (`".yaml"` next to the `.config.*`
213
+ suffixes) and include `".config/*"`. tyfigo then owns that extension, so it can't be combined with another mapper (such as
214
+ Motherload) for it, and every other `.yaml` file is typed as plain data, with no layers or placeholders. See
215
+ [A `.config` directory](https://tyfigo.vercel.app/docs/config-files#a-config-directory) and the
216
+ [config-dir example](https://github.com/imranbarbhuiya/tyfigo/tree/main/examples/config-dir).
217
+
194
218
  ## tyfigo and Motherload
195
219
 
196
220
  [Motherload](https://motherload.dev) also imports data files through TypeScript 7.1 content mappers, but it is a general data
197
- loader: import any `.yaml`, `.toml`, `.csv`, `.md` or other data file and get its contents, typed as `as const` would type them. tyfigo only claims `*.config.*` files and is for the settings your app reads at start-up: widened
221
+ loader: import any `.yaml`, `.toml`, `.csv`, `.md` or other data file and get its contents, typed as `as const` would type them. tyfigo claims `*.config.*` files (and, opt-in, a `.config` folder) and is for the settings your app reads at start-up: widened
198
222
  types, layered environment files checked against the base, placeholders and schema validation. They can share a project,
199
223
  since TypeScript gives each extension one owner and the longest suffix wins, so tyfigo can own `.config.yaml` while
200
224
  Motherload owns `.yaml`. See [tyfigo and Motherload](https://tyfigo.vercel.app/docs#tyfigo-and-motherload).
package/dist/bun.d.mts CHANGED
@@ -1,7 +1,14 @@
1
1
  import { BunPlugin } from "bun";
2
+ export interface TyfigoBunOptions {
3
+ /**
4
+ * The environment name. Defaults to `NODE_ENV`, read when the config loads, or when a server `Bun.build` bundle starts.
5
+ * Setting it fixes the environment of a server bundle.
6
+ */
7
+ readonly environment?: string | undefined;
8
+ }
2
9
  /**
3
10
  * The tyfigo Bun plugin. Importing `tyfigo/bun` (a `bunfig.toml` `preload`) registers it for the runtime and `bun test`; pass
4
11
  * `tyfigo()` in `plugins` to use it with `Bun.build`, where a `browser` target inlines the config and a server target inlines the config files and reads `.env` files when it runs.
5
12
  */
6
- export declare const tyfigo: () => BunPlugin;
13
+ export declare const tyfigo: (options?: TyfigoBunOptions) => BunPlugin;
7
14
  //# sourceMappingURL=bun.d.mts.map
package/dist/bun.mjs CHANGED
@@ -1,26 +1,26 @@
1
- import { n as formatProblems } from "./problems-BNAqmOzL.mjs";
2
- import { t as configSuffixes } from "./formats-D_dKXoPf.mjs";
3
- import { t as load } from "./load-DxIjRpX6.mjs";
1
+ import { n as formatProblems } from "./problems-DS_eJB6e.mjs";
2
+ import { n as configPathPattern } from "./formats-Bf8-iQw-.mjs";
3
+ import { t as load } from "./load-yFCdP4to.mjs";
4
4
  import { extname } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { plugin } from "bun";
7
7
  // src/bun.ts
8
8
  const runtime = fileURLToPath(new URL(`runtime/index${extname(import.meta.url)}`, import.meta.url));
9
- const filter = new RegExp(`[^/\\\\](?:${configSuffixes.map((suffix) => suffix.replaceAll(".", "\\.")).join("|")})$`);
10
9
  /**
11
10
  * The tyfigo Bun plugin. Importing `tyfigo/bun` (a `bunfig.toml` `preload`) registers it for the runtime and `bun test`; pass
12
11
  * `tyfigo()` in `plugins` to use it with `Bun.build`, where a `browser` target inlines the config and a server target inlines the config files and reads `.env` files when it runs.
13
12
  */
14
- const tyfigo = () => ({
13
+ const tyfigo = (options = {}) => ({
15
14
  name: "tyfigo",
16
15
  setup(build) {
17
16
  const config = build.config;
18
17
  const target = config?.target === "browser" ? "browser" : "server";
19
- build.onLoad({ filter }, ({ path }) => {
18
+ build.onLoad({ filter: configPathPattern }, ({ path }) => {
20
19
  const result = load(path, {
21
20
  target,
22
21
  runtime,
23
- bundle: config !== void 0
22
+ bundle: config !== void 0,
23
+ environment: options.environment
24
24
  });
25
25
  if (result.problems.length > 0) throw new Error(`Invalid config ${path}\n${formatProblems(result.problems)}`);
26
26
  return {
package/dist/bun.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bun.mjs","names":[],"sources":["../src/bun.ts"],"sourcesContent":["import { extname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { plugin } from 'bun';\n\nimport { configSuffixes, formatProblems } from './core/index.ts';\nimport { load } from './runtime/index.ts';\n\nimport type { BunPlugin } from 'bun';\n\n// The runtime next to this module, so it resolves from config files outside the dependency tree: `.ts` in this repo, `.mjs` once built.\nconst runtime = fileURLToPath(new URL(`runtime/index${extname(import.meta.url)}`, import.meta.url));\n\nconst filter = new RegExp(`[^/\\\\\\\\](?:${configSuffixes.map((suffix) => suffix.replaceAll('.', '\\\\.')).join('|')})$`);\n\n/**\n * The tyfigo Bun plugin. Importing `tyfigo/bun` (a `bunfig.toml` `preload`) registers it for the runtime and `bun test`; pass\n * `tyfigo()` in `plugins` to use it with `Bun.build`, where a `browser` target inlines the config and a server target inlines the config files and reads `.env` files when it runs.\n */\nexport const tyfigo = (): BunPlugin => ({\n\tname: 'tyfigo',\n\tsetup(build) {\n\t\tconst config = build.config as typeof build.config | undefined;\n\t\tconst target = config?.target === 'browser' ? 'browser' : 'server';\n\t\tbuild.onLoad({ filter }, ({ path }) => {\n\t\t\tconst result = load(path, { target, runtime, bundle: config !== undefined });\n\t\t\tif (result.problems.length > 0) {\n\t\t\t\tthrow new Error(`Invalid config ${path}\\n${formatProblems(result.problems)}`);\n\t\t\t}\n\t\t\treturn { contents: result.code, loader: 'js' };\n\t\t});\n\t},\n});\n\nvoid plugin(tyfigo());\n"],"mappings":";;;;;;;AAWA,MAAM,UAAU,cAAc,IAAI,IAAI,gBAAgB,QAAQ,YAAY,GAAG,KAAK,YAAY,GAAG,CAAC;AAElG,MAAM,SAAS,IAAI,OAAO,cAAc,eAAe,KAAK,WAAW,OAAO,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,GAAG;;;;;AAMnH,MAAa,gBAA2B;CACvC,MAAM;CACN,MAAM,OAAO;EACZ,MAAM,SAAS,MAAM;EACrB,MAAM,SAAS,QAAQ,WAAW,YAAY,YAAY;EAC1D,MAAM,OAAO,EAAE,OAAO,IAAI,EAAE,WAAW;GACtC,MAAM,SAAS,KAAK,MAAM;IAAE;IAAQ;IAAS,QAAQ,WAAW,KAAA;GAAU,CAAC;GAC3E,IAAI,OAAO,SAAS,SAAS,GAC5B,MAAM,IAAI,MAAM,kBAAkB,KAAK,IAAI,eAAe,OAAO,QAAQ,GAAG;GAE7E,OAAO;IAAE,UAAU,OAAO;IAAM,QAAQ;GAAK;EAC9C,CAAC;CACF;AACD;AAEK,OAAO,OAAO,CAAC"}
1
+ {"version":3,"file":"bun.mjs","names":[],"sources":["../src/bun.ts"],"sourcesContent":["import { extname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { plugin } from 'bun';\n\nimport { configPathPattern, formatProblems } from './core/index.ts';\nimport { load } from './runtime/index.ts';\n\nimport type { BunPlugin } from 'bun';\n\n// The runtime next to this module, so it resolves from config files outside the dependency tree: `.ts` in this repo, `.mjs` once built.\nconst runtime = fileURLToPath(new URL(`runtime/index${extname(import.meta.url)}`, import.meta.url));\n\nexport interface TyfigoBunOptions {\n\t/**\n\t * The environment name. Defaults to `NODE_ENV`, read when the config loads, or when a server `Bun.build` bundle starts.\n\t * Setting it fixes the environment of a server bundle.\n\t */\n\treadonly environment?: string | undefined;\n}\n\n/**\n * The tyfigo Bun plugin. Importing `tyfigo/bun` (a `bunfig.toml` `preload`) registers it for the runtime and `bun test`; pass\n * `tyfigo()` in `plugins` to use it with `Bun.build`, where a `browser` target inlines the config and a server target inlines the config files and reads `.env` files when it runs.\n */\nexport const tyfigo = (options: TyfigoBunOptions = {}): BunPlugin => ({\n\tname: 'tyfigo',\n\tsetup(build) {\n\t\tconst config = build.config as typeof build.config | undefined;\n\t\tconst target = config?.target === 'browser' ? 'browser' : 'server';\n\t\tbuild.onLoad({ filter: configPathPattern }, ({ path }) => {\n\t\t\tconst result = load(path, {\n\t\t\t\ttarget,\n\t\t\t\truntime,\n\t\t\t\tbundle: config !== undefined,\n\t\t\t\tenvironment: options.environment,\n\t\t\t});\n\t\t\tif (result.problems.length > 0) {\n\t\t\t\tthrow new Error(`Invalid config ${path}\\n${formatProblems(result.problems)}`);\n\t\t\t}\n\t\t\treturn { contents: result.code, loader: 'js' };\n\t\t});\n\t},\n});\n\nvoid plugin(tyfigo());\n"],"mappings":";;;;;;;AAWA,MAAM,UAAU,cAAc,IAAI,IAAI,gBAAgB,QAAQ,YAAY,GAAG,KAAK,YAAY,GAAG,CAAC;;;;;AAclG,MAAa,UAAU,UAA4B,CAAC,OAAkB;CACrE,MAAM;CACN,MAAM,OAAO;EACZ,MAAM,SAAS,MAAM;EACrB,MAAM,SAAS,QAAQ,WAAW,YAAY,YAAY;EAC1D,MAAM,OAAO,EAAE,QAAQ,kBAAkB,IAAI,EAAE,WAAW;GACzD,MAAM,SAAS,KAAK,MAAM;IACzB;IACA;IACA,QAAQ,WAAW,KAAA;IACnB,aAAa,QAAQ;GACtB,CAAC;GACD,IAAI,OAAO,SAAS,SAAS,GAC5B,MAAM,IAAI,MAAM,kBAAkB,KAAK,IAAI,eAAe,OAAO,QAAQ,GAAG;GAE7E,OAAO;IAAE,UAAU,OAAO;IAAM,QAAQ;GAAK;EAC9C,CAAC;CACF;AACD;AAEK,OAAO,OAAO,CAAC"}
package/dist/effect.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { t as ConfigValidationError } from "./schema-DcD4N9N-.mjs";
2
- import { t as decodeEnv } from "./env-Bd-1Atoz.mjs";
2
+ import { t as decodeEnv } from "./env--pSRlq_k.mjs";
3
3
  import { a as layerConfig, i as ConfigService, n as layerConfigProvider, o as loadConfig, r as ConfigError, t as configProvider } from "./effect.browser-CO87TEE_.mjs";
4
- import { t as resolveConfig } from "./resolve-D3geHCGs.mjs";
4
+ import { t as resolveConfig } from "./resolve-DQfTdYJN.mjs";
5
5
  import { ConfigProvider, Effect, Layer } from "effect";
6
6
  // src/effect/env.ts
7
7
  /**
@@ -1,4 +1,4 @@
1
- import { n as processEnv, r as readEnv } from "./env-R3_XPW_u.mjs";
1
+ import { n as processEnv, r as readEnv } from "./env-Dv5OHfEf.mjs";
2
2
  import { r as validate } from "./schema-DcD4N9N-.mjs";
3
3
  import { Effect } from "effect";
4
4
  import { join } from "node:path";
@@ -24,4 +24,4 @@ const decodeEnv = Effect.fn("decodeEnv")(function* (schema, options) {
24
24
  });
25
25
  export { decodeEnv as t };
26
26
 
27
- //# sourceMappingURL=env-Bd-1Atoz.mjs.map
27
+ //# sourceMappingURL=env--pSRlq_k.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"env-Bd-1Atoz.mjs","names":[],"sources":["../src/env.ts"],"sourcesContent":["import { join } from 'node:path';\nimport process from 'node:process';\n\nimport { Effect } from 'effect';\n\nimport { processEnv, readEnv } from './core/env.ts';\nimport { validate } from './schema.ts';\n\nimport type { ConfigSchema, LoadEnvOptions } from './schema.ts';\n\nexport const decodeEnv = Effect.fn('decodeEnv')(function* <S extends ConfigSchema>(\n\tschema: S,\n\toptions: LoadEnvOptions & { readonly async: boolean; readonly native?: boolean | undefined },\n) {\n\tconst env = options.env ?? processEnv();\n\tconst environment = options.environment ?? (env.NODE_ENV || undefined);\n\tconst result = yield* readEnv(join(options.cwd ?? process.cwd(), '.env'), { environment, env });\n\treturn yield* validate(schema, result.variables, {\n\t\tsubject: 'environment variables',\n\t\tasync: options.async,\n\t\tnative: options.native,\n\t\tissues: result.problems.map((problem) => ({\n\t\t\tpath: [],\n\t\t\tmessage: problem.message,\n\t\t\tlocation: `${problem.file}:${problem.line}:${problem.column}`,\n\t\t})),\n\t});\n});\n"],"mappings":";;;;;;AAUA,MAAa,YAAY,OAAO,GAAG,WAAW,CAAC,CAAC,WAC/C,QACA,SACC;CACD,MAAM,MAAM,QAAQ,OAAO,WAAW;CACtC,MAAM,cAAc,QAAQ,gBAAgB,IAAI,YAAY,KAAA;CAC5D,MAAM,SAAS,OAAO,QAAQ,KAAK,QAAQ,OAAO,QAAQ,IAAI,GAAG,MAAM,GAAG;EAAE;EAAa;CAAI,CAAC;CAC9F,OAAO,OAAO,SAAS,QAAQ,OAAO,WAAW;EAChD,SAAS;EACT,OAAO,QAAQ;EACf,QAAQ,QAAQ;EAChB,QAAQ,OAAO,SAAS,KAAK,aAAa;GACzC,MAAM,CAAC;GACP,SAAS,QAAQ;GACjB,UAAU,GAAG,QAAQ,KAAK,GAAG,QAAQ,KAAK,GAAG,QAAQ;EACtD,EAAE;CACH,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"env--pSRlq_k.mjs","names":[],"sources":["../src/env.ts"],"sourcesContent":["import { join } from 'node:path';\nimport process from 'node:process';\n\nimport { Effect } from 'effect';\n\nimport { processEnv, readEnv } from './core/env.ts';\nimport { validate } from './schema.ts';\n\nimport type { ConfigSchema, LoadEnvOptions } from './schema.ts';\n\nexport const decodeEnv = Effect.fn('decodeEnv')(function* <S extends ConfigSchema>(\n\tschema: S,\n\toptions: LoadEnvOptions & { readonly async: boolean; readonly native?: boolean | undefined },\n) {\n\tconst env = options.env ?? processEnv();\n\tconst environment = options.environment ?? (env.NODE_ENV || undefined);\n\tconst result = yield* readEnv(join(options.cwd ?? process.cwd(), '.env'), { environment, env });\n\treturn yield* validate(schema, result.variables, {\n\t\tsubject: 'environment variables',\n\t\tasync: options.async,\n\t\tnative: options.native,\n\t\tissues: result.problems.map((problem) => ({\n\t\t\tpath: [],\n\t\t\tmessage: problem.message,\n\t\t\tlocation: `${problem.file}:${problem.line}:${problem.column}`,\n\t\t})),\n\t});\n});\n"],"mappings":";;;;;;AAUA,MAAa,YAAY,OAAO,GAAG,WAAW,CAAC,CAAC,WAC/C,QACA,SACC;CACD,MAAM,MAAM,QAAQ,OAAO,WAAW;CACtC,MAAM,cAAc,QAAQ,gBAAgB,IAAI,YAAY,KAAA;CAC5D,MAAM,SAAS,OAAO,QAAQ,KAAK,QAAQ,OAAO,QAAQ,IAAI,GAAG,MAAM,GAAG;EAAE;EAAa;CAAI,CAAC;CAC9F,OAAO,OAAO,SAAS,QAAQ,OAAO,WAAW;EAChD,SAAS;EACT,OAAO,QAAQ;EACf,QAAQ,QAAQ;EAChB,QAAQ,OAAO,SAAS,KAAK,aAAa;GACzC,MAAM,CAAC;GACP,SAAS,QAAQ;GACjB,UAAU,GAAG,QAAQ,KAAK,GAAG,QAAQ,KAAK,GAAG,QAAQ;EACtD,EAAE;CACH,CAAC;AACF,CAAC"}
@@ -0,0 +1,51 @@
1
+ import { a as envDirectories, o as findFile, t as fileProblem } from "./problems-DS_eJB6e.mjs";
2
+ import { Effect, Result } from "effect";
3
+ import { resolve } from "node:path";
4
+ import process from "node:process";
5
+ import { readFileSync } from "node:fs";
6
+ import { parseEnv } from "node:util";
7
+ // src/core/env.ts
8
+ /**
9
+ * A copy of the real environment. This is the only place tyfigo reads `process.env`.
10
+ */
11
+ const processEnv = () => ({ ...process.env });
12
+ const envFileNames = (environment) => environment ? [
13
+ ".env",
14
+ ".env.local",
15
+ `.env.${environment}`,
16
+ `.env.${environment}.local`
17
+ ] : [".env", ".env.local"];
18
+ /**
19
+ * Reads `.env`, `.env.local`, `.env.<environment>` and `.env.<environment>.local` (each later one wins) and puts `env` on top.
20
+ * Each file is searched next to `baseFile` first (above its `.config` directory when it is in one), then in the nearest directory
21
+ * with a `package.json`. Never writes `process.env`.
22
+ */
23
+ const readEnv = Effect.fn("readEnv")(function* (baseFile, options = {}) {
24
+ const directories = yield* envDirectories(resolve(baseFile));
25
+ const variables = {};
26
+ const files = [];
27
+ const problems = [];
28
+ for (const name of envFileNames(options.environment)) {
29
+ const file = yield* findFile(directories, name);
30
+ if (!file) continue;
31
+ const parsed = yield* Effect.result(Effect.try({
32
+ try: () => parseEnv(readFileSync(file, "utf8")),
33
+ catch: (error) => fileProblem(file, "read-error", `Cannot read ${file}: ${error.message}`)
34
+ }));
35
+ if (Result.isFailure(parsed)) {
36
+ problems.push(parsed.failure);
37
+ continue;
38
+ }
39
+ files.push(file);
40
+ Object.assign(variables, parsed.success);
41
+ }
42
+ for (const [key, value] of Object.entries(options.env ?? processEnv())) if (value !== void 0) variables[key] = value;
43
+ return {
44
+ variables,
45
+ files,
46
+ problems
47
+ };
48
+ });
49
+ export { processEnv as n, readEnv as r, envFileNames as t };
50
+
51
+ //# sourceMappingURL=env-Dv5OHfEf.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-Dv5OHfEf.mjs","names":[],"sources":["../src/core/env.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport process from 'node:process';\nimport { parseEnv } from 'node:util';\n\nimport { Effect, Result } from 'effect';\n\nimport { envDirectories, findFile } from './files.ts';\nimport { fileProblem } from './problems.ts';\n\nimport type { Problem } from './problems.ts';\n\nexport type EnvVariables = Readonly<Record<string, string | undefined>>;\n\nexport interface EnvResult {\n\t/**\n\t * The `.env` values with the real environment on top.\n\t */\n\treadonly variables: EnvVariables;\n\t/**\n\t * The `.env` files that were read, in the order they apply.\n\t */\n\treadonly files: readonly string[];\n\treadonly problems: readonly Problem[];\n}\n\n/**\n * A copy of the real environment. This is the only place tyfigo reads `process.env`.\n */\nexport const processEnv = (): EnvVariables => ({ ...process.env });\n\nexport const envFileNames = (environment?: string): readonly string[] =>\n\tenvironment ? ['.env', '.env.local', `.env.${environment}`, `.env.${environment}.local`] : ['.env', '.env.local'];\n\n/**\n * Reads `.env`, `.env.local`, `.env.<environment>` and `.env.<environment>.local` (each later one wins) and puts `env` on top.\n * Each file is searched next to `baseFile` first (above its `.config` directory when it is in one), then in the nearest directory\n * with a `package.json`. Never writes `process.env`.\n */\nexport const readEnv = Effect.fn('readEnv')(function* (\n\tbaseFile: string,\n\toptions: { readonly environment?: string | undefined; readonly env?: EnvVariables | undefined } = {},\n) {\n\tconst directories = yield* envDirectories(resolve(baseFile));\n\tconst variables: Record<string, string | undefined> = {};\n\tconst files: string[] = [];\n\tconst problems: Problem[] = [];\n\tfor (const name of envFileNames(options.environment)) {\n\t\tconst file = yield* findFile(directories, name);\n\t\tif (!file) continue;\n\t\tconst parsed = yield* Effect.result(\n\t\t\tEffect.try({\n\t\t\t\ttry: () => parseEnv(readFileSync(file, 'utf8')),\n\t\t\t\tcatch: (error) => fileProblem(file, 'read-error', `Cannot read ${file}: ${(error as Error).message}`),\n\t\t\t}),\n\t\t);\n\t\tif (Result.isFailure(parsed)) {\n\t\t\tproblems.push(parsed.failure);\n\t\t\tcontinue;\n\t\t}\n\n\t\tfiles.push(file);\n\t\tObject.assign(variables, parsed.success);\n\t}\n\n\tfor (const [key, value] of Object.entries(options.env ?? processEnv())) {\n\t\tif (value !== undefined) variables[key] = value;\n\t}\n\treturn { variables, files, problems } satisfies EnvResult;\n});\n"],"mappings":";;;;;;;;;;AA6BA,MAAa,oBAAkC,EAAE,GAAG,QAAQ,IAAI;AAEhE,MAAa,gBAAgB,gBAC5B,cAAc;CAAC;CAAQ;CAAc,QAAQ;CAAe,QAAQ,YAAY;AAAO,IAAI,CAAC,QAAQ,YAAY;;;;;;AAOjH,MAAa,UAAU,OAAO,GAAG,SAAS,CAAC,CAAC,WAC3C,UACA,UAAkG,CAAC,GAClG;CACD,MAAM,cAAc,OAAO,eAAe,QAAQ,QAAQ,CAAC;CAC3D,MAAM,YAAgD,CAAC;CACvD,MAAM,QAAkB,CAAC;CACzB,MAAM,WAAsB,CAAC;CAC7B,KAAK,MAAM,QAAQ,aAAa,QAAQ,WAAW,GAAG;EACrD,MAAM,OAAO,OAAO,SAAS,aAAa,IAAI;EAC9C,IAAI,CAAC,MAAM;EACX,MAAM,SAAS,OAAO,OAAO,OAC5B,OAAO,IAAI;GACV,WAAW,SAAS,aAAa,MAAM,MAAM,CAAC;GAC9C,QAAQ,UAAU,YAAY,MAAM,cAAc,eAAe,KAAK,IAAK,MAAgB,SAAS;EACrG,CAAC,CACF;EACA,IAAI,OAAO,UAAU,MAAM,GAAG;GAC7B,SAAS,KAAK,OAAO,OAAO;GAC5B;EACD;EAEA,MAAM,KAAK,IAAI;EACf,OAAO,OAAO,WAAW,OAAO,OAAO;CACxC;CAEA,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,OAAO,WAAW,CAAC,GACpE,IAAI,UAAU,KAAA,GAAW,UAAU,OAAO;CAE3C,OAAO;EAAE;EAAW;EAAO;CAAS;AACrC,CAAC"}
@@ -1,5 +1,6 @@
1
- import { r as problemAt } from "./problems-BNAqmOzL.mjs";
1
+ import { i as configDirectory, l as isInConfigDirectory, r as problemAt } from "./problems-DS_eJB6e.mjs";
2
2
  import { n as formatPath, r as pathKey, t as SourceIndex } from "./positions-BCTEFZTX.mjs";
3
+ import { basename, extname } from "node:path";
3
4
  import { getStaticJSONValue, parseJSON, tokenize } from "jsonc-eslint-parser";
4
5
  import { getNodeValue, parseTree, printParseErrorCode } from "jsonc-parser";
5
6
  import { getStaticTOMLValue, parseTOML } from "toml-eslint-parser";
@@ -874,27 +875,54 @@ const yaml = {
874
875
  parseScalar
875
876
  };
876
877
  // src/core/formats/index.ts
877
- const formats = {
878
- ".config.yaml": yaml,
879
- ".config.yml": yaml,
880
- ".config.toml": toml,
881
- ".config.jsonc": jsonc,
882
- ".config.json5": json5,
883
- ".config.ini": ini,
884
- ".config.properties": properties,
885
- ".config.xml": xml
886
- };
878
+ /**
879
+ * The formats by file extension.
880
+ */
881
+ const formatExtensions = {
882
+ ".yaml": yaml,
883
+ ".yml": yaml,
884
+ ".toml": toml,
885
+ ".jsonc": jsonc,
886
+ ".json5": json5,
887
+ ".ini": ini,
888
+ ".properties": properties,
889
+ ".xml": xml
890
+ };
891
+ const formats = Object.fromEntries(Object.entries(formatExtensions).map(([extension, format]) => [`.config${extension}`, format]));
887
892
  const configSuffixes = Object.keys(formats);
888
893
  /**
889
- * Splits a config file name (not a path) into its stem and suffix, or returns `undefined` when tyfigo does not claim it.
894
+ * The format of a file extension such as `.yaml`, or `undefined` when tyfigo does not read it.
890
895
  */
891
- const parseConfigFileName = (fileName) => {
896
+ const formatOfExtension = (extension) => Object.hasOwn(formatExtensions, extension) ? formatExtensions[extension] : void 0;
897
+ /**
898
+ * Splits a config file path into its stem and suffix, or returns `undefined` when tyfigo does not claim it. A config file's name
899
+ * ends in a suffix such as `.config.yaml`, or it has a format extension such as `.yaml` and sits in a `.config` directory.
900
+ */
901
+ const parseConfigPath = (path) => {
902
+ const fileName = basename(path);
892
903
  for (const [suffix, format] of Object.entries(formats)) if (fileName.endsWith(suffix) && fileName.length > suffix.length) return {
893
904
  stem: fileName.slice(0, -suffix.length),
894
905
  suffix,
895
906
  format
896
907
  };
908
+ const suffix = extname(fileName);
909
+ const format = formatOfExtension(suffix);
910
+ if (!format || !isInConfigDirectory(path) || fileName.startsWith(".")) return void 0;
911
+ return {
912
+ stem: fileName.slice(0, -suffix.length),
913
+ suffix,
914
+ format
915
+ };
897
916
  };
898
- export { kindOf as a, isPlainObject as i, parseConfigFileName as n, resolvePlaceholders as o, hasPlaceholder as r, configSuffixes as t };
917
+ const escape = (text) => text.replaceAll(".", String.raw`\.`);
918
+ /**
919
+ * Matches the paths {@link parseConfigPath} claims, with either path separator, for loader and bundler filters.
920
+ */
921
+ const configPathPattern = new RegExp(String.raw`(?:[^/\\](?:${configSuffixes.map(escape).join("|")})|(?:^|[/\\])${escape(configDirectory)}[/\\][^/\\.][^/\\]*(?:${Object.keys(formatExtensions).map(escape).join("|")}))$`, "u");
922
+ /**
923
+ * A short description of the files tyfigo claims, for error messages.
924
+ */
925
+ const configFileDescription = `a name ending in ${configSuffixes.join(", ")}, or a ${Object.keys(formatExtensions).join(", ")} file in a ${configDirectory} directory`;
926
+ export { formatOfExtension as a, isPlainObject as c, formatExtensions as i, kindOf as l, configPathPattern as n, parseConfigPath as o, configSuffixes as r, hasPlaceholder as s, configFileDescription as t, resolvePlaceholders as u };
899
927
 
900
- //# sourceMappingURL=formats-D_dKXoPf.mjs.map
928
+ //# sourceMappingURL=formats-Bf8-iQw-.mjs.map