@adamhl8/configs 2.1.0 → 2.2.1

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/README.md CHANGED
@@ -8,18 +8,24 @@ bun add -D @adamhl8/configs
8
8
 
9
9
  ### Config merging
10
10
 
11
- Each `fooConfig({ ... })` function deep merges your config into the base config. Arrays are appended to the base array by default. To control this per key, any array-typed key can instead take `{ value: [...], mode: "merge" | "replace" }`:
11
+ Each `fooConfig({ ... })` function deep merges your config into the base config. Arrays are appended to the base array by default. To use arrays as-is instead, put them in a second config object, which is merged the same way except its arrays replace the base array:
12
12
 
13
13
  ```ts
14
- const config = oxlintConfig({
15
- // appended to the base plugins (same as passing a plain array)
16
- plugins: { value: ["my-plugin"], mode: "merge" },
17
- // used as-is, the base overrides are dropped
18
- overrides: { value: [], mode: "replace" },
19
- })
14
+ import { oxlintConfig } from "@adamhl8/configs"
15
+
16
+ const config = oxlintConfig(
17
+ {
18
+ // appended to the base plugins
19
+ plugins: ["my-plugin"],
20
+ },
21
+ {
22
+ // used as-is, the base overrides are dropped
23
+ overrides: [],
24
+ },
25
+ )
20
26
  ```
21
27
 
22
- The wrapper works at any depth and never appears in the merged result, in the types or at runtime.
28
+ The second config works at any depth: only the arrays you provide are replaced, sibling keys still merge normally, and a key in both objects gets the second object's value. Required keys (e.g. `platform` for `tsdownConfig`) must be provided in the first object.
23
29
 
24
30
  ### just
25
31
 
