config-vp 1.0.0 → 1.1.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.
package/README.md CHANGED
@@ -10,7 +10,7 @@ One shared [vite-plus](https://viteplus.dev) config for all your packages. Pick
10
10
  - **Pre-commit checks** — staged files are checked and auto-fixed.
11
11
  - **VSCode setup** — a one-command task to wire up the oxc extension at your workspace root.
12
12
 
13
- Everything is opinionated and works out of the box. You can still override or extend any piece (see [Customizing](#customizing)).
13
+ Everything is opinionated and works out of the box. You can still customize any piece (see [Customizing](#customizing)).
14
14
 
15
15
  ## Prerequisites
16
16
 
@@ -81,6 +81,8 @@ Set `type` to match what you're building. It drives Vue lint rules, library pack
81
81
 
82
82
  ### Recipes
83
83
 
84
+ Customize with the `config` hook — it receives the fully built config (see [Customizing](#customizing)).
85
+
84
86
  **Library** — dual ESM + CJS output:
85
87
 
86
88
  ```ts
@@ -88,11 +90,12 @@ import { defineConfig } from 'config-vp';
88
90
 
89
91
  const config = defineConfig({
90
92
  type: 'lib',
91
- overrides: {
92
- pack: {
93
+ config: c => {
94
+ c.pack = {
95
+ ...c.pack,
93
96
  entry: ['src/index.ts'],
94
97
  format: ['esm', 'cjs'],
95
- },
98
+ };
96
99
  },
97
100
  });
98
101
  export default config;
@@ -105,11 +108,12 @@ import { defineConfig } from 'config-vp';
105
108
 
106
109
  const config = defineConfig({
107
110
  type: 'lib:vue',
108
- overrides: {
109
- pack: {
111
+ config: c => {
112
+ c.pack = {
113
+ ...c.pack,
110
114
  entry: ['src/index.ts'],
111
115
  format: ['esm', 'cjs'],
112
- },
116
+ };
113
117
  },
114
118
  });
115
119
  export default config;
@@ -124,7 +128,7 @@ import { defineConfig } from 'config-vp';
124
128
 
125
129
  export const vite = defineConfig({
126
130
  type: 'nuxt:spa', // or 'nuxt:ssr'
127
- overrides: {
131
+ config: c => {
128
132
  // any Vite/vite-plus options the app needs, e.g. plugins, define, test, …
129
133
  },
130
134
  }) as NuxtConfig['vite'];
@@ -138,7 +142,7 @@ import { vite } from './nuxt.config.vite';
138
142
  export default defineNuxtConfig({ vite });
139
143
  ```
140
144
 
141
- The `export const vite = …` form is itself a const export, so `vp` discovers any `run.tasks` you add through `overrides.run`.
145
+ The `export const vite = …` form is itself a const export, so `vp` discovers any tasks you add in the `config` hook.
142
146
 
143
147
  **Plain package** — lint + format + tests only, no build/dev (e.g. a config or scripts-only package). Just call `defineConfig()` with no `type`:
144
148
 
@@ -172,6 +176,8 @@ Run any task with `vp run <task>`. Tasks are cached and run in dependency order
172
176
  | `dev` | any `type` | see [Project types](#project-types) |
173
177
  | `release` | `lib*` types | `vp check --fix && vp test --run --passWithNoTests && vp pack && vpx bumpp && pnpm publish` |
174
178
 
179
+ > All built-in tasks are emitted as objects, so the `config` hook can tweak one in place — e.g. `c.run.tasks.release.command = '…'`.
180
+
175
181
  **Workspace root only:**
176
182
 
177
183
  | Task | Command |
@@ -188,81 +194,114 @@ Run any task with `vp run <task>`. Tasks are cached and run in dependency order
188
194
 
189
195
  ## Customizing
190
196
 
191
- Two knobs, same shapethey differ only in how they merge:
192
-
193
- - **`overrides`** — _replace_ semantics. Your values overwrite the defaults. Applied first.
194
- - **`extends`** — _additive_ semantics. Arrays concatenate, objects deep-merge. Applied last, so it's never clobbered by an override.
197
+ There's one customization hook: **`config`**. It receives the fully built config — lint, fmt, staged, run tasks, and (for `lib*`) pack, all already populated and you change whatever you want with plain JS. Mutate it in place, or return a new object (a returned value wins; otherwise the mutated argument is used). It's fully typed, so autocomplete shows you exactly what's there.
195
198
 
196
199
  ```ts
197
200
  const config = defineConfig({
198
201
  type: 'lib',
199
- overrides: {
200
- // Replace the pack entry point entirely
201
- pack: {
202
+ config: c => {
203
+ c.pack = {
204
+ ...c.pack,
202
205
  entry: ['src/index.ts'],
203
206
  format: ['esm', 'cjs'],
204
- },
205
- },
206
- extends: {
207
- // Add to the lint config without losing built-ins
208
- lint: {
209
- rules: { 'no-console': 'error' },
210
- },
207
+ }; // tweak packaging
208
+ c.lint.rules['no-console'] = 'error'; // add a lint rule (base rules stay)
209
+ c.run.tasks.release.command = 'my-release'; // change a built-in task
211
210
  },
212
211
  });
213
212
  export default config;
214
213
  ```
215
214
 
216
- > `lint` is **always** deep-merged (never fully replaced), even under `overrides` so you can't accidentally wipe the base ruleset.
215
+ Because you hold the real config, there are no merge semantics to learn — you decide what to keep (`{ ...c.pack, … }`) and what to replace (`c.pack = { … }`). To drop something, delete it: `delete c.pack` (skip packaging), `delete c.run.tasks.release`.
216
+
217
+ ### Mutate or return
218
+
219
+ The hook works two ways — use whichever reads better:
220
+
221
+ ```ts
222
+ // Mutate in place and return nothing — best for a few targeted tweaks:
223
+ defineConfig({
224
+ type: 'lib',
225
+ config: c => {
226
+ c.pack = {
227
+ ...c.pack,
228
+ entry: ['src/index.ts'],
229
+ };
230
+ },
231
+ });
232
+
233
+ // Return a new object — best when you want to build the result explicitly:
234
+ defineConfig({
235
+ type: 'lib',
236
+ config: c => ({
237
+ ...c,
238
+ pack: {
239
+ ...c.pack,
240
+ entry: ['src/index.ts'],
241
+ },
242
+ }),
243
+ });
244
+ ```
245
+
246
+ A returned value wins; if you return nothing, the mutated argument is used. Don't do both.
217
247
 
218
248
  ### Your existing Vite config goes here
219
249
 
220
- `overrides` and `extends` accept **any** vite-plus `UserConfig` field, not just config-vp's own keys. So a whole normal Vite config — `plugins`, `resolve`, `define`, `server`, `optimizeDeps`, `worker`, `test`, `build`, `run.tasks`, … — drops straight into `extends` (additive) or `overrides` (replacing). There's nothing config-vp-specific to learn: keep writing Vite config, just nest it.
250
+ Anything in a normal Vite / vite-plus config — `plugins`, `resolve`, `define`, `server`, `optimizeDeps`, `worker`, `test`, `build`, extra `run.tasks`, … — is just a field you set in the hook. Nothing config-vp-specific to learn; keep writing Vite config.
221
251
 
222
252
  ```ts
223
253
  const config = defineConfig({
224
254
  type: 'nuxt:spa',
225
- extends: {
226
- plugins: [tailwindcss()],
227
- define: { 'import.meta.env.VITE_RELEASE': JSON.stringify(release) },
228
- optimizeDeps: { exclude: ['some-wasm-dep'] },
229
- server: { fs: { allow: ['../..'] } },
230
- test, // your vitest config
231
- run: {
232
- tasks: {
233
- preview: { command: 'nuxt preview', cache: false },
234
- deploy: { command: 'vpx tsx scripts/deploy.ts' },
255
+ config: c => {
256
+ c.plugins = [...tailwindcss()];
257
+ c.define = {
258
+ 'import.meta.env.VITE_RELEASE': JSON.stringify(release),
259
+ };
260
+ c.optimizeDeps = { exclude: ['some-wasm-dep'] };
261
+ c.server = {
262
+ fs: {
263
+ allow: ['../..'],
235
264
  },
236
- },
265
+ };
266
+ c.test = { exclude: ['e2e/**'] };
267
+ c.run.tasks.preview = {
268
+ command: 'nuxt preview',
269
+ cache: false,
270
+ };
271
+ c.run.tasks.deploy = { command: 'vpx tsx scripts/deploy.ts' };
237
272
  },
238
273
  });
239
274
  export default config;
240
275
  ```
241
276
 
242
- Your entire app-specific Vite setup lives in `extends`, on top of config-vp's `nuxt:spa` defaults.
243
-
244
277
  ### Ignore patterns (lint + format)
245
278
 
246
- A top-level `ignorePatterns` is forwarded to **both** the linter and the formatter:
279
+ `ignorePatterns` sets the ignore globs for **both** the linter and the formatter. Pass an array to set the whole list, or a function to derive it from the built-in defaults:
247
280
 
248
281
  ```ts
249
282
  defineConfig({
250
283
  type: 'lib',
251
- // Replace the built-in ignore list:
252
- overrides: { ignorePatterns: ['only-this/**'] },
253
- // …or add to it, keeping the defaults:
254
- extends: { ignorePatterns: ['generated/**', 'vendor/**'] },
284
+ // add to the defaults:
285
+ ignorePatterns: defaults => [...defaults, 'generated/**', 'vendor/**'],
286
+ });
287
+
288
+ defineConfig({
289
+ type: 'lib',
290
+ // …or set the list outright:
291
+ ignorePatterns: ['only-this/**'],
255
292
  });
256
293
  ```
257
294
 
258
295
  ### Disable packaging
259
296
 
260
- A `lib*` type includes packaging by default. Turn it off with `pack: false`:
297
+ A `lib*` type includes packaging by default. To skip it, delete `pack` in the hook:
261
298
 
262
299
  ```ts
263
300
  defineConfig({
264
301
  type: 'lib',
265
- overrides: { pack: false },
302
+ config: c => {
303
+ delete c.pack;
304
+ },
266
305
  });
267
306
  ```
268
307
 
@@ -271,25 +310,12 @@ defineConfig({
271
310
  | Option | Type | Description |
272
311
  | --- | --- | --- |
273
312
  | `type` | `ProjectType` | `'lib' \| 'lib:vue' \| 'lib:nuxt' \| 'vue' \| 'nuxt:spa' \| 'nuxt:ssr'`. Omit for shared/root packages. |
274
- | `overrides` | object | Replace-semantics customizations. Accepts `ignorePatterns` and `pack: false`. |
275
- | `extends` | object | Additive customizations, applied after `overrides`. Same shape. |
276
-
277
- ### Merge reference
278
-
279
- Order: `base → overrides (replace) → extends (additive)`.
280
-
281
- | Key | Under `overrides` | Under `extends` |
282
- | --------------- | ------------------------------------- | -------------------- |
283
- | `lint` | arrays concat, rules deep-merge | same |
284
- | `fmt` | shallow-merge | recursive deep-merge |
285
- | `pack` | shallow-merge (or `false` to disable) | recursive deep-merge |
286
- | `staged` | shallow-merge | recursive deep-merge |
287
- | `run` | shallow-merge, tasks combined | recursive deep-merge |
288
- | arrays (others) | replace | concatenate |
313
+ | `ignorePatterns` | `string[] \| (defaults: string[]) => string[]` | Ignore globs for lint **and** fmt. Array sets the list; function derives it from the defaults. |
314
+ | `config` | `(config) => config \| void` | Customization hook receives the fully built config to mutate in place and/or return. |
289
315
 
290
316
  ## Default ignore patterns
291
317
 
292
- The base list applied to both linting and formatting (override or extend it via the [ignore-patterns shortcut](#ignore-patterns-lint--format)):
318
+ The base list applied to both linting and formatting (override or extend it via [`ignorePatterns`](#ignore-patterns-lint--format)):
293
319
 
294
320
  ```
295
321
  *.log* **/.output **/.vp-tsconfig
@@ -301,7 +327,7 @@ The base list applied to both linting and formatting (override or extend it via
301
327
 
302
328
  ## API
303
329
 
304
- The package exports a single function, `defineConfig`, along with its `ProjectType` and `ConfigOptions` types for annotation.
330
+ The package exports a single function, `defineConfig`, plus the types `ConfigOptions` (its argument), `ProjectType`, and `ResolvedConfig` (the value passed to the `config` hook).
305
331
 
306
332
  ## Troubleshooting
307
333
 
package/dist/index.d.mts CHANGED
@@ -1,22 +1,41 @@
1
+ import { OxlintConfig } from "oxlint";
1
2
  import { UserConfig } from "vite-plus";
2
3
 
3
4
  //#region src/index.d.ts
4
5
  type ProjectType = 'lib' | 'lib:nuxt' | 'lib:vue' | 'nuxt:spa' | 'nuxt:ssr' | 'vue';
5
- type DeepPartial<T> = T extends ((...args: never[]) => unknown) ? T : T extends (infer U)[] ? DeepPartial<U>[] : T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;
6
+ type Run = NonNullable<UserConfig['run']>;
7
+ /** A single task as the object form (config-vp always emits objects, never string shorthands). */
8
+ type Task = Exclude<NonNullable<Run['tasks']>[string], string | string[]>;
9
+ /** The pack config object form (never the multi-output array). */
10
+ type Pack = Exclude<NonNullable<UserConfig['pack']>, readonly unknown[]>;
11
+ /**
12
+ * The config as config-vp actually builds it: `lint`/`fmt`/`staged`/`run` are always present,
13
+ * `run.tasks` is a map of task objects, and `pack` (when present) is the object form. This lets
14
+ * the `config` hook mutate `c.run.tasks.x.command`, spread `c.pack`, etc. without guards.
15
+ */
16
+ type ResolvedConfig = Omit<UserConfig, 'fmt' | 'lint' | 'pack' | 'run' | 'staged'> & {
17
+ lint: OxlintConfig;
18
+ fmt: NonNullable<UserConfig['fmt']>;
19
+ staged: NonNullable<UserConfig['staged']>;
20
+ run: Omit<Run, 'tasks'> & {
21
+ tasks: Record<string, Task>;
22
+ };
23
+ pack?: Pack;
24
+ };
6
25
  interface ConfigOptions {
7
- /** Project type — drives vue lint, pack inclusion, and dev/build/release tasks. */
26
+ /** Project type — drives Vue lint rules, library packaging, and dev/build/release tasks. */
8
27
  type?: ProjectType;
9
- /** Replace semantics: values replace defaults, objects shallow-merge at known keys. Exception: lint is always additive (deep-merged). Pass `pack: false` to suppress pack inclusion. */
10
- overrides?: DeepPartial<UserConfig> & {
11
- ignorePatterns?: string[];
12
- pack?: unknown;
13
- };
14
- /** Additive semantics: arrays concatenate, objects deep-merge. Applied AFTER overrides. Pass `pack: false` to suppress pack inclusion. */
15
- extends?: DeepPartial<UserConfig> & {
16
- ignorePatterns?: string[];
17
- pack?: unknown;
18
- };
28
+ /**
29
+ * Ignore globs for both lint and fmt. An array sets the list; a function receives the built-in
30
+ * defaults and returns the final list (e.g. `(defaults) => [...defaults, 'generated/**']`).
31
+ */
32
+ ignorePatterns?: ((defaults: string[]) => string[]) | string[];
33
+ /**
34
+ * Final customization hook. Receives the fully built config and may mutate it in place and/or
35
+ * return a new one (a returned value wins; otherwise the mutated argument is used).
36
+ */
37
+ config?: (config: ResolvedConfig) => ResolvedConfig | undefined;
19
38
  }
20
39
  declare function defineConfig(options?: ConfigOptions): UserConfig;
21
40
  //#endregion
22
- export { ConfigOptions, ProjectType, defineConfig };
41
+ export { ConfigOptions, ProjectType, ResolvedConfig, defineConfig };
package/dist/index.mjs CHANGED
@@ -635,54 +635,8 @@ const vueLint = {
635
635
  };
636
636
  //#endregion
637
637
  //#region src/index.ts
638
- const DEEP_MERGE_KEYS = new Set([
639
- "fmt",
640
- "pack",
641
- "staged"
642
- ]);
643
- function deepExtend(target, source) {
644
- if (source === void 0) return target;
645
- if (Array.isArray(target) && Array.isArray(source)) return [...target, ...source];
646
- if (target !== null && typeof target === "object" && !Array.isArray(target) && source !== null && typeof source === "object" && !Array.isArray(source)) {
647
- const result = { ...target };
648
- for (const [key, value] of Object.entries(source)) result[key] = key in result ? deepExtend(result[key], value) : value;
649
- return result;
650
- }
651
- return source;
652
- }
653
- function mergeLayers(base, ...layers) {
654
- const result = { ...base };
655
- for (const layer of layers) {
656
- if (layer.lint) {
657
- const { extends: layerExtends, ...layerLintRest } = layer.lint;
658
- const baseArr = result.lint ? [result.lint] : [];
659
- const combined = layerExtends !== void 0 ? [...baseArr, ...Array.isArray(layerExtends) ? layerExtends : [layerExtends]] : baseArr;
660
- result.lint = combined.length > 0 ? {
661
- extends: combined,
662
- ...layerLintRest
663
- } : { ...layerLintRest };
664
- }
665
- if (layer.run) result.run = {
666
- ...result.run,
667
- ...layer.run,
668
- tasks: {
669
- ...result.run?.tasks,
670
- ...layer.run.tasks
671
- }
672
- };
673
- for (const [key, value] of Object.entries(layer)) {
674
- if (key === "lint" || key === "run") continue;
675
- if (DEEP_MERGE_KEYS.has(key)) result[key] = {
676
- ...result[key],
677
- ...value
678
- };
679
- else result[key] = value;
680
- }
681
- }
682
- return result;
683
- }
638
+ /** Merge OxlintConfig objects: arrays concat, nested objects shallow-merge, scalars replace. */
684
639
  function mergeLintConfigs(...configs) {
685
- if (configs.length === 0) return {};
686
640
  if (configs.length === 1) return { ...configs[0] };
687
641
  return configs.reduce((acc, config) => {
688
642
  const merged = { ...acc };
@@ -702,60 +656,28 @@ function defineConfig(options = {}) {
702
656
  const t = options.type;
703
657
  const isVue = t === "lib:vue" || t === "lib:nuxt" || t === "vue" || t === "nuxt:spa" || t === "nuxt:ssr";
704
658
  const isLib = t === "lib" || t === "lib:vue" || t === "lib:nuxt";
705
- const overridesPack = options.overrides?.pack;
706
- const extendsPack = options.extends?.pack;
707
- const isPack = (isLib || !!overridesPack || !!extendsPack) && overridesPack !== false && extendsPack !== false;
708
- const lintLayers = [lint];
709
- if (isVue) lintLayers.push(vueLint);
710
- if (options.overrides?.lint) lintLayers.push(options.overrides.lint);
711
- if (options.extends?.lint) lintLayers.push(options.extends.lint);
712
- let mergedLint = mergeLintConfigs(...lintLayers);
713
- if (options.overrides?.ignorePatterns) mergedLint = {
714
- ...mergedLint,
715
- ignorePatterns: options.overrides.ignorePatterns
716
- };
717
- if (options.extends?.ignorePatterns) mergedLint = {
718
- ...mergedLint,
719
- ignorePatterns: [...mergedLint.ignorePatterns ?? [], ...options.extends.ignorePatterns]
720
- };
721
- let mergedFmt = fmt;
722
- if (options.overrides?.ignorePatterns) mergedFmt = {
723
- ...mergedFmt,
724
- ignorePatterns: options.overrides.ignorePatterns
725
- };
726
- if (options.extends?.ignorePatterns) mergedFmt = {
727
- ...mergedFmt,
728
- ignorePatterns: [...mergedFmt?.ignorePatterns ?? [], ...options.extends.ignorePatterns]
729
- };
659
+ const mergedLint = isVue ? mergeLintConfigs(lint, vueLint) : mergeLintConfigs(lint);
660
+ const defaultIgnore = mergedLint.ignorePatterns ?? [];
661
+ const ignorePatterns = typeof options.ignorePatterns === "function" ? options.ignorePatterns(defaultIgnore) : options.ignorePatterns ?? defaultIgnore;
730
662
  const run = buildRunConfig({
731
663
  lib: isLib,
732
664
  nuxt: t === "nuxt:spa" ? "spa" : t === "nuxt:ssr" ? "ssr" : void 0,
733
665
  vueApp: t === "vue"
734
666
  });
735
- let result = {
736
- lint: mergedLint,
737
- fmt: mergedFmt,
667
+ const base = {
668
+ lint: {
669
+ ...mergedLint,
670
+ ignorePatterns
671
+ },
672
+ fmt: {
673
+ ...fmt,
674
+ ignorePatterns
675
+ },
738
676
  staged,
739
677
  run,
740
- ...isPack && { pack }
678
+ ...isLib && { pack }
741
679
  };
742
- if (options.overrides) {
743
- const { lint: _l, ignorePatterns: _ip, pack: _p, ...rest } = options.overrides;
744
- const layer = overridesPack !== void 0 && overridesPack !== false ? {
745
- ...rest,
746
- pack: overridesPack
747
- } : rest;
748
- if (Object.keys(layer).length > 0) result = mergeLayers(result, layer);
749
- }
750
- if (options.extends) {
751
- const { lint: _l, ignorePatterns: _ip, pack: _p, ...rest } = options.extends;
752
- const layer = extendsPack !== void 0 && extendsPack !== false ? {
753
- ...rest,
754
- pack: extendsPack
755
- } : rest;
756
- if (Object.keys(layer).length > 0) result = deepExtend(result, layer);
757
- }
758
- return result;
680
+ return options.config ? options.config(base) ?? base : base;
759
681
  }
760
682
  //#endregion
761
683
  export { defineConfig };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "config-vp",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Shared vite-plus configuration — opinionated defaults for linting, formatting, task running, staged checks, and VSCode setup. Optional Vue and pack layers with deep-merge overrides.",
5
5
  "keywords": [
6
6
  "config",
@@ -47,10 +47,10 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@stylistic/eslint-plugin": "^5.10.0",
50
- "eslint-plugin-perfectionist": "^5.8.0",
50
+ "eslint-plugin-perfectionist": "^5.9.0",
51
51
  "jsonc-parser": "^3.3.1",
52
- "oxfmt": "^0.45.0",
53
- "oxlint": "^1.60.0"
52
+ "oxfmt": "^0.53.0",
53
+ "oxlint": "^1.68.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/node": "^24.10.1",