@@ -29,7 +35,7 @@ Scripts live in a shared [`just`](https://github.com/casey/just) base justfile i
29
35
  import "node_modules/@adamhl8/configs/dist/configs/justfile.base.just"
30
36
  ```
31
37
 
32
- It provides `lint`, `build`, `test`, `bump-deps`, `release`, `release-run`, and `prepare`. Run `just` to list them. `build` only runs `tsdown` if the project has a `tsdown.config.ts`, and `test` (`bun test`) passes when there are no tests, so projects without a build step or tests can use them as-is.
38
+ It provides `lint`, `build`, `test`, `bump-deps`, `release`, `release-run`, `tofu-check`, and `prepare`. Run `just` to list them. The recipes degrade gracefully so projects can use them as-is: `build` only runs `tsdown` if the project has a `tsdown.config.ts`, `test` (`bun test`) passes when there are no tests, and `tofu-check` (a `tofu plan` drift check, which `build` also runs) skips itself unless run locally in a project with `.tofu` files.
33
39
 
34
40
  To customize a recipe, redefine it in your `justfile`. Each public recipe is a thin wrapper over a private `_recipe` that holds the actual body, so an override can still run the original instead of copying it:
35
41
 
@@ -103,7 +109,7 @@ const config = tsdownConfig({ ... })
103
109
  export default defineConfig(config)
104
110
  ```
105
111
 
106
- `tsdownBinConfig` is also exported for building `package.json` bin executables: a bundled, single-file, extensionless build with no type declarations.
112
+ `tsdownBundleConfig` is also exported for single-file bundles that aren't published libraries: everything is bundled, with no type declarations or publish checks. `tsdownBinConfig` builds `package.json` bin executables: the same bundle for node, with an extensionless output file.
107
113
 
108
114
  ### commitlint
109
115
 
@@ -168,6 +174,24 @@ dist/
168
174
 
169
175
  Everything below the marker is left untouched. The sync can also be run manually by running `adamhl8-gitignore`.
170
176
 
177
+ ### env
178
+
179
+ Utilities for validated environment variables, exported from the `/env` subpath:
180
+
181
+ ```ts
182
+ import { parseEnv, requireWhen } from "@adamhl8/configs/env"
183
+
184
+ const isProd = process.env["NODE_ENV"] === "production"
185
+
186
+ export const env = parseEnv({
187
+ API_URL: "string.url",
188
+ // required in production, optional otherwise
189
+ SENTRY_DSN: requireWhen(isProd, "string.url"),
190
+ })
191
+ ```
192
+
193
+ `parseEnv` is `arkenv` with friendlier failures: invalid env prints the validation message and exits instead of throwing. `requireWhen` makes a var required while its condition is true and optional otherwise. An optional third argument supplies a fallback for when the var is unset (the fallback is exempt from validation, a set value never is).
194
+
171
195
  ## GitHub Actions
172
196
 
173
197
  This repo hosts three reusable workflows you can call from other projects instead of copy/pasting them. The calling project must use `bun`, have a `justfile` that imports the base justfile (see [just](#just)), and have a commitlint config (e.g. via these configs). The workflows set up bun and install just and lefthook themselves.
@@ -240,16 +264,38 @@ jobs:
240
264
  secrets: inherit
241
265
  ```
242
266
 
267
+ #### Docker
268
+
269
+ Pass `docker-image` to build the repo's `Dockerfile` and push it to `ghcr.io/<owner>/<docker-image>:latest` once the release lands. The image name is separate from the repo name, since they often differ. Omit the input and no image is built.
270
+
271
+ The build checks out the tag the release just created, not `main`, so the image matches the released commit. Callers that use it must also grant `packages: write`, because a called workflow's permissions are capped by what the calling job grants.
272
+
273
+ The optional `GH_TOKEN` secret is passed to the build as a BuildKit secret named `GH_TOKEN`, for images whose build calls the GitHub API. Mount it in the Dockerfile with `RUN --mount=type=secret,id=GH_TOKEN,env=GITHUB_TOKEN`. A Dockerfile that doesn't mount it is unaffected.
274
+
275
+ To deploy after the push, add a job that needs the release. A job waiting on a called workflow waits for all of its jobs, so it runs after the image is pushed.
276
+
277
+ ```yaml
278
+ jobs:
279
+ release:
280
+ permissions:
281
+ contents: write
282
+ packages: write
283
+ uses: adamhl8/configs/.github/workflows/release.yml@main
284
+ with:
285
+ docker-image: actual-budget-venmo-importer
286
+ secrets: inherit
287
+ ```
288
+
243
289
  ### Secrets
244
290
 
245
291
  Configure these on each repo that uses the workflows (Settings -> Secrets and variables -> Actions):
246
292
 
247
- | Secret | Used by | What it is |
248
- | ---------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
249
- | `CI_TOKEN` | `update-deps.yml` | PAT with `contents` and `pull-requests` write, so the opened PR triggers CI |
250
- | `GH_TOKEN` | `ci.yml` | Optional. PAT for a build that calls the GitHub API. Reaches the build as `GITHUB_TOKEN`. Unset, the build falls back to the automatic token. |
251
- | `NPM_CI_TOKEN` | `release.yml` | npm automation token with publish access |
252
- | `CI_SIGNING_KEY` | `release.yml` | Private SSH key that signs the release commit and tag. Add the matching public key to your GitHub account as a Signing Key so the signature verifies as you. Use a dedicated, passphrase-less key, not your personal auth key. |
293
+ | Secret | Used by | What it is |
294
+ | ---------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
295
+ | `CI_TOKEN` | `update-deps.yml` | PAT with `contents` and `pull-requests` write, so the opened PR triggers CI |
296
+ | `GH_TOKEN` | `ci.yml`, `release.yml` | Optional. PAT for a build that calls the GitHub API. Reaches a `ci.yml` build as `GITHUB_TOKEN`, and a `release.yml` docker build as the `GH_TOKEN` BuildKit secret. Unset, the `ci.yml` build falls back to the automatic token. |
297
+ | `NPM_CI_TOKEN` | `release.yml` | npm automation token with publish access |
298
+ | `CI_SIGNING_KEY` | `release.yml` | Private SSH key that signs the release commit and tag. Add the matching public key to your GitHub account as a Signing Key so the signature verifies as you. Use a dedicated, passphrase-less key, not your personal auth key. |
253
299
 
254
300
  `GITHUB_TOKEN` is provided automatically. The release wrapper grants it `contents: write` to push the release commit and tag and to create the GitHub release.
255
301
 
@@ -1,4 +1,4 @@
1
- import { OptionalMergeConfigFn } from "../utils.js";
1
+ import { OptionalMergeConfigFn } from "../merge-config/merge-config.js";
2
2
  import { UserConfig } from "@commitlint/types";
3
3
  //#region src/configs/commitlint.base.d.ts
4
4
  declare const commitlintConfig: OptionalMergeConfigFn<UserConfig, {
@@ -1,4 +1,4 @@
1
- import { createMergeConfigFn } from "../utils.js";
1
+ import { createMergeConfigFn } from "../merge-config/merge-config.js";
2
2
  import conventional from "@commitlint/config-conventional";
3
3
  //#region src/configs/commitlint.base.ts
4
4
  const [_severity, _condition, conventionalTypes] = conventional.rules["type-enum"];
@@ -1 +1 @@
1
- {"version":3,"file":"commitlint.base.js","names":[],"sources":["../../src/configs/commitlint.base.ts"],"sourcesContent":["import conventional from \"@commitlint/config-conventional\"\nimport type { UserConfig } from \"@commitlint/types\"\n\nimport { createMergeConfigFn } from \"#utils.ts\"\n\nconst [_severity, _condition, conventionalTypes] = conventional.rules[\"type-enum\"]\n\nconst EXCLUDED_TYPES = new Set([\"test\", \"style\", \"build\"])\nconst filteredTypes = conventionalTypes.filter((type) => !EXCLUDED_TYPES.has(type))\nconst commitTypes = [...filteredTypes, \"improve\", \"release\"]\n\nconst baseConfig = {\n extends: [\"@commitlint/config-conventional\"],\n rules: {\n \"type-enum\": [2, \"always\", commitTypes],\n },\n} as const satisfies UserConfig\n\nexport const commitlintConfig = createMergeConfigFn<UserConfig, typeof baseConfig>(baseConfig)\n"],"mappings":";;;AAKA,MAAM,CAAC,WAAW,YAAY,qBAAqB,aAAa,MAAM;AAEtE,MAAM,iCAAiB,IAAI,IAAI;CAAC;CAAQ;CAAS;AAAO,CAAC;AAWzD,MAAa,mBAAmB,oBAAmD;CANjF,SAAS,CAAC,iCAAiC;CAC3C,OAAO,EACL,aAAa;EAAC;EAAG;EAAU;GALV,GADC,kBAAkB,QAAQ,SAAS,CAAC,eAAe,IAAI,IAAI,CAC7C;GAAG;GAAW;EAKT;CAAC,EACxC;AAGiF,CAAU"}
1
+ {"version":3,"file":"commitlint.base.js","names":[],"sources":["../../src/configs/commitlint.base.ts"],"sourcesContent":["import conventional from \"@commitlint/config-conventional\"\nimport type { UserConfig } from \"@commitlint/types\"\n\nimport { createMergeConfigFn } from \"#merge-config/merge-config.ts\"\n\nconst [_severity, _condition, conventionalTypes] = conventional.rules[\"type-enum\"]\n\nconst EXCLUDED_TYPES = new Set([\"test\", \"style\", \"build\"])\nconst filteredTypes = conventionalTypes.filter((type) => !EXCLUDED_TYPES.has(type))\nconst commitTypes = [...filteredTypes, \"improve\", \"release\"]\n\nconst baseConfig = {\n extends: [\"@commitlint/config-conventional\"],\n rules: {\n \"type-enum\": [2, \"always\", commitTypes],\n },\n} as const satisfies UserConfig\n\nexport const commitlintConfig = createMergeConfigFn<UserConfig, typeof baseConfig>(baseConfig)\n"],"mappings":";;;AAKA,MAAM,CAAC,WAAW,YAAY,qBAAqB,aAAa,MAAM;AAEtE,MAAM,iCAAiB,IAAI,IAAI;CAAC;CAAQ;CAAS;AAAO,CAAC;AAWzD,MAAa,mBAAmB,oBAAmD;CANjF,SAAS,CAAC,iCAAiC;CAC3C,OAAO,EACL,aAAa;EAAC;EAAG;EAAU;GALV,GADC,kBAAkB,QAAQ,SAAS,CAAC,eAAe,IAAI,IAAI,CAC7C;GAAG;GAAW;EAKT;CAAC,EACxC;AAGiF,CAAU"}
@@ -1,4 +1,4 @@
1
- import { OptionalMergeConfigFn } from "../utils.js";
1
+ import { OptionalMergeConfigFn } from "../merge-config/merge-config.js";
2
2
  import { KnipConfig } from "knip";
3
3
  //#region src/configs/knip.base.d.ts
4
4
  declare const knipConfig: OptionalMergeConfigFn<KnipConfig, {
@@ -1,4 +1,4 @@
1
- import { createMergeConfigFn } from "../utils.js";
1
+ import { createMergeConfigFn } from "../merge-config/merge-config.js";
2
2
  const knipConfig = createMergeConfigFn({
3
3
  project: ["**/*"],
4
4
  ignoreBinaries: ["just"],
@@ -1 +1 @@
1
- {"version":3,"file":"knip.base.js","names":[],"sources":["../../src/configs/knip.base.ts"],"sourcesContent":["import type { KnipConfig } from \"knip\"\n\nimport { createMergeConfigFn } from \"#utils.ts\"\n\n/** Knip sees `just` in the `prepare` script and the release-it hooks, but it isn't a package.json dependency */\nconst IGNORE_BINARIES = [\"just\"] as const satisfies string[]\n\n/**\n * - Knip resolves `@adamhl8/eslint-plugin-clean-modules` via the oxlint config, but we don't need to install it directly\n * in the consuming project, so it shows as unlisted\n */\nconst IGNORE_DEPENDENCIES = [\"@adamhl8/eslint-plugin-clean-modules\"] as const satisfies string[]\n\nconst baseConfig = {\n project: [\"**/*\"],\n ignoreBinaries: IGNORE_BINARIES,\n ignoreDependencies: IGNORE_DEPENDENCIES,\n bun: {\n entry: [\"**/*.test.ts\"],\n },\n} as const satisfies KnipConfig\n\nexport const knipConfig = createMergeConfigFn<KnipConfig, typeof baseConfig>(baseConfig)\n"],"mappings":";AAsBA,MAAa,aAAa,oBAAmD;CAR3E,SAAS,CAAC,MAAM;CAChB,gBAAgB,CAVO,MAUO;CAC9B,oBAAoB,CALO,sCAKW;CACtC,KAAK,EACH,OAAO,CAAC,cAAc,EACxB;AAG2E,CAAU"}
1
+ {"version":3,"file":"knip.base.js","names":[],"sources":["../../src/configs/knip.base.ts"],"sourcesContent":["import type { KnipConfig } from \"knip\"\n\nimport { createMergeConfigFn } from \"#merge-config/merge-config.ts\"\n\n/** Knip sees `just` in the `prepare` script and the release-it hooks, but it isn't a package.json dependency */\nconst IGNORE_BINARIES = [\"just\"] as const satisfies string[]\n\n/**\n * - Knip resolves `@adamhl8/eslint-plugin-clean-modules` via the oxlint config, but we don't need to install it directly\n * in the consuming project, so it shows as unlisted\n */\nconst IGNORE_DEPENDENCIES = [\"@adamhl8/eslint-plugin-clean-modules\"] as const satisfies string[]\n\nconst baseConfig = {\n project: [\"**/*\"],\n ignoreBinaries: IGNORE_BINARIES,\n ignoreDependencies: IGNORE_DEPENDENCIES,\n bun: {\n entry: [\"**/*.test.ts\"],\n },\n} as const satisfies KnipConfig\n\nexport const knipConfig = createMergeConfigFn<KnipConfig, typeof baseConfig>(baseConfig)\n"],"mappings":";AAsBA,MAAa,aAAa,oBAAmD;CAR3E,SAAS,CAAC,MAAM;CAChB,gBAAgB,CAVO,MAUO;CAC9B,oBAAoB,CALO,sCAKW;CACtC,KAAK,EACH,OAAO,CAAC,cAAc,EACxB;AAG2E,CAAU"}
@@ -1,4 +1,4 @@
1
- import { OptionalMergeConfigFn } from "../utils.js";
1
+ import { OptionalMergeConfigFn } from "../merge-config/merge-config.js";
2
2
  import { OxfmtConfig } from "oxfmt";
3
3
  //#region src/configs/oxfmt.base.d.ts
4
4
  declare const oxfmtConfig: OptionalMergeConfigFn<OxfmtConfig, {
@@ -1,4 +1,4 @@
1
- import { createMergeConfigFn } from "../utils.js";
1
+ import { createMergeConfigFn } from "../merge-config/merge-config.js";
2
2
  const oxfmtConfig = createMergeConfigFn({
3
3
  printWidth: 120,
4
4
  semi: false,
@@ -1 +1 @@
1
- {"version":3,"file":"oxfmt.base.js","names":[],"sources":["../../src/configs/oxfmt.base.ts"],"sourcesContent":["import type { OxfmtConfig } from \"oxfmt\"\n\nimport { createMergeConfigFn } from \"#utils.ts\"\n\nconst baseConfig = {\n printWidth: 120,\n semi: false,\n jsdoc: true,\n sortImports: {\n groups: [\n \"builtin\",\n \"external\",\n [\"subpath\", \"internal\"],\n [\"index\", \"sibling\", \"parent\"],\n \"side_effect\",\n [\"style\", \"side_effect_style\"],\n ],\n },\n sortTailwindcss: true,\n} as const satisfies OxfmtConfig\n\nexport const oxfmtConfig = createMergeConfigFn<OxfmtConfig, typeof baseConfig>(baseConfig)\n"],"mappings":";AAqBA,MAAa,cAAc,oBAAoD;CAhB7E,YAAY;CACZ,MAAM;CACN,OAAO;CACP,aAAa,EACX,QAAQ;EACN;EACA;EACA,CAAC,WAAW,UAAU;EACtB;GAAC;GAAS;GAAW;EAAQ;EAC7B;EACA,CAAC,SAAS,mBAAmB;CAC/B,EACF;CACA,iBAAiB;AAG4D,CAAU"}
1
+ {"version":3,"file":"oxfmt.base.js","names":[],"sources":["../../src/configs/oxfmt.base.ts"],"sourcesContent":["import type { OxfmtConfig } from \"oxfmt\"\n\nimport { createMergeConfigFn } from \"#merge-config/merge-config.ts\"\n\nconst baseConfig = {\n printWidth: 120,\n semi: false,\n jsdoc: true,\n sortImports: {\n groups: [\n \"builtin\",\n \"external\",\n [\"subpath\", \"internal\"],\n [\"index\", \"sibling\", \"parent\"],\n \"side_effect\",\n [\"style\", \"side_effect_style\"],\n ],\n },\n sortTailwindcss: true,\n} as const satisfies OxfmtConfig\n\nexport const oxfmtConfig = createMergeConfigFn<OxfmtConfig, typeof baseConfig>(baseConfig)\n"],"mappings":";AAqBA,MAAa,cAAc,oBAAoD;CAhB7E,YAAY;CACZ,MAAM;CACN,OAAO;CACP,aAAa,EACX,QAAQ;EACN;EACA;EACA,CAAC,WAAW,UAAU;EACtB;GAAC;GAAS;GAAW;EAAQ;EAC7B;EACA,CAAC,SAAS,mBAAmB;CAC/B,EACF;CACA,iBAAiB;AAG4D,CAAU"}
@@ -1,4 +1,4 @@
1
- import { OptionalMergeConfigFn } from "../utils.js";
1
+ import { OptionalMergeConfigFn } from "../merge-config/merge-config.js";
2
2
  import { OxlintConfig } from "oxlint";
3
3
  //#region src/configs/oxlint.base.d.ts
4
4
  declare const oxlintConfig: OptionalMergeConfigFn<OxlintConfig, {
@@ -1,4 +1,4 @@
1
- import { createMergeConfigFn } from "../utils.js";
1
+ import { createMergeConfigFn } from "../merge-config/merge-config.js";
2
2
  import { fileURLToPath } from "node:url";
3
3
  const oxlintConfig = createMergeConfigFn({
4
4
  options: {
@@ -1 +1 @@
1
- {"version":3,"file":"oxlint.base.js","names":[],"sources":["../../src/configs/oxlint.base.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\"\n\nimport type { OxlintConfig } from \"oxlint\"\n\nimport { createMergeConfigFn } from \"#utils.ts\"\n\nconst baseConfig = {\n options: {\n reportUnusedDisableDirectives: \"error\",\n typeAware: true,\n typeCheck: true,\n },\n\n categories: {\n correctness: \"error\",\n nursery: \"error\",\n pedantic: \"error\",\n perf: \"error\",\n restriction: \"error\",\n style: \"error\",\n suspicious: \"error\",\n },\n\n plugins: [\n \"eslint\",\n \"react\",\n \"unicorn\",\n \"typescript\",\n \"oxc\",\n \"import\",\n \"jsdoc\",\n \"vitest\",\n \"jsx-a11y\",\n \"react-perf\",\n \"promise\",\n \"node\",\n ],\n\n // Resolve to an absolute path so consumers load it from this package's own deps.\n jsPlugins: [fileURLToPath(import.meta.resolve(\"@adamhl8/eslint-plugin-clean-modules\"))],\n\n overrides: [\n {\n files: [\n \".release-it.ts\",\n \"astro.config.ts\",\n \"commitlint.config.ts\",\n \"knip.ts\",\n \"oxfmt.config.ts\",\n \"oxlint.config.ts\",\n \"prisma.config.ts\",\n \"tsdown.config.ts\",\n ],\n rules: {\n \"import/no-default-export\": \"off\",\n },\n },\n {\n files: [\".release-it.ts\"],\n rules: {\n \"no-template-curly-in-string\": \"off\",\n },\n },\n ],\n\n rules: {\n \"clean-modules/require-direct-exports\": \"error\",\n \"clean-modules/require-import-extensions\": \"error\",\n \"clean-modules/require-subpath-imports\": \"error\",\n\n curly: [\"error\", \"multi-or-nest\"],\n \"func-style\": [\"error\", \"expression\"],\n \"react/jsx-curly-brace-presence\": [\"error\", { children: \"never\", propElementValues: \"always\", props: \"never\" }],\n \"require-unicode-regexp\": [\"error\", { requireFlag: \"v\" }],\n \"unicorn/filename-case\": [\n \"error\",\n {\n cases: { kebabCase: true, pascalCase: true },\n },\n ],\n \"unicorn/no-useless-undefined\": [\"error\", { checkArguments: false }],\n\n \"capitalized-comments\": \"off\",\n \"id-length\": \"off\",\n \"import/exports-last\": \"off\",\n \"import/group-exports\": \"off\",\n \"import/max-dependencies\": \"off\",\n \"import/no-named-export\": \"off\",\n \"import/no-nodejs-modules\": \"off\",\n \"import/no-unassigned-import\": \"off\",\n \"import/prefer-default-export\": \"off\",\n \"init-declarations\": \"off\",\n \"jsdoc/require-param\": \"off\",\n \"jsdoc/require-returns\": \"off\",\n \"max-classes-per-file\": \"off\",\n \"max-lines\": \"off\",\n \"max-lines-per-function\": \"off\",\n \"max-statements\": \"off\",\n \"no-console\": \"off\",\n \"no-continue\": \"off\",\n \"no-duplicate-imports\": \"off\",\n \"no-inline-comments\": \"off\",\n \"no-magic-numbers\": \"off\",\n \"no-plusplus\": \"off\",\n \"no-ternary\": \"off\",\n \"no-undef\": \"off\",\n \"no-undefined\": \"off\",\n \"no-useless-return\": \"off\",\n \"no-void\": \"off\",\n \"node/callback-return\": \"off\",\n \"node/no-sync\": \"off\",\n \"oxc/no-async-await\": \"off\",\n \"oxc/no-optional-chaining\": \"off\",\n \"oxc/no-rest-spread-properties\": \"off\",\n \"react/forbid-component-props\": \"off\",\n \"react/jsx-filename-extension\": \"off\",\n \"react/jsx-max-depth\": \"off\",\n \"react/jsx-no-literals\": \"off\",\n \"react/react-in-jsx-scope\": \"off\",\n \"require-await\": \"off\",\n \"sort-imports\": \"off\",\n \"sort-keys\": \"off\",\n \"typescript/consistent-return\": \"off\",\n \"typescript/explicit-function-return-type\": \"off\",\n \"typescript/explicit-module-boundary-types\": \"off\",\n \"typescript/prefer-readonly-parameter-types\": \"off\",\n \"typescript/strict-boolean-expressions\": \"off\",\n \"unicorn/no-array-callback-reference\": \"off\",\n \"unicorn/no-array-method-this-argument\": \"off\",\n \"unicorn/no-await-expression-member\": \"off\",\n \"unicorn/no-process-exit\": \"off\",\n \"unicorn/number-literal-case\": \"off\",\n \"unicorn/switch-case-braces\": \"off\",\n \"vitest/no-hooks\": \"off\",\n \"vitest/no-importing-vitest-globals\": \"off\",\n \"vitest/prefer-describe-function-title\": \"off\",\n \"vitest/prefer-expect-assertions\": \"off\",\n \"vitest/prefer-importing-vitest-globals\": \"off\",\n \"vitest/prefer-lowercase-title\": \"off\",\n \"vitest/prefer-to-be-falsy\": \"off\",\n \"vitest/prefer-to-be-truthy\": \"off\",\n \"vitest/require-hook\": \"off\", // reenable when false positives are fixed?\n \"vitest/require-test-timeout\": \"off\",\n },\n} as const satisfies OxlintConfig\n\nexport const oxlintConfig = createMergeConfigFn<OxlintConfig, typeof baseConfig>(baseConfig)\n"],"mappings":";;AAkJA,MAAa,eAAe,oBAAqD;CA3I/E,SAAS;EACP,+BAA+B;EAC/B,WAAW;EACX,WAAW;CACb;CAEA,YAAY;EACV,aAAa;EACb,SAAS;EACT,UAAU;EACV,MAAM;EACN,aAAa;EACb,OAAO;EACP,YAAY;CACd;CAEA,SAAS;EACP;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CAGA,WAAW,CAAC,cAAc,OAAO,KAAK,QAAQ,sCAAsC,CAAC,CAAC;CAEtF,WAAW,CACT;EACE,OAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,OAAO,EACL,4BAA4B,MAC9B;CACF,GACA;EACE,OAAO,CAAC,gBAAgB;EACxB,OAAO,EACL,+BAA+B,MACjC;CACF,CACF;CAEA,OAAO;EACL,wCAAwC;EACxC,2CAA2C;EAC3C,yCAAyC;EAEzC,OAAO,CAAC,SAAS,eAAe;EAChC,cAAc,CAAC,SAAS,YAAY;EACpC,kCAAkC,CAAC,SAAS;GAAE,UAAU;GAAS,mBAAmB;GAAU,OAAO;EAAQ,CAAC;EAC9G,0BAA0B,CAAC,SAAS,EAAE,aAAa,IAAI,CAAC;EACxD,yBAAyB,CACvB,SACA,EACE,OAAO;GAAE,WAAW;GAAM,YAAY;EAAK,EAC7C,CACF;EACA,gCAAgC,CAAC,SAAS,EAAE,gBAAgB,MAAM,CAAC;EAEnE,wBAAwB;EACxB,aAAa;EACb,uBAAuB;EACvB,wBAAwB;EACxB,2BAA2B;EAC3B,0BAA0B;EAC1B,4BAA4B;EAC5B,+BAA+B;EAC/B,gCAAgC;EAChC,qBAAqB;EACrB,uBAAuB;EACvB,yBAAyB;EACzB,wBAAwB;EACxB,aAAa;EACb,0BAA0B;EAC1B,kBAAkB;EAClB,cAAc;EACd,eAAe;EACf,wBAAwB;EACxB,sBAAsB;EACtB,oBAAoB;EACpB,eAAe;EACf,cAAc;EACd,YAAY;EACZ,gBAAgB;EAChB,qBAAqB;EACrB,WAAW;EACX,wBAAwB;EACxB,gBAAgB;EAChB,sBAAsB;EACtB,4BAA4B;EAC5B,iCAAiC;EACjC,gCAAgC;EAChC,gCAAgC;EAChC,uBAAuB;EACvB,yBAAyB;EACzB,4BAA4B;EAC5B,iBAAiB;EACjB,gBAAgB;EAChB,aAAa;EACb,gCAAgC;EAChC,4CAA4C;EAC5C,6CAA6C;EAC7C,8CAA8C;EAC9C,yCAAyC;EACzC,uCAAuC;EACvC,yCAAyC;EACzC,sCAAsC;EACtC,2BAA2B;EAC3B,+BAA+B;EAC/B,8BAA8B;EAC9B,mBAAmB;EACnB,sCAAsC;EACtC,yCAAyC;EACzC,mCAAmC;EACnC,0CAA0C;EAC1C,iCAAiC;EACjC,6BAA6B;EAC7B,8BAA8B;EAC9B,uBAAuB;EACvB,+BAA+B;CACjC;AAG+E,CAAU"}
1
+ {"version":3,"file":"oxlint.base.js","names":[],"sources":["../../src/configs/oxlint.base.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\"\n\nimport type { OxlintConfig } from \"oxlint\"\n\nimport { createMergeConfigFn } from \"#merge-config/merge-config.ts\"\n\nconst baseConfig = {\n options: {\n reportUnusedDisableDirectives: \"error\",\n typeAware: true,\n typeCheck: true,\n },\n\n categories: {\n correctness: \"error\",\n nursery: \"error\",\n pedantic: \"error\",\n perf: \"error\",\n restriction: \"error\",\n style: \"error\",\n suspicious: \"error\",\n },\n\n plugins: [\n \"eslint\",\n \"react\",\n \"unicorn\",\n \"typescript\",\n \"oxc\",\n \"import\",\n \"jsdoc\",\n \"vitest\",\n \"jsx-a11y\",\n \"react-perf\",\n \"promise\",\n \"node\",\n ],\n\n // Resolve to an absolute path so consumers load it from this package's own deps.\n jsPlugins: [fileURLToPath(import.meta.resolve(\"@adamhl8/eslint-plugin-clean-modules\"))],\n\n overrides: [\n {\n files: [\n \".release-it.ts\",\n \"astro.config.ts\",\n \"commitlint.config.ts\",\n \"knip.ts\",\n \"oxfmt.config.ts\",\n \"oxlint.config.ts\",\n \"prisma.config.ts\",\n \"tsdown.config.ts\",\n ],\n rules: {\n \"import/no-default-export\": \"off\",\n },\n },\n {\n files: [\".release-it.ts\"],\n rules: {\n \"no-template-curly-in-string\": \"off\",\n },\n },\n ],\n\n rules: {\n \"clean-modules/require-direct-exports\": \"error\",\n \"clean-modules/require-import-extensions\": \"error\",\n \"clean-modules/require-subpath-imports\": \"error\",\n\n curly: [\"error\", \"multi-or-nest\"],\n \"func-style\": [\"error\", \"expression\"],\n \"react/jsx-curly-brace-presence\": [\"error\", { children: \"never\", propElementValues: \"always\", props: \"never\" }],\n \"require-unicode-regexp\": [\"error\", { requireFlag: \"v\" }],\n \"unicorn/filename-case\": [\n \"error\",\n {\n cases: { kebabCase: true, pascalCase: true },\n },\n ],\n \"unicorn/no-useless-undefined\": [\"error\", { checkArguments: false }],\n\n \"capitalized-comments\": \"off\",\n \"id-length\": \"off\",\n \"import/exports-last\": \"off\",\n \"import/group-exports\": \"off\",\n \"import/max-dependencies\": \"off\",\n \"import/no-named-export\": \"off\",\n \"import/no-nodejs-modules\": \"off\",\n \"import/no-unassigned-import\": \"off\",\n \"import/prefer-default-export\": \"off\",\n \"init-declarations\": \"off\",\n \"jsdoc/require-param\": \"off\",\n \"jsdoc/require-returns\": \"off\",\n \"max-classes-per-file\": \"off\",\n \"max-lines\": \"off\",\n \"max-lines-per-function\": \"off\",\n \"max-statements\": \"off\",\n \"no-console\": \"off\",\n \"no-continue\": \"off\",\n \"no-duplicate-imports\": \"off\",\n \"no-inline-comments\": \"off\",\n \"no-magic-numbers\": \"off\",\n \"no-plusplus\": \"off\",\n \"no-ternary\": \"off\",\n \"no-undef\": \"off\",\n \"no-undefined\": \"off\",\n \"no-useless-return\": \"off\",\n \"no-void\": \"off\",\n \"node/callback-return\": \"off\",\n \"node/no-sync\": \"off\",\n \"oxc/no-async-await\": \"off\",\n \"oxc/no-optional-chaining\": \"off\",\n \"oxc/no-rest-spread-properties\": \"off\",\n \"react/forbid-component-props\": \"off\",\n \"react/jsx-filename-extension\": \"off\",\n \"react/jsx-max-depth\": \"off\",\n \"react/jsx-no-literals\": \"off\",\n \"react/react-in-jsx-scope\": \"off\",\n \"require-await\": \"off\",\n \"sort-imports\": \"off\",\n \"sort-keys\": \"off\",\n \"typescript/consistent-return\": \"off\",\n \"typescript/explicit-function-return-type\": \"off\",\n \"typescript/explicit-module-boundary-types\": \"off\",\n \"typescript/prefer-readonly-parameter-types\": \"off\",\n \"typescript/strict-boolean-expressions\": \"off\",\n \"unicorn/no-array-callback-reference\": \"off\",\n \"unicorn/no-array-method-this-argument\": \"off\",\n \"unicorn/no-await-expression-member\": \"off\",\n \"unicorn/no-process-exit\": \"off\",\n \"unicorn/number-literal-case\": \"off\",\n \"unicorn/switch-case-braces\": \"off\",\n \"vitest/no-hooks\": \"off\",\n \"vitest/no-importing-vitest-globals\": \"off\",\n \"vitest/prefer-describe-function-title\": \"off\",\n \"vitest/prefer-expect-assertions\": \"off\",\n \"vitest/prefer-importing-vitest-globals\": \"off\",\n \"vitest/prefer-lowercase-title\": \"off\",\n \"vitest/prefer-to-be-falsy\": \"off\",\n \"vitest/prefer-to-be-truthy\": \"off\",\n \"vitest/require-hook\": \"off\", // reenable when false positives are fixed?\n \"vitest/require-test-timeout\": \"off\",\n },\n} as const satisfies OxlintConfig\n\nexport const oxlintConfig = createMergeConfigFn<OxlintConfig, typeof baseConfig>(baseConfig)\n"],"mappings":";;AAkJA,MAAa,eAAe,oBAAqD;CA3I/E,SAAS;EACP,+BAA+B;EAC/B,WAAW;EACX,WAAW;CACb;CAEA,YAAY;EACV,aAAa;EACb,SAAS;EACT,UAAU;EACV,MAAM;EACN,aAAa;EACb,OAAO;EACP,YAAY;CACd;CAEA,SAAS;EACP;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CAGA,WAAW,CAAC,cAAc,OAAO,KAAK,QAAQ,sCAAsC,CAAC,CAAC;CAEtF,WAAW,CACT;EACE,OAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,OAAO,EACL,4BAA4B,MAC9B;CACF,GACA;EACE,OAAO,CAAC,gBAAgB;EACxB,OAAO,EACL,+BAA+B,MACjC;CACF,CACF;CAEA,OAAO;EACL,wCAAwC;EACxC,2CAA2C;EAC3C,yCAAyC;EAEzC,OAAO,CAAC,SAAS,eAAe;EAChC,cAAc,CAAC,SAAS,YAAY;EACpC,kCAAkC,CAAC,SAAS;GAAE,UAAU;GAAS,mBAAmB;GAAU,OAAO;EAAQ,CAAC;EAC9G,0BAA0B,CAAC,SAAS,EAAE,aAAa,IAAI,CAAC;EACxD,yBAAyB,CACvB,SACA,EACE,OAAO;GAAE,WAAW;GAAM,YAAY;EAAK,EAC7C,CACF;EACA,gCAAgC,CAAC,SAAS,EAAE,gBAAgB,MAAM,CAAC;EAEnE,wBAAwB;EACxB,aAAa;EACb,uBAAuB;EACvB,wBAAwB;EACxB,2BAA2B;EAC3B,0BAA0B;EAC1B,4BAA4B;EAC5B,+BAA+B;EAC/B,gCAAgC;EAChC,qBAAqB;EACrB,uBAAuB;EACvB,yBAAyB;EACzB,wBAAwB;EACxB,aAAa;EACb,0BAA0B;EAC1B,kBAAkB;EAClB,cAAc;EACd,eAAe;EACf,wBAAwB;EACxB,sBAAsB;EACtB,oBAAoB;EACpB,eAAe;EACf,cAAc;EACd,YAAY;EACZ,gBAAgB;EAChB,qBAAqB;EACrB,WAAW;EACX,wBAAwB;EACxB,gBAAgB;EAChB,sBAAsB;EACtB,4BAA4B;EAC5B,iCAAiC;EACjC,gCAAgC;EAChC,gCAAgC;EAChC,uBAAuB;EACvB,yBAAyB;EACzB,4BAA4B;EAC5B,iBAAiB;EACjB,gBAAgB;EAChB,aAAa;EACb,gCAAgC;EAChC,4CAA4C;EAC5C,6CAA6C;EAC7C,8CAA8C;EAC9C,yCAAyC;EACzC,uCAAuC;EACvC,yCAAyC;EACzC,sCAAsC;EACtC,2BAA2B;EAC3B,+BAA+B;EAC/B,8BAA8B;EAC9B,mBAAmB;EACnB,sCAAsC;EACtC,yCAAyC;EACzC,mCAAmC;EACnC,0CAA0C;EAC1C,iCAAiC;EACjC,6BAA6B;EAC7B,8BAA8B;EAC9B,uBAAuB;EACvB,+BAA+B;CACjC;AAG+E,CAAU"}
@@ -1,4 +1,4 @@
1
- import { OptionalMergeConfigFn } from "../utils.js";
1
+ import { OptionalMergeConfigFn } from "../merge-config/merge-config.js";
2
2
  import { Config } from "release-it";
3
3
  //#region src/configs/release-it.base.d.ts
4
4
  declare const releaseItConfig: OptionalMergeConfigFn<Config, {
@@ -1,4 +1,4 @@
1
- import { createMergeConfigFn } from "../utils.js";
1
+ import { createMergeConfigFn } from "../merge-config/merge-config.js";
2
2
  const releaseItConfig = createMergeConfigFn({
3
3
  hooks: {
4
4
  "before:init": "just build",
@@ -1 +1 @@
1
- {"version":3,"file":"release-it.base.js","names":[],"sources":["../../src/configs/release-it.base.ts"],"sourcesContent":["// oxlint-disable no-template-curly-in-string\nimport type { Config } from \"release-it\"\n\nimport { createMergeConfigFn } from \"#utils.ts\"\n\nconst baseConfig = {\n hooks: {\n \"before:init\": \"just build\",\n \"after:bump\": [\"bun adamhl8-cliff --tag ${version} -o\", \"bun oxfmt CHANGELOG.md\"],\n },\n git: {\n commitMessage: \"release: ${version}\",\n tagName: \"${version}\",\n tagAnnotation: \"release ${version}\",\n addUntrackedFiles: true,\n },\n github: {\n release: true,\n releaseName: \"${version}\",\n releaseNotes: \"bun adamhl8-cliff --unreleased --tag ${version}\",\n },\n npm: {\n publishPackageManager: \"bun\",\n publishPath: \"\",\n publishArgs: [\"--ignore-scripts\"],\n },\n} as const satisfies Config\n\nexport const releaseItConfig = createMergeConfigFn<Config, typeof baseConfig>(baseConfig)\n"],"mappings":";AA4BA,MAAa,kBAAkB,oBAA+C;CAtB5E,OAAO;EACL,eAAe;EACf,cAAc,CAAC,yCAAyC,wBAAwB;CAClF;CACA,KAAK;EACH,eAAe;EACf,SAAS;EACT,eAAe;EACf,mBAAmB;CACrB;CACA,QAAQ;EACN,SAAS;EACT,aAAa;EACb,cAAc;CAChB;CACA,KAAK;EACH,uBAAuB;EACvB,aAAa;EACb,aAAa,CAAC,kBAAkB;CAClC;AAG4E,CAAU"}
1
+ {"version":3,"file":"release-it.base.js","names":[],"sources":["../../src/configs/release-it.base.ts"],"sourcesContent":["// oxlint-disable no-template-curly-in-string\nimport type { Config } from \"release-it\"\n\nimport { createMergeConfigFn } from \"#merge-config/merge-config.ts\"\n\nconst baseConfig = {\n hooks: {\n \"before:init\": \"just build\",\n \"after:bump\": [\"bun adamhl8-cliff --tag ${version} -o\", \"bun oxfmt CHANGELOG.md\"],\n },\n git: {\n commitMessage: \"release: ${version}\",\n tagName: \"${version}\",\n tagAnnotation: \"release ${version}\",\n addUntrackedFiles: true,\n },\n github: {\n release: true,\n releaseName: \"${version}\",\n releaseNotes: \"bun adamhl8-cliff --unreleased --tag ${version}\",\n },\n npm: {\n publishPackageManager: \"bun\",\n publishPath: \"\",\n publishArgs: [\"--ignore-scripts\"],\n },\n} as const satisfies Config\n\nexport const releaseItConfig = createMergeConfigFn<Config, typeof baseConfig>(baseConfig)\n"],"mappings":";AA4BA,MAAa,kBAAkB,oBAA+C;CAtB5E,OAAO;EACL,eAAe;EACf,cAAc,CAAC,yCAAyC,wBAAwB;CAClF;CACA,KAAK;EACH,eAAe;EACf,SAAS;EACT,eAAe;EACf,mBAAmB;CACrB;CACA,QAAQ;EACN,SAAS;EACT,aAAa;EACb,cAAc;CAChB;CACA,KAAK;EACH,uBAAuB;EACvB,aAAa;EACb,aAAa,CAAC,kBAAkB;CAClC;AAG4E,CAAU"}
@@ -1,4 +1,4 @@
1
- import { MergeConfigFn } from "../utils.js";
1
+ import { MergeConfigFn } from "../merge-config/merge-config.js";
2
2
  import { SetRequired } from "type-fest";
3
3
  import { UserConfig } from "tsdown";
4
4
  //#region src/configs/tsdown.base.d.ts
@@ -26,6 +26,7 @@ declare const baseConfig: {
26
26
  };
27
27
  readonly publint: true;
28
28
  readonly failOnWarn: true;
29
+ readonly suppressWarnings: ["does not yet have a stable API"];
29
30
  };
30
31
  type StrictBundleConfig = SetRequired<UserConfig, "platform" | "entry">;
31
32
  declare const bundleConfig: {
@@ -38,6 +39,7 @@ declare const bundleConfig: {
38
39
  readonly neverBundle: ["bun"];
39
40
  };
40
41
  readonly failOnWarn: true;
42
+ readonly suppressWarnings: ["does not yet have a stable API"];
41
43
  readonly entry: [];
42
44
  readonly unbundle: false;
43
45
  readonly sourcemap: false;
@@ -60,6 +62,7 @@ declare const binConfig: {
60
62
  readonly neverBundle: ["bun"];
61
63
  };
62
64
  readonly failOnWarn: true;
65
+ readonly suppressWarnings: ["does not yet have a stable API"];
63
66
  readonly entry: [];
64
67
  readonly unbundle: false;
65
68
  readonly sourcemap: false;
@@ -1 +1 @@
1
- {"version":3,"file":"tsdown.base.d.ts","names":[],"sources":["../../src/configs/tsdown.base.ts"],"mappings":";;;;KAOK,eAAe,YAAY;cAE1B;;;;;;;;;;aAUF;aACA;aACA;;;aAGA;;;aAGA;aACA;;;;;KAOC,qBAAqB,YAAY;cAGhC;;;;;;;aAdF;;;;;;;;;;aAuBA;eACE;;;;KAMD,kBAAkB,YAAY;cAG7B;;;;;;aAjCF;;;;;;;;;;aAuBA;eACE;;;;;;;;cAeO,cAAc,cAAc,qBAAqB;cAKjD,oBAAoB,cAAc,2BAA2B;cAK7D,iBAAiB,cAAc,wBAAwB"}
1
+ {"version":3,"file":"tsdown.base.d.ts","names":[],"sources":["../../src/configs/tsdown.base.ts"],"mappings":";;;;KAOK,eAAe,YAAY;cAE1B;;;;;;;;;;aAUF;aACA;aACA;;;aAGA;;;aAGA;aACA;;;;;;KAQC,qBAAqB,YAAY;cAGhC;;;;;;;aAfF;;;;;;;;;;;aAwBA;eACE;;;;KAMD,kBAAkB,YAAY;cAG7B;;;;;;aAlCF;;;;;;;;;;;aAwBA;eACE;;;;;;;;cAiBO,cAAc,cAAc,qBAAqB;cAKjD,oBAAoB,cAAc,2BAA2B;cAK7D,iBAAiB,cAAc,wBAAwB"}
@@ -1,4 +1,4 @@
1
- import { createMergeConfigFn } from "../utils.js";
1
+ import { createMergeConfigFn } from "../merge-config/merge-config.js";
2
2
  //#region src/configs/tsdown.base.ts
3
3
  const baseConfig = {
4
4
  entry: ["./src/index.ts"],
@@ -20,7 +20,8 @@ const baseConfig = {
20
20
  profile: "esm-only"
21
21
  },
22
22
  publint: true,
23
- failOnWarn: true
23
+ failOnWarn: true,
24
+ suppressWarnings: ["does not yet have a stable API"]
24
25
  };
25
26
  const bundleConfig = {
26
27
  ...baseConfig,
@@ -1 +1 @@
1
- {"version":3,"file":"tsdown.base.js","names":[],"sources":["../../src/configs/tsdown.base.ts"],"sourcesContent":["import type { UserConfig } from \"tsdown\"\nimport type { SetRequired } from \"type-fest\"\n\nimport type { MergeConfigFn } from \"#utils.ts\"\nimport { createMergeConfigFn } from \"#utils.ts\"\n\n// Force projects to specify platform\ntype StrictConfig = SetRequired<UserConfig, \"platform\">\n\nconst baseConfig = {\n entry: [\"./src/index.ts\"],\n platform: \"neutral\",\n target: false,\n unbundle: true,\n sourcemap: true,\n minify: \"dce-only\",\n fixedExtension: false,\n hash: false,\n dts: {\n newContext: true,\n tsgo: true,\n sourcemap: true,\n },\n deps: {\n neverBundle: [\"bun\"],\n },\n attw: {\n level: \"error\",\n profile: \"esm-only\",\n },\n publint: true,\n failOnWarn: true,\n} as const satisfies StrictConfig\n\n// Force projects to specify platform and entry\ntype StrictBundleConfig = SetRequired<UserConfig, \"platform\" | \"entry\">\n\n// Single-file bundle (not a published library): bundle everything, drop types/lint/sourcemap.\nconst bundleConfig = {\n ...baseConfig,\n entry: [],\n unbundle: false,\n sourcemap: false,\n dts: false,\n attw: false,\n publint: false,\n inputOptions: {\n experimental: {\n attachDebugInfo: \"none\", // remove region comments from bundled output\n },\n },\n} as const satisfies StrictBundleConfig\n\n// Force projects to specify platform and entry\ntype StrictBinConfig = SetRequired<UserConfig, \"entry\">\n\n// A bin is a node bundle with no output file extension.\nconst binConfig = {\n ...bundleConfig,\n platform: \"node\",\n outExtensions: () => ({ js: \"\" }) as const,\n} as const satisfies StrictBinConfig\n\nexport const tsdownConfig: MergeConfigFn<StrictConfig, typeof baseConfig> = createMergeConfigFn<\n StrictConfig,\n typeof baseConfig\n>(baseConfig)\n\nexport const tsdownBundleConfig: MergeConfigFn<StrictBundleConfig, typeof bundleConfig> = createMergeConfigFn<\n StrictBundleConfig,\n typeof bundleConfig\n>(bundleConfig)\n\nexport const tsdownBinConfig: MergeConfigFn<StrictBinConfig, typeof binConfig> = createMergeConfigFn<\n StrictBinConfig,\n typeof binConfig\n>(binConfig)\n"],"mappings":";;AASA,MAAM,aAAa;CACjB,OAAO,CAAC,gBAAgB;CACxB,UAAU;CACV,QAAQ;CACR,UAAU;CACV,WAAW;CACX,QAAQ;CACR,gBAAgB;CAChB,MAAM;CACN,KAAK;EACH,YAAY;EACZ,MAAM;EACN,WAAW;CACb;CACA,MAAM,EACJ,aAAa,CAAC,KAAK,EACrB;CACA,MAAM;EACJ,OAAO;EACP,SAAS;CACX;CACA,SAAS;CACT,YAAY;AACd;AAMA,MAAM,eAAe;CACnB,GAAG;CACH,OAAO,CAAC;CACR,UAAU;CACV,WAAW;CACX,KAAK;CACL,MAAM;CACN,SAAS;CACT,cAAc,EACZ,cAAc,EACZ,iBAAiB,OACnB,EACF;AACF;AAMA,MAAM,YAAY;CAChB,GAAG;CACH,UAAU;CACV,sBAAsB,EAAE,IAAI,GAAG;AACjC;AAEA,MAAa,eAA+D,oBAG1E,UAAU;AAEZ,MAAa,qBAA6E,oBAGxF,YAAY;AAEd,MAAa,kBAAoE,oBAG/E,SAAS"}
1
+ {"version":3,"file":"tsdown.base.js","names":[],"sources":["../../src/configs/tsdown.base.ts"],"sourcesContent":["import type { UserConfig } from \"tsdown\"\nimport type { SetRequired } from \"type-fest\"\n\nimport type { MergeConfigFn } from \"#merge-config/merge-config.ts\"\nimport { createMergeConfigFn } from \"#merge-config/merge-config.ts\"\n\n// Force projects to specify platform\ntype StrictConfig = SetRequired<UserConfig, \"platform\">\n\nconst baseConfig = {\n entry: [\"./src/index.ts\"],\n platform: \"neutral\",\n target: false,\n unbundle: true,\n sourcemap: true,\n minify: \"dce-only\",\n fixedExtension: false,\n hash: false,\n dts: {\n newContext: true,\n tsgo: true,\n sourcemap: true,\n },\n deps: {\n neverBundle: [\"bun\"],\n },\n attw: {\n level: \"error\",\n profile: \"esm-only\",\n },\n publint: true,\n failOnWarn: true,\n suppressWarnings: [\"does not yet have a stable API\"],\n} as const satisfies StrictConfig\n\n// Force projects to specify platform and entry\ntype StrictBundleConfig = SetRequired<UserConfig, \"platform\" | \"entry\">\n\n// Single-file bundle (not a published library): bundle everything, drop types/lint/sourcemap.\nconst bundleConfig = {\n ...baseConfig,\n entry: [],\n unbundle: false,\n sourcemap: false,\n dts: false,\n attw: false,\n publint: false,\n inputOptions: {\n experimental: {\n attachDebugInfo: \"none\", // remove region comments from bundled output\n },\n },\n} as const satisfies StrictBundleConfig\n\n// Force projects to specify platform and entry\ntype StrictBinConfig = SetRequired<UserConfig, \"entry\">\n\n// A bin is a node bundle with no output file extension.\nconst binConfig = {\n ...bundleConfig,\n platform: \"node\",\n outExtensions: () => ({ js: \"\" }) as const,\n} as const satisfies StrictBinConfig\n\n// The export annotations forbid the no-arg call (platform/entry must be provided) and are load-bearing for\n// declaration emit: without one, tsgo synthesizes the type and fails with TS2883 on tsdown's non-exported internals.\nexport const tsdownConfig: MergeConfigFn<StrictConfig, typeof baseConfig> = createMergeConfigFn<\n StrictConfig,\n typeof baseConfig\n>(baseConfig)\n\nexport const tsdownBundleConfig: MergeConfigFn<StrictBundleConfig, typeof bundleConfig> = createMergeConfigFn<\n StrictBundleConfig,\n typeof bundleConfig\n>(bundleConfig)\n\nexport const tsdownBinConfig: MergeConfigFn<StrictBinConfig, typeof binConfig> = createMergeConfigFn<\n StrictBinConfig,\n typeof binConfig\n>(binConfig)\n"],"mappings":";;AASA,MAAM,aAAa;CACjB,OAAO,CAAC,gBAAgB;CACxB,UAAU;CACV,QAAQ;CACR,UAAU;CACV,WAAW;CACX,QAAQ;CACR,gBAAgB;CAChB,MAAM;CACN,KAAK;EACH,YAAY;EACZ,MAAM;EACN,WAAW;CACb;CACA,MAAM,EACJ,aAAa,CAAC,KAAK,EACrB;CACA,MAAM;EACJ,OAAO;EACP,SAAS;CACX;CACA,SAAS;CACT,YAAY;CACZ,kBAAkB,CAAC,gCAAgC;AACrD;AAMA,MAAM,eAAe;CACnB,GAAG;CACH,OAAO,CAAC;CACR,UAAU;CACV,WAAW;CACX,KAAK;CACL,MAAM;CACN,SAAS;CACT,cAAc,EACZ,cAAc,EACZ,iBAAiB,OACnB,EACF;AACF;AAMA,MAAM,YAAY;CAChB,GAAG;CACH,UAAU;CACV,sBAAsB,EAAE,IAAI,GAAG;AACjC;AAIA,MAAa,eAA+D,oBAG1E,UAAU;AAEZ,MAAa,qBAA6E,oBAGxF,YAAY;AAEd,MAAa,kBAAoE,oBAG/E,SAAS"}
@@ -0,0 +1,19 @@
1
+ import { Clean, MergedConfig } from "./merge.types.js";
2
+ import { ReadonlyDeep } from "type-fest";
3
+ //#region src/merge-config/merge-config.d.ts
4
+ /**
5
+ * The merge config function. `userConfig` is deep merged into `BaseConfig` with arrays appended to the base array. The
6
+ * optional `replaceConfig` is then deep merged into that result with arrays used as-is, so putting an array there
7
+ * replaces just that key (sibling keys still merge normally, and a key in both configs gets the replace config's
8
+ * value). Required keys must be provided in `userConfig`.
9
+ *
10
+ * The `const` type parameters keep literal types without requiring `as const` at the call site. The constraints accept
11
+ * readonly arrays/objects (e.g. from `as const`), and the result is writable so it stays assignable to the tools' own
12
+ * (mutable) config types.
13
+ */
14
+ type MergeConfigFn<UserConfig, BaseConfig> = (<const UserConfigToMerge extends ReadonlyDeep<UserConfig>>(userConfig: UserConfigToMerge) => MergedConfig<BaseConfig, UserConfigToMerge>) & (<const UserConfigToMerge extends ReadonlyDeep<UserConfig>, const ReplaceConfigToMerge extends ReadonlyDeep<Partial<UserConfig>>>(userConfig: UserConfigToMerge, replaceConfig: ReplaceConfigToMerge) => MergedConfig<MergedConfig<BaseConfig, UserConfigToMerge>, ReplaceConfigToMerge, "replace">);
15
+ /** A MergeConfigFn that can also be called with no arguments, returning a writable clone of the base config. */
16
+ type OptionalMergeConfigFn<UserConfig, BaseConfig> = MergeConfigFn<UserConfig, BaseConfig> & (() => Clean<BaseConfig>);
17
+ //#endregion
18
+ export { MergeConfigFn, OptionalMergeConfigFn };
19
+ //# sourceMappingURL=merge-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-config.d.ts","names":[],"sources":["../../src/merge-config/merge-config.ts"],"mappings":";;;;;;;;;;;;;KAgCY,cAAc,YAAY,sBAAsB,0BAA0B,aAAa,aACjG,YAAY,sBACT,aAAa,YAAY,8BAEpB,0BAA0B,aAAa,mBACvC,6BAA6B,aAAa,QAAQ,cAExD,YAAY,mBACZ,eAAe,yBACZ,aAAa,aAAa,YAAY,oBAAoB;;KAGrD,sBAAsB,YAAY,cAAc,cAAc,YAAY,qBAC7E,MAAM"}
@@ -0,0 +1,30 @@
1
+ import { cloneDeep, mergeWith } from "es-toolkit";
2
+ //#region src/merge-config/merge-config.ts
3
+ /**
4
+ * The runtime counterpart of MergedConfig in merge.types.ts: a deep merge whose customizer applies the array mode. The
5
+ * source is pre-cloned so the result never aliases the caller's objects. The merge visits string keys only, so
6
+ * symbol-keyed properties in user configs are dropped (the types mirror this).
7
+ */
8
+ const merge = (target, source, mode) => mergeWith(target, cloneDeep(source), (objValue, srcValue) => {
9
+ if (!Array.isArray(srcValue)) return;
10
+ const userArray = srcValue;
11
+ if (mode === "spread" && Array.isArray(objValue)) return [...objValue, ...userArray];
12
+ return userArray;
13
+ });
14
+ /**
15
+ * Creates a config merge function with proper types for merging with a base config. `UserConfig` is not inferrable, so
16
+ * both type arguments must be passed explicitly.
17
+ */
18
+ const createMergeConfigFn = (baseConfig) => {
19
+ const mergeConfigFn = (userConfig, replaceConfig) => {
20
+ const result = cloneDeep(baseConfig);
21
+ if (userConfig !== void 0) merge(result, userConfig, "spread");
22
+ if (replaceConfig !== void 0) merge(result, replaceConfig, "replace");
23
+ return result;
24
+ };
25
+ return mergeConfigFn;
26
+ };
27
+ //#endregion
28
+ export { createMergeConfigFn };
29
+
30
+ //# sourceMappingURL=merge-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-config.js","names":["baseArray"],"sources":["../../src/merge-config/merge-config.ts"],"sourcesContent":["/* oxlint-disable unicorn/prefer-structured-clone -- structuredClone does not properly clone functions */\nimport { cloneDeep, mergeWith } from \"es-toolkit\"\nimport type { ReadonlyDeep } from \"type-fest\"\n\nimport type { Clean, MergedConfig, Mode } from \"#merge-config/merge.types.ts\"\n\n/**\n * The runtime counterpart of MergedConfig in merge.types.ts: a deep merge whose customizer applies the array mode. The\n * source is pre-cloned so the result never aliases the caller's objects. The merge visits string keys only, so\n * symbol-keyed properties in user configs are dropped (the types mirror this).\n */\nconst merge = (target: object, source: object, mode: Mode): object =>\n mergeWith(target, cloneDeep(source), (objValue: unknown, srcValue: unknown) => {\n if (!Array.isArray(srcValue)) return\n const userArray: unknown[] = srcValue\n if (mode === \"spread\" && Array.isArray(objValue)) {\n const baseArray: unknown[] = objValue\n return [...baseArray, ...userArray]\n }\n return userArray\n })\n\n/**\n * The merge config function. `userConfig` is deep merged into `BaseConfig` with arrays appended to the base array. The\n * optional `replaceConfig` is then deep merged into that result with arrays used as-is, so putting an array there\n * replaces just that key (sibling keys still merge normally, and a key in both configs gets the replace config's\n * value). Required keys must be provided in `userConfig`.\n *\n * The `const` type parameters keep literal types without requiring `as const` at the call site. The constraints accept\n * readonly arrays/objects (e.g. from `as const`), and the result is writable so it stays assignable to the tools' own\n * (mutable) config types.\n */\nexport type MergeConfigFn<UserConfig, BaseConfig> = (<const UserConfigToMerge extends ReadonlyDeep<UserConfig>>(\n userConfig: UserConfigToMerge,\n) => MergedConfig<BaseConfig, UserConfigToMerge>) &\n (<\n const UserConfigToMerge extends ReadonlyDeep<UserConfig>,\n const ReplaceConfigToMerge extends ReadonlyDeep<Partial<UserConfig>>,\n >(\n userConfig: UserConfigToMerge,\n replaceConfig: ReplaceConfigToMerge,\n ) => MergedConfig<MergedConfig<BaseConfig, UserConfigToMerge>, ReplaceConfigToMerge, \"replace\">)\n\n/** A MergeConfigFn that can also be called with no arguments, returning a writable clone of the base config. */\nexport type OptionalMergeConfigFn<UserConfig, BaseConfig> = MergeConfigFn<UserConfig, BaseConfig> &\n (() => Clean<BaseConfig>)\n\n/**\n * Creates a config merge function with proper types for merging with a base config. `UserConfig` is not inferrable, so\n * both type arguments must be passed explicitly.\n */\nexport const createMergeConfigFn = <UserConfig extends object, BaseConfig extends ReadonlyDeep<UserConfig>>(\n baseConfig: BaseConfig,\n): OptionalMergeConfigFn<UserConfig, BaseConfig> => {\n // Loosely typed on purpose, the assertion below gives it the precise public signature\n const mergeConfigFn = (userConfig?: object, replaceConfig?: object) => {\n // Start from a clone so consumers can't mutate the shared base config\n const result = cloneDeep(baseConfig)\n if (userConfig !== undefined) merge(result, userConfig, \"spread\")\n if (replaceConfig !== undefined) merge(result, replaceConfig, \"replace\")\n return result\n }\n\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion\n return mergeConfigFn as OptionalMergeConfigFn<UserConfig, BaseConfig>\n}\n"],"mappings":";;;;;;;AAWA,MAAM,SAAS,QAAgB,QAAgB,SAC7C,UAAU,QAAQ,UAAU,MAAM,IAAI,UAAmB,aAAsB;CAC7E,IAAI,CAAC,MAAM,QAAQ,QAAQ,GAAG;CAC9B,MAAM,YAAuB;CAC7B,IAAI,SAAS,YAAY,MAAM,QAAQ,QAAQ,GAE7C,OAAO,CAAC,GAAGA,UAAW,GAAG,SAAS;CAEpC,OAAO;AACT,CAAC;;;;;AA+BH,MAAa,uBACX,eACkD;CAElD,MAAM,iBAAiB,YAAqB,kBAA2B;EAErE,MAAM,SAAS,UAAU,UAAU;EACnC,IAAI,eAAe,KAAA,GAAW,MAAM,QAAQ,YAAY,QAAQ;EAChE,IAAI,kBAAkB,KAAA,GAAW,MAAM,QAAQ,eAAe,SAAS;EACvE,OAAO;CACT;CAGA,OAAO;AACT"}
@@ -0,0 +1,51 @@
1
+ //#region src/merge-config/merge.types.d.ts
2
+ type AnyFunction = (...args: never[]) => unknown;
3
+ /** How user arrays combine with base arrays: appended in spread mode, used as-is in replace mode. */
4
+ type Mode = "spread" | "replace";
5
+ /**
6
+ * Recursion budget for the merge walk, threaded through every recursive type below. When a call fails the generic
7
+ * constraint, these types run over the full constraint (the whole tool config type, with circular types like Console in
8
+ * it), and the budget makes that walk terminate instead of exploding.
9
+ */
10
+ type MaxDepth = readonly [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
11
+ /**
12
+ * Values the walk must keep as-is instead of mapping over (a mapped type would mangle their methods into plain
13
+ * properties): functions, the toStringTag-less built-ins, and everything declaring `Symbol.toStringTag` (Map, Set,
14
+ * Promise, URL, typed arrays, streams, ...).
15
+ */
16
+ type NonRecursive = AnyFunction | Date | RegExp | Error | {
17
+ readonly [Symbol.toStringTag]: string;
18
+ };
19
+ /**
20
+ * Makes a type deeply writable, keeping tuples, functions, and built-ins as-is. Symbol keys are kept: values that reach
21
+ * Clean land in the result as wholesale clones at runtime, which preserve them (and filtering them here would mangle
22
+ * interface-shaped values with symbol-keyed members, e.g. anything iterable). The `T extends object` gate comes first
23
+ * so primitive leaves (most nodes of a config type) cost one relation, and only recursion spends the depth budget.
24
+ */
25
+ type Clean<T, Depth extends readonly unknown[] = MaxDepth> = T extends object ? Depth extends readonly [unknown, ...infer RemainingDepth] ? T extends NonRecursive ? T : { -readonly [K in keyof T]: Clean<T[K], RemainingDepth>; } : T : T;
26
+ /**
27
+ * In spread mode, base and user arrays concatenate to an element-union array. In replace mode, or over a non-array
28
+ * base, the user array is used as-is.
29
+ */
30
+ type MergeArrays<BaseValue, UserValue extends readonly unknown[], Depth extends readonly unknown[], M extends Mode> = M extends "spread" ? BaseValue extends readonly unknown[] ? (Clean<BaseValue[number], Depth> | Clean<UserValue[number], Depth>)[] : Clean<UserValue, Depth> : Clean<UserValue, Depth>;
31
+ /**
32
+ * Merge one key's values: combine arrays per the mode, recurse into objects, else user wins. Gated on `UserValue
33
+ * extends object` first for the same primitive-leaf reason as Clean.
34
+ */
35
+ type MergeValues<BaseValue, UserValue, Depth extends readonly unknown[], M extends Mode> = UserValue extends object ? UserValue extends readonly unknown[] ? MergeArrays<BaseValue, UserValue, Depth, M> : UserValue extends NonRecursive ? UserValue : BaseValue extends readonly unknown[] | NonRecursive ? Clean<UserValue, Depth> : BaseValue extends object ? MergeObjects<BaseValue, UserValue, Depth, M> : Clean<UserValue, Depth> : UserValue;
36
+ /**
37
+ * Merge two object types key by key, in base key order. An optional user key (only possible when the user passes a
38
+ * wide-typed value rather than a literal) may be absent at runtime, so it unions with the base value. User-only symbol
39
+ * keys are dropped because the runtime merge visits string keys only. The final `extends infer Merged` flattens the
40
+ * intersection through an anonymous mapped type: a named helper alias would show up as `SomeAlias<...>` in hovers
41
+ * instead of the plain object.
42
+ */
43
+ type MergeObjects<BaseConfig, UserConfig, Depth extends readonly unknown[], M extends Mode> = Depth extends readonly [unknown, ...infer RemainingDepth] ? { -readonly [K in keyof BaseConfig]: K extends keyof UserConfig ? undefined extends UserConfig[K] ? MergeValues<BaseConfig[K], Exclude<UserConfig[K], undefined>, RemainingDepth, M> | Clean<BaseConfig[K], RemainingDepth> : MergeValues<BaseConfig[K], UserConfig[K], RemainingDepth, M> : Clean<BaseConfig[K], RemainingDepth>; } & { -readonly [K in keyof UserConfig as K extends keyof BaseConfig | symbol ? never : K]: Clean<UserConfig[K], RemainingDepth>; } extends (infer Merged) ? { [K in keyof Merged]: Merged[K]; } : never : UserConfig;
44
+ /**
45
+ * The merged result type, exact per key: spread-mode arrays show the element-union of base and user, replace-mode
46
+ * arrays show only the user's elements.
47
+ */
48
+ type MergedConfig<BaseConfig, UserConfig, M extends Mode = "spread"> = MergeObjects<BaseConfig, UserConfig, MaxDepth, M>;
49
+ //#endregion
50
+ export { Clean, MergedConfig, Mode };
51
+ //# sourceMappingURL=merge.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.types.d.ts","names":[],"sources":["../../src/merge-config/merge.types.ts"],"mappings":";KAWK,kBAAkB;;KAGX;;;;;;KAOP;;;;;;KAOA,eAAe,cAAc,OAAO,SAAS;YAAoB,OAAO;;;;;;;;KAQjE,MAAM,GAAG,mCAAmC,YAAY,mBAChE,0CAA0C,kBACxC,UAAU,eACR,iBACa,WAAW,IAAI,MAAM,EAAE,IAAI,qBAC1C,IACF;;;;;KAMC,YACH,WACA,sCACA,kCACA,UAAU,QACR,qBACA,wCACG,MAAM,mBAAmB,SAAS,MAAM,mBAAmB,YAC5D,MAAM,WAAW,SACnB,MAAM,WAAW;;;;;KAMhB,YAAY,WAAW,WAAW,kCAAkC,UAAU,QAAQ,2BACvF,uCACE,YAAY,WAAW,WAAW,OAAO,KACzC,kBAAkB,eAChB,YACA,uCAAuC,eACrC,MAAM,WAAW,SACjB,2BACE,aAAa,WAAW,WAAW,OAAO,KAC1C,MAAM,WAAW,SACzB;;;;;;;;KASC,aAAa,YAAY,YAAY,kCAAkC,UAAU,QAAQ,0CAEnF,+BAGM,WAAW,aAAa,gBAAgB,+BAC7B,WAAW,KAEvB,YAAY,WAAW,IAAI,QAAQ,WAAW,gBAAgB,gBAAgB,KAC9E,MAAM,WAAW,IAAI,kBACzB,YAAY,WAAW,IAAI,WAAW,IAAI,gBAAgB,KAC5D,MAAM,WAAW,IAAI,kCAEd,WAAW,cAAc,gBAAgB,8BAA8B,IAAI,MACpF,WAAW,IACX,kCAEY,aACX,WAAW,SAAS,OAAO,gBAEhC;;;;;KAMQ,aAAa,YAAY,YAAY,UAAU,mBAAmB,aAC5E,YACA,YACA,UACA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adamhl8/configs",
3
- "version": "2.1.0",
3
+ "version": "2.2.1",
4
4
  "description": "My shared configs for TS projects",
5
5
  "homepage": "https://github.com/adamhl8/configs",
6
6
  "bugs": {
@@ -46,14 +46,14 @@
46
46
  "prepare": "just prepare"
47
47
  },
48
48
  "dependencies": {
49
- "@adamhl8/eslint-plugin-clean-modules": "^0.1.4",
49
+ "@adamhl8/eslint-plugin-clean-modules": "^0.1.5",
50
50
  "@commitlint/config-conventional": "^21.2.0",
51
51
  "@commitlint/types": "^21.2.0",
52
52
  "arkenv": "^0.12.2",
53
53
  "arktype": "^2.2.3",
54
54
  "es-toolkit": "^1.49.0",
55
55
  "smol-toml": "^1.7.0",
56
- "ts-explicit-errors": "^4.1.2",
56
+ "ts-explicit-errors": "^4.1.3",
57
57
  "ts-extras": "^1.2.0",
58
58
  "type-fest": "^5.8.0"
59
59
  },
@@ -69,7 +69,7 @@
69
69
  "oxlint-tsgolint": "^0.24.0",
70
70
  "publint": "^0.3.21",
71
71
  "release-it": "^20.2.1",
72
- "tsdown": "^0.22.5",
72
+ "tsdown": "^0.22.7",
73
73
  "typescript": "^7.0.2"
74
74
  },
75
75
  "peerDependencies": {
package/dist/utils.d.ts DELETED
@@ -1,79 +0,0 @@
1
- import { MergeDeep, ReadonlyDeep, SimplifyDeep, WritableDeep } from "type-fest";
2
- //#region src/utils.d.ts
3
- type AnyFunction = (...args: never[]) => unknown;
4
- /**
5
- * How a wrapped user array is combined with the base array. - `"merge"` (the default for plain arrays): the user array
6
- * is appended to the base array - `"replace"`: the user array is used as-is
7
- */
8
- type ArrayMergeMode = "merge" | "replace";
9
- /**
10
- * A per-key array override. Wherever a config key takes an array, `{ value: [...], mode: "merge" | "replace" }` may be
11
- * passed instead; `mode` controls how that key's array combines with the base config's. The wrapper never appears in
12
- * the merged result. A config value that is exactly this shape is always consumed as a wrapper; an object with extra
13
- * keys is treated as a plain config object.
14
- */
15
- interface ArrayOverride<Value extends readonly unknown[], Mode extends ArrayMergeMode = ArrayMergeMode> {
16
- readonly value: Value;
17
- readonly mode: Mode;
18
- }
19
- /** True only when T is exactly the wrapper shape (keys value + mode, nothing else), mirroring isArrayOverride. */
20
- type IsArrayOverride<T> = T extends {
21
- readonly value: readonly unknown[];
22
- readonly mode: ArrayMergeMode;
23
- } ? [keyof T] extends [keyof ArrayOverride<readonly unknown[]>] ? true : false : false;
24
- /**
25
- * For every array-typed value in T, additionally allow `{ value: <that array type>, mode: ArrayMergeMode }`.
26
- * Distributes over unions, does not recurse into array elements, and leaves functions/primitives alone.
27
- */
28
- type WithArrayOverrides<T> = T extends readonly unknown[] ? T | ArrayOverride<T> : T extends AnyFunction ? T : T extends object ? { [K in keyof T]: WithArrayOverrides<T[K]>; } : T;
29
- /**
30
- * True if T contains (at any object depth, not inside arrays) a replace-mode wrapper. Depth is a recursion budget: when
31
- * a call fails the generic constraint, this type runs over the full constraint (the whole tool config type), and the
32
- * eager `{ ... }[keyof T]` walk would hit TS2615 on circular types in it (e.g. Console) and bury the real error.
33
- */
34
- type HasReplaceWrapper<T, Depth extends readonly unknown[] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]> = Depth extends readonly [unknown, ...infer RemainingDepth] ? T extends readonly unknown[] ? false : T extends AnyFunction ? false : T extends object ? IsArrayOverride<T> extends true ? T extends {
35
- readonly mode: "replace";
36
- } ? true : false : true extends { [K in keyof T]: HasReplaceWrapper<T[K], RemainingDepth>; }[keyof T] ? true : false : false : false;
37
- /** Keep only the key paths whose leaf is a replace-mode wrapper; unwrap those leaves to their `value` type. */
38
- type DeepPickReplaceWrappers<T> = { [K in keyof T as HasReplaceWrapper<T[K]> extends true ? K : never]: IsArrayOverride<T[K]> extends true ? T[K] extends {
39
- readonly value: infer V extends readonly unknown[];
40
- } ? V : never : DeepPickReplaceWrappers<T[K]>; };
41
- /** Drop key paths whose leaf is a replace-mode wrapper; unwrap merge-mode wrappers in place. */
42
- type DeepDropReplaceWrappers<T> = { [K in keyof T as IsArrayOverride<T[K]> extends true ? T[K] extends {
43
- readonly mode: "replace";
44
- } ? never : K : K]: T[K] extends readonly unknown[] ? T[K] : T[K] extends AnyFunction ? T[K] : IsArrayOverride<T[K]> extends true ? T[K] extends {
45
- readonly value: infer V extends readonly unknown[];
46
- } ? V : never : T[K] extends object ? DeepDropReplaceWrappers<T[K]> : T[K]; };
47
- /**
48
- * The merged result type, exact for per-key overrides: pass 1 merges the user config minus replace-wrapper keys
49
- * (unwrapping merge-mode wrappers, which behave like plain arrays) with array spreading; pass 2 merges the unwrapped
50
- * replace-wrapper keys with array replacement. Instead of returning `BaseConfig & UserConfig` (from `merge`), this is a
51
- * more friendly type via `MergeDeep`.
52
- */
53
- type MergedConfig<BaseConfig, UserConfig> = MergeDeep<MergeDeep<SimplifyDeep<WritableDeep<BaseConfig>>, SimplifyDeep<WritableDeep<DeepDropReplaceWrappers<UserConfig>>>, {
54
- arrayMergeMode: "spread";
55
- }>, SimplifyDeep<WritableDeep<DeepPickReplaceWrappers<UserConfig>>>, {
56
- arrayMergeMode: "replace";
57
- }>;
58
- /**
59
- * The merge config function, where the `UserConfig` passed in is merged with `BaseConfig`.
60
- *
61
- * The `const` type parameter keeps literal types without requiring `as const` at the call site. The constraint is
62
- * `WithArrayOverrides<ReadonlyDeep<...>>` so readonly arrays/objects (e.g. from `as const`) are accepted and any
63
- * array-typed key can alternatively take an ArrayOverride wrapper; the result is `WritableDeep` so it stays assignable
64
- * to the tools' own (mutable) config types.
65
- */
66
- type MergeConfigFn<UserConfig, BaseConfig> = <const UserConfigToMerge extends WithArrayOverrides<ReadonlyDeep<UserConfig>>>(userConfig: UserConfigToMerge) => MergedConfig<BaseConfig, UserConfigToMerge>;
67
- /**
68
- * The optional merge config function, where the `userConfig` argument is optional. - if `UserConfig` is not provided,
69
- * the return type is `BaseConfig` - if `UserConfig` is provided, the return type is the merged type of `BaseConfig` and
70
- * `UserConfig`.
71
- *
72
- * CRITICAL: must stay a type-alias intersection (NOT `interface ... extends MergeConfigFn`): checking the
73
- * interface-inheritance relation forces tsc to relate the no-arg signature's return type against the recursive
74
- * `MergedConfig` type, which explodes to millions of type instantiations.
75
- */
76
- type OptionalMergeConfigFn<UserConfig, BaseConfig> = MergeConfigFn<UserConfig, BaseConfig> & (() => SimplifyDeep<WritableDeep<BaseConfig>>);
77
- //#endregion
78
- export { MergeConfigFn, OptionalMergeConfigFn };
79
- //# sourceMappingURL=utils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils.d.ts","names":[],"sources":["../src/utils.ts"],"mappings":";;KAIK,kBAAkB;;;;;KAMlB;;;;;;;UAQK,cAAc,kCAAkC,aAAa,iBAAiB;WAC7E,OAAO;WACP,MAAM;;;KAiCZ,gBAAgB,KAAK;WAAqB;WAAoC,MAAM;WAC9E,kBAAkB;;;;;KASxB,mBAAmB,KAAK,+BACzB,IAAI,cAAc,KAClB,UAAU,cACR,IACA,sBACK,WAAW,IAAI,mBAAmB,EAAE,SACvC;;;;;;KAOH,kBAAkB,GAAG,qEAAqE,0CAEpF,kBAEP,uCAEE,UAAU,sBAER,mBACE,gBAAgB,kBACd;WAAqB;mCAGL,WAAW,IAAI,kBAAkB,EAAE,IAAI,yBAAwB;;KAOtF,wBAAwB,QAC1B,WAAW,KAAK,kBAAkB,EAAE,mBAAmB,YAAY,gBAAgB,EAAE,mBAClF,EAAE;WAAsB,aAAa;IACnC,YAEF,wBAAwB,EAAE;;KAI3B,wBAAwB,QAC1B,WAAW,KAAK,gBAAgB,EAAE,mBAC/B,EAAE;WAAsB;YAEtB,IACF,IAAI,EAAE,gCACN,EAAE,KACF,EAAE,WAAW,cACX,EAAE,KACF,gBAAgB,EAAE,mBAChB,EAAE;WAAsB,aAAa;IACnC,YAEF,EAAE,oBACA,wBAAwB,EAAE,MAC1B,EAAE;;;;;;;KAST,aAAa,YAAY,cAAc,UAC1C,UACE,aAAa,aAAa,cAC1B,aAAa,aAAa,wBAAwB;EAChD;IAEJ,aAAa,aAAa,wBAAwB;EAChD;;;;;;;;;;KAWQ,cAAc,YAAY,qBAC9B,0BAA0B,mBAAmB,aAAa,cAEhE,YAAY,sBACT,aAAa,YAAY;;;;;;;;;;KAWlB,sBAAsB,YAAY,cAAc,cAAc,YAAY,qBAC7E,aAAa,aAAa"}
package/dist/utils.js DELETED
@@ -1,46 +0,0 @@
1
- import { cloneDeep, isPlainObject, mergeWith } from "es-toolkit";
2
- //#region src/utils.ts
3
- /** Runtime wrapper check, mirrored by IsArrayOverride: plain object with exactly the keys value (array) + mode. */
4
- const isArrayOverride = (v) => {
5
- if (!isPlainObject(v)) return false;
6
- if (Object.keys(v).length !== 2) return false;
7
- const { value } = v;
8
- const { mode } = v;
9
- return Array.isArray(value) && (mode === "merge" || mode === "replace");
10
- };
11
- /**
12
- * A wrapper around es-toolkit's `mergeWith` with a custom merge function that concatenates arrays, honoring per-key
13
- * ArrayOverride wrappers. Wrapper interception must come first: the default path would deep-merge the wrapper as a
14
- * plain object.
15
- */
16
- const merge = (target, source) => mergeWith(target, source, (objValue, srcValue) => {
17
- if (isArrayOverride(srcValue)) {
18
- const { value: userArray, mode } = srcValue;
19
- if (!Array.isArray(objValue)) return userArray;
20
- return mode === "merge" ? [...objValue, ...userArray] : userArray;
21
- }
22
- if (!Array.isArray(objValue)) return;
23
- if (!Array.isArray(srcValue)) return srcValue;
24
- const baseArray = objValue;
25
- const userArray = srcValue;
26
- return [...baseArray, ...userArray];
27
- });
28
- /**
29
- * Creates a config merge function with proper types for merging with a base config.
30
- *
31
- * `UserConfig` is not inferrable, so both type arguments must be passed explicitly. Returning the exact
32
- * `OptionalMergeConfigFn` instantiation (instead of relating a looser type to an annotation at the export site) keeps
33
- * tsc from eagerly evaluating the deep-mapped types over the full config type, which blows the instantiation depth
34
- * limit on large configs like tsdown's.
35
- */
36
- const createMergeConfigFn = (baseConfig) => {
37
- const mergeConfigFn = (userConfig) => {
38
- if (userConfig === void 0) return cloneDeep(baseConfig);
39
- return merge(cloneDeep(baseConfig), cloneDeep(userConfig));
40
- };
41
- return mergeConfigFn;
42
- };
43
- //#endregion
44
- export { createMergeConfigFn };
45
-
46
- //# sourceMappingURL=utils.js.map
package/dist/utils.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils.js","names":["baseArray"],"sources":["../src/utils.ts"],"sourcesContent":["import { cloneDeep, isPlainObject, mergeWith } from \"es-toolkit\"\nimport type { MergeDeep, ReadonlyDeep, WritableDeep, SimplifyDeep } from \"type-fest\"\n\ntype AnyObj = object\ntype AnyFunction = (...args: never[]) => unknown\n\n/**\n * How a wrapped user array is combined with the base array. - `\"merge\"` (the default for plain arrays): the user array\n * is appended to the base array - `\"replace\"`: the user array is used as-is\n */\ntype ArrayMergeMode = \"merge\" | \"replace\"\n\n/**\n * A per-key array override. Wherever a config key takes an array, `{ value: [...], mode: \"merge\" | \"replace\" }` may be\n * passed instead; `mode` controls how that key's array combines with the base config's. The wrapper never appears in\n * the merged result. A config value that is exactly this shape is always consumed as a wrapper; an object with extra\n * keys is treated as a plain config object.\n */\ninterface ArrayOverride<Value extends readonly unknown[], Mode extends ArrayMergeMode = ArrayMergeMode> {\n readonly value: Value\n readonly mode: Mode\n}\n\n/** Runtime wrapper check, mirrored by IsArrayOverride: plain object with exactly the keys value (array) + mode. */\nconst isArrayOverride = (v: unknown): v is { value: unknown[]; mode: ArrayMergeMode } => {\n if (!isPlainObject(v)) return false\n if (Object.keys(v).length !== 2) return false\n const { value } = v\n const { mode } = v\n return Array.isArray(value) && (mode === \"merge\" || mode === \"replace\")\n}\n\n/**\n * A wrapper around es-toolkit's `mergeWith` with a custom merge function that concatenates arrays, honoring per-key\n * ArrayOverride wrappers. Wrapper interception must come first: the default path would deep-merge the wrapper as a\n * plain object.\n */\nconst merge = <T extends AnyObj, S extends AnyObj>(target: T, source: S): T & S =>\n mergeWith(target, source, (objValue: unknown, srcValue: unknown) => {\n if (isArrayOverride(srcValue)) {\n const { value: userArray, mode } = srcValue\n if (!Array.isArray(objValue)) return userArray\n const baseArray: unknown[] = objValue\n return mode === \"merge\" ? [...baseArray, ...userArray] : userArray\n }\n if (!Array.isArray(objValue)) return\n if (!Array.isArray(srcValue)) return srcValue\n const baseArray: unknown[] = objValue\n const userArray: unknown[] = srcValue\n return [...baseArray, ...userArray]\n })\n\n/** True only when T is exactly the wrapper shape (keys value + mode, nothing else), mirroring isArrayOverride. */\ntype IsArrayOverride<T> = T extends { readonly value: readonly unknown[]; readonly mode: ArrayMergeMode }\n ? [keyof T] extends [keyof ArrayOverride<readonly unknown[]>]\n ? true\n : false\n : false\n\n/**\n * For every array-typed value in T, additionally allow `{ value: <that array type>, mode: ArrayMergeMode }`.\n * Distributes over unions, does not recurse into array elements, and leaves functions/primitives alone.\n */\ntype WithArrayOverrides<T> = T extends readonly unknown[]\n ? T | ArrayOverride<T>\n : T extends AnyFunction\n ? T\n : T extends object\n ? { [K in keyof T]: WithArrayOverrides<T[K]> }\n : T\n\n/**\n * True if T contains (at any object depth, not inside arrays) a replace-mode wrapper. Depth is a recursion budget: when\n * a call fails the generic constraint, this type runs over the full constraint (the whole tool config type), and the\n * eager `{ ... }[keyof T]` walk would hit TS2615 on circular types in it (e.g. Console) and bury the real error.\n */\ntype HasReplaceWrapper<T, Depth extends readonly unknown[] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]> = Depth extends readonly [\n unknown,\n ...infer RemainingDepth,\n]\n ? T extends readonly unknown[]\n ? false\n : T extends AnyFunction\n ? false\n : T extends object\n ? IsArrayOverride<T> extends true\n ? T extends { readonly mode: \"replace\" }\n ? true\n : false\n : true extends { [K in keyof T]: HasReplaceWrapper<T[K], RemainingDepth> }[keyof T]\n ? true\n : false\n : false\n : false\n\n/** Keep only the key paths whose leaf is a replace-mode wrapper; unwrap those leaves to their `value` type. */\ntype DeepPickReplaceWrappers<T> = {\n [K in keyof T as HasReplaceWrapper<T[K]> extends true ? K : never]: IsArrayOverride<T[K]> extends true\n ? T[K] extends { readonly value: infer V extends readonly unknown[] }\n ? V\n : never\n : DeepPickReplaceWrappers<T[K]>\n}\n\n/** Drop key paths whose leaf is a replace-mode wrapper; unwrap merge-mode wrappers in place. */\ntype DeepDropReplaceWrappers<T> = {\n [K in keyof T as IsArrayOverride<T[K]> extends true\n ? T[K] extends { readonly mode: \"replace\" }\n ? never\n : K\n : K]: T[K] extends readonly unknown[]\n ? T[K]\n : T[K] extends AnyFunction\n ? T[K]\n : IsArrayOverride<T[K]> extends true\n ? T[K] extends { readonly value: infer V extends readonly unknown[] }\n ? V\n : never\n : T[K] extends object\n ? DeepDropReplaceWrappers<T[K]>\n : T[K]\n}\n\n/**\n * The merged result type, exact for per-key overrides: pass 1 merges the user config minus replace-wrapper keys\n * (unwrapping merge-mode wrappers, which behave like plain arrays) with array spreading; pass 2 merges the unwrapped\n * replace-wrapper keys with array replacement. Instead of returning `BaseConfig & UserConfig` (from `merge`), this is a\n * more friendly type via `MergeDeep`.\n */\ntype MergedConfig<BaseConfig, UserConfig> = MergeDeep<\n MergeDeep<\n SimplifyDeep<WritableDeep<BaseConfig>>,\n SimplifyDeep<WritableDeep<DeepDropReplaceWrappers<UserConfig>>>,\n { arrayMergeMode: \"spread\" }\n >,\n SimplifyDeep<WritableDeep<DeepPickReplaceWrappers<UserConfig>>>,\n { arrayMergeMode: \"replace\" }\n>\n\n/**\n * The merge config function, where the `UserConfig` passed in is merged with `BaseConfig`.\n *\n * The `const` type parameter keeps literal types without requiring `as const` at the call site. The constraint is\n * `WithArrayOverrides<ReadonlyDeep<...>>` so readonly arrays/objects (e.g. from `as const`) are accepted and any\n * array-typed key can alternatively take an ArrayOverride wrapper; the result is `WritableDeep` so it stays assignable\n * to the tools' own (mutable) config types.\n */\nexport type MergeConfigFn<UserConfig, BaseConfig> = <\n const UserConfigToMerge extends WithArrayOverrides<ReadonlyDeep<UserConfig>>,\n>(\n userConfig: UserConfigToMerge,\n) => MergedConfig<BaseConfig, UserConfigToMerge>\n\n/**\n * The optional merge config function, where the `userConfig` argument is optional. - if `UserConfig` is not provided,\n * the return type is `BaseConfig` - if `UserConfig` is provided, the return type is the merged type of `BaseConfig` and\n * `UserConfig`.\n *\n * CRITICAL: must stay a type-alias intersection (NOT `interface ... extends MergeConfigFn`): checking the\n * interface-inheritance relation forces tsc to relate the no-arg signature's return type against the recursive\n * `MergedConfig` type, which explodes to millions of type instantiations.\n */\nexport type OptionalMergeConfigFn<UserConfig, BaseConfig> = MergeConfigFn<UserConfig, BaseConfig> &\n (() => SimplifyDeep<WritableDeep<BaseConfig>>)\n\n/**\n * Creates a config merge function with proper types for merging with a base config.\n *\n * `UserConfig` is not inferrable, so both type arguments must be passed explicitly. Returning the exact\n * `OptionalMergeConfigFn` instantiation (instead of relating a looser type to an annotation at the export site) keeps\n * tsc from eagerly evaluating the deep-mapped types over the full config type, which blows the instantiation depth\n * limit on large configs like tsdown's.\n */\nexport const createMergeConfigFn = <UserConfig extends AnyObj, BaseConfig extends ReadonlyDeep<UserConfig>>(\n baseConfig: BaseConfig,\n): OptionalMergeConfigFn<UserConfig, BaseConfig> => {\n // We don't care about the specific type of userConfig here because we assert mergeConfigFn as the correct type below\n const mergeConfigFn = (userConfig?: AnyObj) => {\n // Return a clone so consumers can't mutate the shared base config\n // oxlint-disable-next-line unicorn/prefer-structured-clone - structuredClone does not properly clone functions\n if (userConfig === undefined) return cloneDeep(baseConfig)\n // Clone both target and source so we never mutate the original objects\n // oxlint-disable-next-line unicorn/prefer-structured-clone - structuredClone does not properly clone functions\n return merge(cloneDeep(baseConfig), cloneDeep(userConfig))\n }\n\n // CRITICAL: two-step assertion required: a direct cast makes tsc run a comparability check relating the\n // implementation signature against the recursive MergedConfig type (millions of instantiations).\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion\n return mergeConfigFn as unknown as OptionalMergeConfigFn<UserConfig, BaseConfig>\n}\n"],"mappings":";;;AAwBA,MAAM,mBAAmB,MAAgE;CACvF,IAAI,CAAC,cAAc,CAAC,GAAG,OAAO;CAC9B,IAAI,OAAO,KAAK,CAAC,CAAC,CAAC,WAAW,GAAG,OAAO;CACxC,MAAM,EAAE,UAAU;CAClB,MAAM,EAAE,SAAS;CACjB,OAAO,MAAM,QAAQ,KAAK,MAAM,SAAS,WAAW,SAAS;AAC/D;;;;;;AAOA,MAAM,SAA6C,QAAW,WAC5D,UAAU,QAAQ,SAAS,UAAmB,aAAsB;CAClE,IAAI,gBAAgB,QAAQ,GAAG;EAC7B,MAAM,EAAE,OAAO,WAAW,SAAS;EACnC,IAAI,CAAC,MAAM,QAAQ,QAAQ,GAAG,OAAO;EAErC,OAAO,SAAS,UAAU,CAAC,GAAGA,UAAW,GAAG,SAAS,IAAI;CAC3D;CACA,IAAI,CAAC,MAAM,QAAQ,QAAQ,GAAG;CAC9B,IAAI,CAAC,MAAM,QAAQ,QAAQ,GAAG,OAAO;CACrC,MAAM,YAAuB;CAC7B,MAAM,YAAuB;CAC7B,OAAO,CAAC,GAAG,WAAW,GAAG,SAAS;AACpC,CAAC;;;;;;;;;AA2HH,MAAa,uBACX,eACkD;CAElD,MAAM,iBAAiB,eAAwB;EAG7C,IAAI,eAAe,KAAA,GAAW,OAAO,UAAU,UAAU;EAGzD,OAAO,MAAM,UAAU,UAAU,GAAG,UAAU,UAAU,CAAC;CAC3D;CAKA,OAAO;AACT"}