@liangmi/vp-config 0.0.0-alpha.0 → 0.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 +107 -8
- package/dist/constants-D9DskZO3.d.mts +5 -0
- package/dist/index.d.mts +26 -2
- package/dist/index.mjs +444 -4
- package/dist/info-CHtwxjhy.mjs +306 -0
- package/dist/oxlint-plugin/index.d.mts +34 -0
- package/dist/oxlint-plugin/index.mjs +161 -0
- package/package.json +13 -7
package/README.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# @liangmi/vp-config
|
|
2
2
|
|
|
3
|
-
Liang's
|
|
4
|
-
|
|
5
|
-
It is designed to work with different kinds of project, cli / tui development, library, and website development (WIP, waiting for [Oxlint's better Vue support](https://github.com/oxc-project/oxc/issues/15761))
|
|
3
|
+
Liang's united [Vite+](https://viteplus.dev/) config presets for JavaScript development — opinionated, strict and designed to be universal with different kinds of projects.
|
|
6
4
|
|
|
7
5
|
## Usage
|
|
8
6
|
|
|
@@ -15,15 +13,116 @@ vp install -D @liangmi/vp-config
|
|
|
15
13
|
And modify your `vite.config.ts` like that:
|
|
16
14
|
|
|
17
15
|
```typescript
|
|
18
|
-
import {
|
|
16
|
+
import { base } from "@liangmi/vp-config";
|
|
17
|
+
|
|
18
|
+
export default base({
|
|
19
|
+
/* Your personal config overrides, will be merged deeply */
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
> [!WARNING]
|
|
24
|
+
> Considering Vite+ is now still alpha, the internal API can be unstable and expected to change, please manage to use `@liangmi/vp-config` with the latest Vite+. If you found something that doesn't work expectedly, please [submit an issue](https://github.com/liangmiQwQ/vp-config/issues/new).
|
|
25
|
+
|
|
26
|
+
### Categories
|
|
27
|
+
|
|
28
|
+
We provide four config categories for different kinds of projects.
|
|
29
|
+
|
|
30
|
+
| Category | Description | Recommended for |
|
|
31
|
+
| --------- | ------------------------------------------------------ | -------------------- |
|
|
32
|
+
| `base` | Pure and basic config | Workspace root |
|
|
33
|
+
| `cli` | Config for Node.js CLI and React/Vue Tui applications | CLI and TUI projects |
|
|
34
|
+
| `lib` | Config with library bundling defaults | Libraries |
|
|
35
|
+
| `website` | Config for browser environment and website development | Websites |
|
|
36
|
+
|
|
37
|
+
The `website` category is experimental and its defaults may change before the package reaches a stable release. React and browser linting are available, but Vue template linting is still waiting for [better Vue support in Oxlint](https://github.com/oxc-project/oxc/issues/15761).
|
|
38
|
+
|
|
39
|
+
For monorepos, different presets should be used in combination. We should use `base` the workspace root, and use other categories as needed.
|
|
40
|
+
|
|
41
|
+
### Customizable
|
|
42
|
+
|
|
43
|
+
Each category is a wrapper of Vite+'s `defineConfig`. The config passed to it overrides and deeply merges with the preset.
|
|
44
|
+
|
|
45
|
+
> [!TIP]
|
|
46
|
+
>
|
|
47
|
+
> Deep merging means your config only needs to specify what should change. Nested preset options that you do not override remain enabled, while values from your config take precedence.
|
|
48
|
+
|
|
49
|
+
Use `.only()` to load only selected parts of a preset:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { base } from "@liangmi/vp-config";
|
|
19
53
|
|
|
20
|
-
export default
|
|
21
|
-
|
|
54
|
+
export default base.only(["lint", "fmt"], {
|
|
55
|
+
lint: {
|
|
56
|
+
/* Your own lint config */
|
|
57
|
+
},
|
|
58
|
+
fmt: {
|
|
59
|
+
/* Your own format config */
|
|
60
|
+
},
|
|
22
61
|
});
|
|
23
62
|
```
|
|
24
63
|
|
|
25
|
-
|
|
64
|
+
Use `.exclude()` to omit selected parts while keeping the rest:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { base } from "@liangmi/vp-config";
|
|
68
|
+
|
|
69
|
+
export default base.exclude(["staged"], {});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Available config parts are `fmt`, `lint`, `pack`, `run`, and `staged`, depending on the selected category.
|
|
73
|
+
|
|
74
|
+
## What's included by default
|
|
75
|
+
|
|
76
|
+
Vite+ is a united toolchain for JavaScript development, it includes linting, formatting, library bundling, git hooks, test, task runner, website development, etc.
|
|
77
|
+
|
|
78
|
+
`@liangmi/vp-config` tries to extract reusable configurations from these, in order to improve the development experience as much as possible.
|
|
79
|
+
|
|
80
|
+
### Lint
|
|
81
|
+
|
|
82
|
+
The lint config prioritizes correctness and fast feedback. Rules that prevent bugs report errors, while style and fixable readability rules generally report warnings and are left to autofixes. Warnings also fail the lint command, keeping the codebase consistent without treating every style concern as a hand-written task.
|
|
83
|
+
|
|
84
|
+
All categories include a strict Oxlint config with type-aware linting and type checking enabled, which means you do not need to run `tsc` manually. Correctness, performance, suspicious, and nursery rules report errors.
|
|
85
|
+
|
|
86
|
+
`console.log` is not allowed except in the `cli` category and Node.js script files. Test files enable Vitest rules, while the `cli` and `website` categories add React and Vue component rules. The `website` category also enables browser-specific rules.
|
|
87
|
+
|
|
88
|
+
The included `liangmi` Oxlint plugin checks that presets are loaded correctly. It reports orphan `vite.config.ts` files, preset imports outside config files, missing preset wrappers, improper root or project categories, and configs that mix library and website signals.
|
|
89
|
+
|
|
90
|
+
### Format
|
|
91
|
+
|
|
92
|
+
The format config follows a simple philosophy: remove syntax that does not improve readability, and let the formatter handle mechanical consistency.
|
|
93
|
+
|
|
94
|
+
All categories include an Oxfmt config using single quotes, no semicolons, no unnecessary trailing commas, sorted imports, and sorted `package.json` fields. Embedded-language formatting is disabled for `base` and `lib`, and enabled for `cli` and `website`.
|
|
95
|
+
|
|
96
|
+
### Pack
|
|
97
|
+
|
|
98
|
+
The packaging presets provide the best-practice defaults for each kind of project while leaving project-specific details explicit. Because the package entry depends on the project's source layout, you still need to define `pack.entry` manually.
|
|
99
|
+
|
|
100
|
+
The `lib` preset generates `.d.ts` and package exports with fixed extensions. The `cli` preset targets Node.js, minifies output, strips `node:` protocol prefixes, and disables `dts` generation.
|
|
101
|
+
|
|
102
|
+
### Cached commands
|
|
103
|
+
|
|
104
|
+
In order to make full use of Vite+'s powerful cache system without too much config and make it contributors-friendly, we provide cached tasks task wrappers for common Vite+ commands. This feature is included in all categories.
|
|
105
|
+
|
|
106
|
+
In most cases, they should be treated more like cached versions of Vite+ commands rather than normal user-defined tasks. For example, users can run `vpr ccheck` as a cached replacement for `vp check`.
|
|
107
|
+
|
|
108
|
+
| Task | Command |
|
|
109
|
+
| --------- | ----------- |
|
|
110
|
+
| `cbuild` | `vp build` |
|
|
111
|
+
| `cpack` | `vp pack` |
|
|
112
|
+
| `clint` | `vp lint` |
|
|
113
|
+
| `cfmt` | `vp fmt` |
|
|
114
|
+
| `cformat` | `vp format` |
|
|
115
|
+
| `ccheck` | `vp check` |
|
|
116
|
+
| `ctest` | `vp test` |
|
|
117
|
+
|
|
118
|
+
Run them with `vp run <task>`, such as `vp run cpack`, or shorthand `vpr cpack`. They can also be used in `package.json` scripts while retaining Vite+ task caching.
|
|
119
|
+
|
|
120
|
+
### Staged files
|
|
121
|
+
|
|
122
|
+
Staged-file checks keep automatic fixes close to the commit workflow, so only code that is about to be committed is processed.
|
|
123
|
+
|
|
124
|
+
All categories run `vp check --fix` for staged files. Run `vp config` to install Vite+'s commit hook.
|
|
26
125
|
|
|
27
126
|
## License
|
|
28
127
|
|
|
29
|
-
[MIT](./LICENSE) License © 2026-PRESENT Liang
|
|
128
|
+
[MIT](./LICENSE) License © 2026-PRESENT Liang & Contributors
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
|
+
import { UserConfig, defineConfig } from "vite-plus";
|
|
2
|
+
import { PackUserConfig } from "vite-plus/pack";
|
|
3
|
+
|
|
4
|
+
//#region src/entry.d.ts
|
|
5
|
+
interface PresetConfig {
|
|
6
|
+
fmt?: UserConfig['fmt'];
|
|
7
|
+
lint?: UserConfig['lint'];
|
|
8
|
+
pack?: PackUserConfig;
|
|
9
|
+
run?: UserConfig['run'];
|
|
10
|
+
staged?: UserConfig['staged'];
|
|
11
|
+
}
|
|
12
|
+
type ConfigArgs = Parameters<typeof defineConfig>;
|
|
13
|
+
type ConfigResult = ReturnType<typeof defineConfig>;
|
|
14
|
+
type Unique<T extends readonly unknown[]> = T extends readonly [infer Head, ...infer Tail] ? Head extends Tail[number] ? never : readonly [Head, ...Unique<Tail>] : T;
|
|
15
|
+
type ConfigPart<PresetConfig> = Extract<keyof NonNullable<PresetConfig>, string>;
|
|
16
|
+
type ConfigFunction<PresetConfig> = <const Parts extends readonly ConfigPart<PresetConfig>[]>(parts: Parts & Unique<Parts>, ...args: ConfigArgs) => ConfigResult;
|
|
17
|
+
type ConfigEntry<PresetConfig> = {
|
|
18
|
+
only: ConfigFunction<PresetConfig>;
|
|
19
|
+
exclude: ConfigFunction<PresetConfig>;
|
|
20
|
+
} & typeof defineConfig;
|
|
21
|
+
//#endregion
|
|
1
22
|
//#region src/index.d.ts
|
|
2
|
-
declare
|
|
23
|
+
declare const base: ConfigEntry<PresetConfig>;
|
|
24
|
+
declare const cli: ConfigEntry<PresetConfig>;
|
|
25
|
+
declare const lib: ConfigEntry<PresetConfig>;
|
|
26
|
+
declare const website: ConfigEntry<PresetConfig>;
|
|
3
27
|
//#endregion
|
|
4
|
-
export {
|
|
28
|
+
export { base, cli, lib, website };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,446 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { c as writeRuntimeInfo } from "./info-CHtwxjhy.mjs";
|
|
2
|
+
import { defineConfig, mergeConfig } from "vite-plus";
|
|
3
|
+
//#region src/base/fmt.ts
|
|
4
|
+
const fmtBase = {
|
|
5
|
+
arrowParens: "avoid",
|
|
6
|
+
embeddedLanguageFormatting: "off",
|
|
7
|
+
singleQuote: true,
|
|
8
|
+
sortImports: { partitionByComment: true },
|
|
9
|
+
sortPackageJson: { sortScripts: true },
|
|
10
|
+
semi: false,
|
|
11
|
+
trailingComma: "none"
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/shared/lint.ts
|
|
15
|
+
const cliOverride = {
|
|
16
|
+
env: { node: true },
|
|
17
|
+
plugins: ["node"],
|
|
18
|
+
rules: {
|
|
19
|
+
"no-console": "off",
|
|
20
|
+
"unicorn/no-process-exit": "off",
|
|
21
|
+
"node/no-path-concat": "error"
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const componentOverride = {
|
|
25
|
+
env: { vue: true },
|
|
26
|
+
plugins: [
|
|
27
|
+
"react",
|
|
28
|
+
"react-perf",
|
|
29
|
+
"vue"
|
|
30
|
+
],
|
|
31
|
+
rules: {
|
|
32
|
+
"react/react-in-jsx-scope": "off",
|
|
33
|
+
"react/no-clone-element": "error",
|
|
34
|
+
"react/no-react-children": "error",
|
|
35
|
+
"react/prefer-function-component": "error",
|
|
36
|
+
"vue/no-import-compiler-macros": "warn",
|
|
37
|
+
"react/rules-of-hooks": "error",
|
|
38
|
+
"react/jsx-no-useless-fragment": "warn",
|
|
39
|
+
"react/no-unescaped-entities": "warn",
|
|
40
|
+
"react/jsx-curly-brace-presence": ["warn", {
|
|
41
|
+
children: "never",
|
|
42
|
+
propElementValues: "always",
|
|
43
|
+
props: "never"
|
|
44
|
+
}],
|
|
45
|
+
"vue/define-emits-declaration": ["warn", "type-literal"],
|
|
46
|
+
"vue/define-props-declaration": ["warn", "type-based"],
|
|
47
|
+
"vue/next-tick-style": ["warn", "promise"],
|
|
48
|
+
"vue/prop-name-casing": ["warn", "camelCase"],
|
|
49
|
+
"vue/require-prop-types": "off",
|
|
50
|
+
"react/jsx-max-depth": "off",
|
|
51
|
+
"react/no-redundant-should-component-update": "off",
|
|
52
|
+
"react/jsx-props-no-spreading": "off"
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/base/lint.ts
|
|
57
|
+
const suspicious = {
|
|
58
|
+
"no-shadow": "off",
|
|
59
|
+
"typescript/no-unsafe-type-assertion": "off"
|
|
60
|
+
};
|
|
61
|
+
const nursery = {
|
|
62
|
+
"no-undef": "off",
|
|
63
|
+
"import/named": "off",
|
|
64
|
+
"no-restricted-exports": "off"
|
|
65
|
+
};
|
|
66
|
+
const style = {
|
|
67
|
+
"no-duplicate-imports": ["warn", { allowSeparateTypeImports: true }],
|
|
68
|
+
"typescript/consistent-type-definitions": ["warn", "interface"],
|
|
69
|
+
"typescript/consistent-indexed-object-style": ["warn", "record"],
|
|
70
|
+
"eslint/func-names": ["warn", "never"],
|
|
71
|
+
"id-length": ["warn", { checkGeneric: false }],
|
|
72
|
+
"typescript/method-signature-style": ["warn", "property"],
|
|
73
|
+
"unicorn/prefer-ternary": ["warn", "only-single-line"],
|
|
74
|
+
"import/exports-last": "off",
|
|
75
|
+
"func-style": "off",
|
|
76
|
+
"init-declarations": "off",
|
|
77
|
+
"import/no-anonymous-default-export": "off",
|
|
78
|
+
"unicorn/no-await-expression-member": "off",
|
|
79
|
+
"max-params": "off",
|
|
80
|
+
"max-statements": "off",
|
|
81
|
+
"no-continue": "off",
|
|
82
|
+
"no-nested-ternary": "off",
|
|
83
|
+
"unicorn/no-nested-ternary": "off",
|
|
84
|
+
"no-magic-numbers": "off",
|
|
85
|
+
"import/no-namespace": "off",
|
|
86
|
+
"import/no-named-export": "off",
|
|
87
|
+
"no-ternary": "off",
|
|
88
|
+
"sort-imports": "off",
|
|
89
|
+
"sort-keys": "off",
|
|
90
|
+
"import/group-exports": "off",
|
|
91
|
+
"import/no-nodejs-modules": "off",
|
|
92
|
+
"import/prefer-default-export": "off",
|
|
93
|
+
"prefer-await-to-then": "off",
|
|
94
|
+
"unicorn/no-null": "off",
|
|
95
|
+
"eslint/prefer-named-capture-group": "off"
|
|
96
|
+
};
|
|
97
|
+
const restriction = {
|
|
98
|
+
"oxc/bad-bitwise-operator": "error",
|
|
99
|
+
"class-methods-use-this": "error",
|
|
100
|
+
"default-case": "error",
|
|
101
|
+
"typescript/explicit-function-return-type": "error",
|
|
102
|
+
"typescript/explicit-module-boundary-types": "error",
|
|
103
|
+
"import/extensions": [
|
|
104
|
+
"error",
|
|
105
|
+
"always",
|
|
106
|
+
{
|
|
107
|
+
checkTypeImports: true,
|
|
108
|
+
ignorePackages: true
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
"unicorn/no-abusive-eslint-disable": "error",
|
|
112
|
+
"no-alert": "error",
|
|
113
|
+
"import/no-amd": "error",
|
|
114
|
+
"no-array-reduce": "error",
|
|
115
|
+
"unicorn/prefer-module": "error",
|
|
116
|
+
"no-console": "error",
|
|
117
|
+
"import/no-cycle": "error",
|
|
118
|
+
"oxc/no-const-enum": "error",
|
|
119
|
+
"typescript/no-dynamic-delete": "error",
|
|
120
|
+
"no-dynamic-require": "error",
|
|
121
|
+
"no-empty": "error",
|
|
122
|
+
"no-empty-function": "error",
|
|
123
|
+
"no-explicit-any": "error",
|
|
124
|
+
"no-implicit-globals": "error",
|
|
125
|
+
"typescript/no-import-type-side-effects": "error",
|
|
126
|
+
"typescript/no-invalid-void-type": "error",
|
|
127
|
+
"unicorn/no-magic-array-flat-depth": "error",
|
|
128
|
+
"typescript/no-namespace": "error",
|
|
129
|
+
"typescript/no-non-null-asserted-nullish-coalescing": "error",
|
|
130
|
+
"no-param-reassign": "error",
|
|
131
|
+
"unicorn/no-process-exit": "error",
|
|
132
|
+
"no-sequences": "error",
|
|
133
|
+
"no-var": "error",
|
|
134
|
+
"non-nullable-type-assertion-style": "error",
|
|
135
|
+
"unicorn/prefer-modern-math-apis": "error",
|
|
136
|
+
"unicode-bom": "error",
|
|
137
|
+
"prefer-node-protocol": "warn",
|
|
138
|
+
"unicorn/prefer-number-properties": "warn",
|
|
139
|
+
"no-array-for-each": "warn",
|
|
140
|
+
"no-div-regex": "warn",
|
|
141
|
+
"no-default-export": "warn",
|
|
142
|
+
"no-empty-object-type": "warn",
|
|
143
|
+
"no-proto": "warn"
|
|
144
|
+
};
|
|
145
|
+
const pedantic = {
|
|
146
|
+
eqeqeq: "error",
|
|
147
|
+
"oxc/branches-sharing-code": "error",
|
|
148
|
+
"array-callback-return": "error",
|
|
149
|
+
"unicorn/consistent-empty-array-spread": "error",
|
|
150
|
+
"max-classes-per-file": ["error", { max: 0 }],
|
|
151
|
+
"max-depth": ["error", { max: 5 }],
|
|
152
|
+
"eslint/max-nested-callbacks": ["error", { max: 6 }],
|
|
153
|
+
"unicorn/new-for-builtins": "error",
|
|
154
|
+
"no-case-declarations": "error",
|
|
155
|
+
"no-constructor-return": "error",
|
|
156
|
+
"typescript/no-deprecated": "error",
|
|
157
|
+
"no-fallthrough": "error",
|
|
158
|
+
"unicorn/no-immediate-mutation": "error",
|
|
159
|
+
"no-loop-func": "error",
|
|
160
|
+
"typescript/no-misused-promises": "error",
|
|
161
|
+
"typescript/no-mixed-enums": "error",
|
|
162
|
+
"unicorn/no-new-buffer": "error",
|
|
163
|
+
"no-object-constructor": "error",
|
|
164
|
+
"no-promise-executor-return": "error",
|
|
165
|
+
"no-self-compare": "error",
|
|
166
|
+
"no-throw-literal": "error",
|
|
167
|
+
"no-unnecessary-array-flat-depth": "error",
|
|
168
|
+
"typescript/only-throw-error": "error",
|
|
169
|
+
"prefer-array-some": "error",
|
|
170
|
+
"prefer-code-point": "error",
|
|
171
|
+
"typescript/prefer-enum-initializers": "error",
|
|
172
|
+
"unicorn/prefer-event-target": "error",
|
|
173
|
+
"typescript/prefer-nullish-coalescing": "error",
|
|
174
|
+
"typescript/prefer-promise-reject-errors": "error",
|
|
175
|
+
"typescript/require-await": "error",
|
|
176
|
+
"require-unicode-regexp": "error",
|
|
177
|
+
"typescript/return-await": ["error", "never"],
|
|
178
|
+
"unicorn/no-hex-escape": "warn",
|
|
179
|
+
"unicorn/no-typeof-undefined": "warn",
|
|
180
|
+
"unicorn/no-unnecessary-array-splice-count": "warn",
|
|
181
|
+
"unicorn/no-unnecessary-slice-end": "warn",
|
|
182
|
+
"unicorn/no-instanceof-array": "warn",
|
|
183
|
+
"no-else-return": "warn",
|
|
184
|
+
"typescript/ban-ts-comment": "warn",
|
|
185
|
+
"typescript/no-confusing-void-expression": "warn",
|
|
186
|
+
"unicorn/escape-case": "warn",
|
|
187
|
+
"unicorn/explicit-length-check": "warn",
|
|
188
|
+
"no-array-constructor": "warn",
|
|
189
|
+
"unicorn/no-useless-promise-resolve-reject": "warn",
|
|
190
|
+
"no-useless-undefined": "warn",
|
|
191
|
+
"prefer-date-now": "warn",
|
|
192
|
+
"prefer-import-meta-properties": "warn",
|
|
193
|
+
"prefer-includes": "warn",
|
|
194
|
+
"prefer-math-min-max": "warn",
|
|
195
|
+
"unicorn/prefer-prototype-methods": "warn",
|
|
196
|
+
"unicorn/prefer-regexp-test": "warn",
|
|
197
|
+
"unicorn/prefer-string-replace-all": "warn",
|
|
198
|
+
"unicorn/prefer-string-slice": "warn",
|
|
199
|
+
"prefer-type-error": "warn",
|
|
200
|
+
"unicorn/require-number-to-fixed-digits-argument": "warn",
|
|
201
|
+
"typescript/strict-void-return": "warn",
|
|
202
|
+
"unicorn/prefer-native-coercion-functions": "warn",
|
|
203
|
+
"unicorn/prefer-array-flat": "warn",
|
|
204
|
+
"eslint/no-useless-return": "warn",
|
|
205
|
+
"unicorn/no-useless-switch-case": "warn",
|
|
206
|
+
"no-lonely-if": "warn",
|
|
207
|
+
"unicorn/no-negation-in-equality-check": "warn",
|
|
208
|
+
"no-negated-condition": "warn"
|
|
209
|
+
};
|
|
210
|
+
const lintBase = {
|
|
211
|
+
categories: {
|
|
212
|
+
correctness: "error",
|
|
213
|
+
perf: "error",
|
|
214
|
+
suspicious: "error",
|
|
215
|
+
nursery: "error",
|
|
216
|
+
style: "warn",
|
|
217
|
+
restriction: "off",
|
|
218
|
+
pedantic: "off"
|
|
219
|
+
},
|
|
220
|
+
jsPlugins: [{
|
|
221
|
+
name: "liangmi",
|
|
222
|
+
specifier: "@liangmi/vp-config/oxlint-plugin"
|
|
223
|
+
}],
|
|
224
|
+
plugins: [
|
|
225
|
+
"eslint",
|
|
226
|
+
"oxc",
|
|
227
|
+
"import",
|
|
228
|
+
"promise",
|
|
229
|
+
"typescript",
|
|
230
|
+
"unicorn"
|
|
231
|
+
],
|
|
232
|
+
rules: {
|
|
233
|
+
...suspicious,
|
|
234
|
+
...nursery,
|
|
235
|
+
...restriction,
|
|
236
|
+
...pedantic,
|
|
237
|
+
...style,
|
|
238
|
+
"liangmi/no-orphan-vite-config": "error",
|
|
239
|
+
"liangmi/no-useless-vp-preset-imports": "error",
|
|
240
|
+
"liangmi/use-preset-vp-config": "error",
|
|
241
|
+
"liangmi/load-proper-vp-config-category": "error",
|
|
242
|
+
"liangmi/no-mixed-project": "error"
|
|
243
|
+
},
|
|
244
|
+
options: {
|
|
245
|
+
typeAware: true,
|
|
246
|
+
typeCheck: true,
|
|
247
|
+
denyWarnings: true,
|
|
248
|
+
reportUnusedDisableDirectives: "warn",
|
|
249
|
+
respectEslintDisableDirectives: false
|
|
250
|
+
},
|
|
251
|
+
overrides: [
|
|
252
|
+
{
|
|
253
|
+
env: {
|
|
254
|
+
node: true,
|
|
255
|
+
vitest: true
|
|
256
|
+
},
|
|
257
|
+
plugins: ["vitest"],
|
|
258
|
+
rules: {
|
|
259
|
+
"vitest/consistent-test-filename": ["warn", { pattern: ".*.test.ts$" }],
|
|
260
|
+
"vitest/consistent-test-it": ["warn", {
|
|
261
|
+
fn: "it",
|
|
262
|
+
withinDescribe: "it"
|
|
263
|
+
}],
|
|
264
|
+
"vitest/no-hooks": "off",
|
|
265
|
+
"vitest/require-top-level-describe": "off",
|
|
266
|
+
"vitest/prefer-strict-boolean-matchers": "off",
|
|
267
|
+
"vitest/max-expects": "off",
|
|
268
|
+
"vitest/prefer-expect-assertions": "off",
|
|
269
|
+
"vitest/prefer-importing-vitest-globals": "off",
|
|
270
|
+
"vitest/no-importing-vitest-globals": "off",
|
|
271
|
+
"vitest/no-large-snapshots": "off",
|
|
272
|
+
"vitest/no-restricted-matchers": "off",
|
|
273
|
+
"vitest/no-restricted-vi-methods": "off"
|
|
274
|
+
},
|
|
275
|
+
files: ["*.test.ts", "*.spec.ts"]
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
files: [
|
|
279
|
+
"./scripts/**",
|
|
280
|
+
"./script/**",
|
|
281
|
+
"./*.ts",
|
|
282
|
+
"./*.js"
|
|
283
|
+
],
|
|
284
|
+
...cliOverride
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
rules: {
|
|
288
|
+
"import/no-default-export": "off",
|
|
289
|
+
"no-console": "error"
|
|
290
|
+
},
|
|
291
|
+
files: ["*.config.ts"]
|
|
292
|
+
}
|
|
293
|
+
]
|
|
294
|
+
};
|
|
295
|
+
//#endregion
|
|
296
|
+
//#region src/base/run.ts
|
|
297
|
+
const lintInput = ["!node_modules/.vp-config/info.json", "index.html"];
|
|
298
|
+
const runBase = {
|
|
299
|
+
tasks: {
|
|
300
|
+
cbuild: "vp build",
|
|
301
|
+
ccheck: {
|
|
302
|
+
command: "vp check",
|
|
303
|
+
input: lintInput
|
|
304
|
+
},
|
|
305
|
+
cfmt: "vp fmt",
|
|
306
|
+
cformat: "vp format",
|
|
307
|
+
clint: {
|
|
308
|
+
command: "vp lint",
|
|
309
|
+
input: lintInput
|
|
310
|
+
},
|
|
311
|
+
cpack: "vp pack",
|
|
312
|
+
ctest: "vp test"
|
|
313
|
+
},
|
|
314
|
+
cache: {
|
|
315
|
+
scripts: false,
|
|
316
|
+
tasks: true
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
//#endregion
|
|
320
|
+
//#region src/base/staged.ts
|
|
321
|
+
const stagedBase = { "*": "vp check --fix" };
|
|
322
|
+
//#endregion
|
|
323
|
+
//#region src/base/index.ts
|
|
324
|
+
const baseConfig = {
|
|
325
|
+
fmt: fmtBase,
|
|
326
|
+
lint: lintBase,
|
|
327
|
+
run: runBase,
|
|
328
|
+
staged: stagedBase
|
|
329
|
+
};
|
|
330
|
+
//#endregion
|
|
331
|
+
//#region src/website/fmt.ts
|
|
332
|
+
const fmtWebsite = {
|
|
333
|
+
...fmtBase,
|
|
334
|
+
jsxSingleQuote: true,
|
|
335
|
+
embeddedLanguageFormatting: "auto",
|
|
336
|
+
sortTailwindcss: false
|
|
337
|
+
};
|
|
338
|
+
//#endregion
|
|
339
|
+
//#region src/cli/index.ts
|
|
340
|
+
const cliConfig = {
|
|
341
|
+
fmt: fmtWebsite,
|
|
342
|
+
lint: mergeConfig(lintBase, mergeConfig(cliOverride, componentOverride)),
|
|
343
|
+
pack: {
|
|
344
|
+
dts: false,
|
|
345
|
+
minify: true,
|
|
346
|
+
platform: "node",
|
|
347
|
+
nodeProtocol: "strip"
|
|
348
|
+
},
|
|
349
|
+
run: runBase,
|
|
350
|
+
staged: stagedBase
|
|
351
|
+
};
|
|
352
|
+
//#endregion
|
|
353
|
+
//#region src/entry.ts
|
|
354
|
+
function createConfigEntry(presetConfig, category) {
|
|
355
|
+
const entry = ((config) => defineMergedConfig(presetConfig, config, category));
|
|
356
|
+
const only = (parts, ...args) => defineMergedConfig(pickPresetConfig(presetConfig, parts), args[0], category);
|
|
357
|
+
const exclude = (parts, ...args) => defineMergedConfig(omitPresetConfig(presetConfig, parts), args[0], category);
|
|
358
|
+
return Object.assign(entry, {
|
|
359
|
+
only,
|
|
360
|
+
exclude
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
function defineMergedConfig(presetConfig, config, category) {
|
|
364
|
+
if (typeof config === "function") return defineConfig(async (env) => {
|
|
365
|
+
return trackRuntimeInfo(mergePresetConfig(presetConfig, await config(env)), category);
|
|
366
|
+
});
|
|
367
|
+
if (config instanceof Promise) return defineConfig(config.then((userConfig) => trackRuntimeInfo(mergePresetConfig(presetConfig, userConfig), category)));
|
|
368
|
+
return defineConfig(trackRuntimeInfo(mergePresetConfig(presetConfig, config), category));
|
|
369
|
+
}
|
|
370
|
+
function pickPresetConfig(presetConfig, parts) {
|
|
371
|
+
return Object.fromEntries(parts.map((part) => [part, presetConfig[part]]));
|
|
4
372
|
}
|
|
373
|
+
function omitPresetConfig(presetConfig, parts) {
|
|
374
|
+
const excludedParts = new Set(parts);
|
|
375
|
+
return Object.fromEntries(Object.entries(presetConfig).filter(([part]) => !excludedParts.has(part)));
|
|
376
|
+
}
|
|
377
|
+
function mergePresetConfig(presetConfig, userConfig) {
|
|
378
|
+
const config = { ...userConfig };
|
|
379
|
+
if (presetConfig.fmt) config.fmt = mergeConfig(presetConfig.fmt, userConfig.fmt ?? {});
|
|
380
|
+
if (presetConfig.lint) config.lint = mergeLintConfig(presetConfig.lint, userConfig.lint);
|
|
381
|
+
if (presetConfig.pack) config.pack = mergePackConfig(presetConfig.pack, userConfig.pack);
|
|
382
|
+
if (presetConfig.run) config.run = mergeConfig(presetConfig.run, userConfig.run ?? {});
|
|
383
|
+
if (presetConfig.staged) config.staged = mergeStagedConfig(presetConfig.staged, userConfig.staged);
|
|
384
|
+
return config;
|
|
385
|
+
}
|
|
386
|
+
function trackRuntimeInfo(config, category) {
|
|
387
|
+
writeRuntimeInfo({
|
|
388
|
+
category,
|
|
389
|
+
config
|
|
390
|
+
});
|
|
391
|
+
return config;
|
|
392
|
+
}
|
|
393
|
+
function mergeLintConfig(presetLint, userLint) {
|
|
394
|
+
return mergeConfig(presetLint, userLint ?? {});
|
|
395
|
+
}
|
|
396
|
+
function mergePackConfig(presetPack, userPack) {
|
|
397
|
+
if (Array.isArray(userPack)) return userPack.map((packConfig) => mergeConfig(presetPack, packConfig));
|
|
398
|
+
return mergeConfig(presetPack, userPack ?? {});
|
|
399
|
+
}
|
|
400
|
+
function mergeStagedConfig(presetStaged, userStaged) {
|
|
401
|
+
if (!isStagedObjectConfig(presetStaged)) return userStaged ?? presetStaged;
|
|
402
|
+
if (userStaged && !isStagedObjectConfig(userStaged)) return userStaged;
|
|
403
|
+
return mergeConfig(presetStaged, userStaged ?? {});
|
|
404
|
+
}
|
|
405
|
+
function isStagedObjectConfig(config) {
|
|
406
|
+
return typeof config === "object";
|
|
407
|
+
}
|
|
408
|
+
//#endregion
|
|
409
|
+
//#region src/lib/index.ts
|
|
410
|
+
const libConfig = {
|
|
411
|
+
fmt: fmtBase,
|
|
412
|
+
lint: mergeConfig(lintBase, {}),
|
|
413
|
+
pack: {
|
|
414
|
+
fixedExtension: true,
|
|
415
|
+
exports: true,
|
|
416
|
+
dts: { tsgo: true }
|
|
417
|
+
},
|
|
418
|
+
run: runBase,
|
|
419
|
+
staged: stagedBase
|
|
420
|
+
};
|
|
421
|
+
//#endregion
|
|
422
|
+
//#region src/website/index.ts
|
|
423
|
+
const websiteConfig = {
|
|
424
|
+
fmt: fmtWebsite,
|
|
425
|
+
lint: mergeConfig(mergeConfig(lintBase, componentOverride), {
|
|
426
|
+
env: { browser: true },
|
|
427
|
+
rules: {
|
|
428
|
+
"prefer-dom-node-append": "warn",
|
|
429
|
+
"prefer-dom-node-dataset": "warn",
|
|
430
|
+
"unicorn/prefer-query-selector": "warn",
|
|
431
|
+
"prefer-dom-node-remove": "warn",
|
|
432
|
+
"prefer-blob-reading-methods": "warn",
|
|
433
|
+
"import/no-unassigned-import": ["error", { allow: ["**/*.css"] }]
|
|
434
|
+
}
|
|
435
|
+
}),
|
|
436
|
+
run: runBase,
|
|
437
|
+
staged: stagedBase
|
|
438
|
+
};
|
|
439
|
+
//#endregion
|
|
440
|
+
//#region src/index.ts
|
|
441
|
+
const base = createConfigEntry(baseConfig, "base");
|
|
442
|
+
const cli = createConfigEntry(cliConfig, "cli");
|
|
443
|
+
const lib = createConfigEntry(libConfig, "lib");
|
|
444
|
+
const website = createConfigEntry(websiteConfig, "website");
|
|
5
445
|
//#endregion
|
|
6
|
-
export {
|
|
446
|
+
export { base, cli, lib, website };
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, normalize } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
//#region src/oxlint-plugin/constants.ts
|
|
5
|
+
const packageName = "@liangmi/vp-config";
|
|
6
|
+
const pluginName = "liangmi";
|
|
7
|
+
const infoDirectoryName = ".vp-config";
|
|
8
|
+
const infoFileName = "info.json";
|
|
9
|
+
const projectConfigNames = [
|
|
10
|
+
"cli",
|
|
11
|
+
"lib",
|
|
12
|
+
"website"
|
|
13
|
+
];
|
|
14
|
+
const rootConfigNames = ["base"];
|
|
15
|
+
const configNames = [...rootConfigNames, ...projectConfigNames];
|
|
16
|
+
const viteConfigNames = [
|
|
17
|
+
"vite.config.ts",
|
|
18
|
+
"vite.config.mts",
|
|
19
|
+
"vite.config.cts",
|
|
20
|
+
"vite.config.js",
|
|
21
|
+
"vite.config.mjs",
|
|
22
|
+
"vite.config.cjs"
|
|
23
|
+
];
|
|
24
|
+
function isVpConfigEntrySpecifier(specifier) {
|
|
25
|
+
return specifier === "@liangmi/vp-config" || /(?:^|[\\/])src[\\/]index\.ts$/u.test(specifier) || specifier.endsWith("/src/index.ts");
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/oxlint-plugin/info.ts
|
|
29
|
+
function getInfoPath(configDirectory) {
|
|
30
|
+
return join(configDirectory, "node_modules", infoDirectoryName, infoFileName);
|
|
31
|
+
}
|
|
32
|
+
function readRuntimeInfo(configDirectory) {
|
|
33
|
+
const infoPath = getInfoPath(configDirectory);
|
|
34
|
+
if (!existsSync(infoPath)) return;
|
|
35
|
+
try {
|
|
36
|
+
const info = JSON.parse(readFileSync(infoPath, "utf8"));
|
|
37
|
+
return isRuntimeInfo(info) ? info : void 0;
|
|
38
|
+
} catch {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function writeRuntimeInfo(input) {
|
|
43
|
+
const { stack } = /* @__PURE__ */ new Error("Find vite config caller");
|
|
44
|
+
if (!shouldWriteRuntimeInfo(stack)) return;
|
|
45
|
+
const configFile = findConfigFileFromStack(stack) ?? findCwdConfigFile();
|
|
46
|
+
if (!configFile) return;
|
|
47
|
+
writeRuntimeInfoFile(configFile, input);
|
|
48
|
+
}
|
|
49
|
+
function ensureRuntimeInfo(configFile) {
|
|
50
|
+
const configDirectory = dirname(configFile);
|
|
51
|
+
if (!hasConfigPackageJson(configFile)) return;
|
|
52
|
+
loadRuntimeInfo(configFile);
|
|
53
|
+
return readRuntimeInfo(configDirectory);
|
|
54
|
+
}
|
|
55
|
+
function cleanupRuntimeInfo(configDirectory) {
|
|
56
|
+
const info = readRuntimeInfo(configDirectory);
|
|
57
|
+
const nodeModulesDirectory = join(configDirectory, "node_modules");
|
|
58
|
+
rmSync(getInfoPath(configDirectory), { force: true });
|
|
59
|
+
rmSync(join(nodeModulesDirectory, infoDirectoryName), {
|
|
60
|
+
recursive: true,
|
|
61
|
+
force: true
|
|
62
|
+
});
|
|
63
|
+
if (info?.cleanup.createdNodeModules) rmSync(nodeModulesDirectory, {
|
|
64
|
+
recursive: true,
|
|
65
|
+
force: true
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function writeRuntimeInfoFile(configFile, input) {
|
|
69
|
+
if (!hasConfigPackageJson(configFile)) return;
|
|
70
|
+
const configDirectory = dirname(configFile);
|
|
71
|
+
const createdNodeModules = !existsSync(join(configDirectory, "node_modules"));
|
|
72
|
+
const infoPath = getInfoPath(configDirectory);
|
|
73
|
+
rmSync(infoPath, { force: true });
|
|
74
|
+
mkdirSync(dirname(infoPath), { recursive: true });
|
|
75
|
+
writeFileSync(infoPath, `${JSON.stringify(createRuntimeInfo(configFile, input, createdNodeModules), null, 2)}\n`);
|
|
76
|
+
}
|
|
77
|
+
function shouldWriteRuntimeInfo(stack) {
|
|
78
|
+
return hasOxlintEntryStack(stack) || hasVitePlusResolveStack(stack);
|
|
79
|
+
}
|
|
80
|
+
function hasOxlintEntryStack(stack) {
|
|
81
|
+
return Boolean(stack && /node_modules[\\/](?:\.pnpm[\\/]oxlint@[^\\/]+[\\/]node_modules[\\/])?oxlint[\\/]dist[\\/]/u.test(stack));
|
|
82
|
+
}
|
|
83
|
+
function hasVitePlusResolveStack(stack) {
|
|
84
|
+
return Boolean(stack && /node_modules[\\/]vite-plus[\\/]dist[\\/]resolve-vite-config(?:-[^\\/]+)?\.js/u.test(stack));
|
|
85
|
+
}
|
|
86
|
+
function loadRuntimeInfo(configFile) {
|
|
87
|
+
const input = readRuntimeConfigInput(configFile);
|
|
88
|
+
if (input) writeRuntimeInfoFile(configFile, input);
|
|
89
|
+
}
|
|
90
|
+
function createRuntimeInfo(configFile, input, createdNodeModules) {
|
|
91
|
+
const configDirectory = dirname(configFile);
|
|
92
|
+
const configKeys = Object.keys(input.config);
|
|
93
|
+
const hasPack = configKeys.includes("pack");
|
|
94
|
+
const hasViteConfigFields = configKeys.some(isViteConfigField);
|
|
95
|
+
return {
|
|
96
|
+
version: 1,
|
|
97
|
+
configFile,
|
|
98
|
+
configDirectory,
|
|
99
|
+
category: input.category,
|
|
100
|
+
configKeys,
|
|
101
|
+
project: {
|
|
102
|
+
hasViteConfigFields,
|
|
103
|
+
hasPack
|
|
104
|
+
},
|
|
105
|
+
cleanup: { createdNodeModules }
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const vitePlusConfigKeys = new Set([
|
|
109
|
+
"create",
|
|
110
|
+
"fmt",
|
|
111
|
+
"lint",
|
|
112
|
+
"pack",
|
|
113
|
+
"run",
|
|
114
|
+
"staged",
|
|
115
|
+
"test"
|
|
116
|
+
]);
|
|
117
|
+
function isViteConfigField(key) {
|
|
118
|
+
return !vitePlusConfigKeys.has(key);
|
|
119
|
+
}
|
|
120
|
+
function findConfigFileFromStack(stack) {
|
|
121
|
+
if (!stack) return;
|
|
122
|
+
for (const rawLine of stack.split("\n")) {
|
|
123
|
+
const path = normalizeStackPath(rawLine);
|
|
124
|
+
const configPath = path ? resolveBundledConfigPath(path) : void 0;
|
|
125
|
+
if (configPath && viteConfigNames.includes(getPathBasename(configPath))) return configPath;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function getPathBasename(path) {
|
|
129
|
+
return path.split(/[\\/]/u).at(-1) ?? path;
|
|
130
|
+
}
|
|
131
|
+
function normalizeStackPath(line) {
|
|
132
|
+
const fileUrlMatch = /file:\/\/\/.*?vite\.config\.[cm]?[jt]s/u.exec(line);
|
|
133
|
+
if (fileUrlMatch) return fileURLToPath(fileUrlMatch[0]);
|
|
134
|
+
const absolutePathMatch = /((?:[a-zA-Z]:[\\/]|\/)[^:)]+vite\.config\.[cm]?[jt]s)/u.exec(line);
|
|
135
|
+
return absolutePathMatch ? normalize(absolutePathMatch[1]) : void 0;
|
|
136
|
+
}
|
|
137
|
+
function resolveBundledConfigPath(path) {
|
|
138
|
+
const bundledConfigMatch = /^(.*)([\\/])node_modules[\\/]\.vite-temp[\\/](vite\.config\.[cm]?[jt]s)$/u.exec(path);
|
|
139
|
+
return bundledConfigMatch ? `${bundledConfigMatch[1]}${bundledConfigMatch[2]}${bundledConfigMatch[3]}` : path;
|
|
140
|
+
}
|
|
141
|
+
function readRuntimeConfigInput(configFile) {
|
|
142
|
+
try {
|
|
143
|
+
return parseRuntimeConfigInput(readFileSync(configFile, "utf8"));
|
|
144
|
+
} catch {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function parseRuntimeConfigInput(source) {
|
|
149
|
+
const imports = getConfigImports(source);
|
|
150
|
+
for (const [localName, category] of imports.named) {
|
|
151
|
+
const input = findConfigCallInput(source, localName, category);
|
|
152
|
+
if (input) return input;
|
|
153
|
+
}
|
|
154
|
+
for (const namespace of imports.namespaces) for (const category of configNames) {
|
|
155
|
+
const input = findConfigCallInput(source, `${namespace}.${category}`, category);
|
|
156
|
+
if (input) return input;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function getConfigImports(source) {
|
|
160
|
+
const named = /* @__PURE__ */ new Map();
|
|
161
|
+
const namespaces = /* @__PURE__ */ new Set();
|
|
162
|
+
for (const match of source.matchAll(/import\s*\{(?<imports>[^}]+)\}\s*from\s*['"](?<specifier>[^'"]+)['"]/gu)) {
|
|
163
|
+
if (!isConfigEntrySpecifier(match.groups?.specifier)) continue;
|
|
164
|
+
for (const specifier of splitSimpleList(match.groups?.imports ?? "")) {
|
|
165
|
+
const [importedName, localName] = specifier.split(/\s+as\s+/u).map((part) => part.trim());
|
|
166
|
+
const category = toConfigName(importedName);
|
|
167
|
+
if (category) named.set(localName || importedName, category);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const match of source.matchAll(/import\s*\*\s*as\s*(?<localName>[$A-Z_a-z][$\w]*)\s*from\s*['"](?<specifier>[^'"]+)['"]/gu)) if (isConfigEntrySpecifier(match.groups?.specifier) && match.groups?.localName) namespaces.add(match.groups.localName);
|
|
171
|
+
for (const match of source.matchAll(/const\s*\{(?<imports>[^}]+)\}\s*=\s*require\(\s*['"](?<specifier>[^'"]+)['"]\s*\)/gu)) {
|
|
172
|
+
if (!isConfigEntrySpecifier(match.groups?.specifier)) continue;
|
|
173
|
+
for (const specifier of splitSimpleList(match.groups?.imports ?? "")) {
|
|
174
|
+
const [importedName, localName] = specifier.split(/\s*:\s*/u).map((part) => part.trim());
|
|
175
|
+
const category = toConfigName(importedName);
|
|
176
|
+
if (category) named.set(localName || importedName, category);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
named,
|
|
181
|
+
namespaces
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function findConfigCallInput(source, callee, category) {
|
|
185
|
+
const callPattern = new RegExp(`(^|[^$\\w])${callee.split(".").map((part) => escapeRegExp(part)).join(String.raw`\s*\.\s*`)}\\s*(?:\\.\\s*(?<method>only|exclude)\\s*)?\\(`, "gu");
|
|
186
|
+
for (const match of source.matchAll(callPattern)) {
|
|
187
|
+
const argument = readCallArgument(source, match.index + match[0].lastIndexOf("("), match.groups?.method ? 1 : 0);
|
|
188
|
+
const configKeys = argument ? readObjectKeys(argument) : [];
|
|
189
|
+
if (configKeys.length > 0 || argument?.trim() === "{}") return {
|
|
190
|
+
category,
|
|
191
|
+
config: Object.fromEntries(configKeys.map((key) => [key, true]))
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function readCallArgument(source, openParenIndex, argumentIndex) {
|
|
196
|
+
const closeParenIndex = findClosingDelimiter(source, openParenIndex, "(", ")");
|
|
197
|
+
if (closeParenIndex === void 0) return;
|
|
198
|
+
return splitTopLevel(source.slice(openParenIndex + 1, closeParenIndex))[argumentIndex];
|
|
199
|
+
}
|
|
200
|
+
function readObjectKeys(source) {
|
|
201
|
+
const objectStart = source.search(/\S/u);
|
|
202
|
+
if (objectStart === -1 || source[objectStart] !== "{") return [];
|
|
203
|
+
const objectEnd = findClosingDelimiter(source, objectStart, "{", "}");
|
|
204
|
+
if (objectEnd === void 0) return [];
|
|
205
|
+
return splitTopLevel(source.slice(objectStart + 1, objectEnd)).flatMap(readPropertyKey);
|
|
206
|
+
}
|
|
207
|
+
function readPropertyKey(source) {
|
|
208
|
+
const property = source.trim();
|
|
209
|
+
if (!property || property.startsWith("...") || property.startsWith("[")) return [];
|
|
210
|
+
const quotedKeyMatch = /^['"](?<key>[^'"]+)['"]\s*[:(]/u.exec(property);
|
|
211
|
+
if (quotedKeyMatch?.groups?.key) return [quotedKeyMatch.groups.key];
|
|
212
|
+
const identifierKeyMatch = /^(?:async\s+|get\s+|set\s+)?(?<key>[$A-Z_a-z][$\w]*)\s*(?::|\(|$)/u.exec(property);
|
|
213
|
+
return identifierKeyMatch?.groups?.key ? [identifierKeyMatch.groups.key] : [];
|
|
214
|
+
}
|
|
215
|
+
function splitTopLevel(source) {
|
|
216
|
+
const parts = [];
|
|
217
|
+
let start = 0;
|
|
218
|
+
let depth = 0;
|
|
219
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
220
|
+
const skippedIndex = skipSyntax(source, index);
|
|
221
|
+
if (skippedIndex !== index) {
|
|
222
|
+
index = skippedIndex;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const char = source[index];
|
|
226
|
+
if (char === "(" || char === "[" || char === "{") depth += 1;
|
|
227
|
+
else if (char === ")" || char === "]" || char === "}") depth -= 1;
|
|
228
|
+
else if (char === "," && depth === 0) {
|
|
229
|
+
parts.push(source.slice(start, index).trim());
|
|
230
|
+
start = index + 1;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
parts.push(source.slice(start).trim());
|
|
234
|
+
return parts.filter(Boolean);
|
|
235
|
+
}
|
|
236
|
+
function findClosingDelimiter(source, openIndex, openDelimiter, closeDelimiter) {
|
|
237
|
+
let depth = 0;
|
|
238
|
+
for (let index = openIndex; index < source.length; index += 1) {
|
|
239
|
+
const skippedIndex = skipSyntax(source, index);
|
|
240
|
+
if (skippedIndex !== index) {
|
|
241
|
+
index = skippedIndex;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (source[index] === openDelimiter) depth += 1;
|
|
245
|
+
else if (source[index] === closeDelimiter) {
|
|
246
|
+
depth -= 1;
|
|
247
|
+
if (depth === 0) return index;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function skipSyntax(source, index) {
|
|
252
|
+
const char = source[index];
|
|
253
|
+
const nextChar = source[index + 1];
|
|
254
|
+
if (char === "\"" || char === "'" || char === "`") return skipString(source, index, char);
|
|
255
|
+
if (char === "/" && nextChar === "/") return skipLineComment(source, index);
|
|
256
|
+
if (char === "/" && nextChar === "*") return skipBlockComment(source, index);
|
|
257
|
+
return index;
|
|
258
|
+
}
|
|
259
|
+
function skipString(source, start, quote) {
|
|
260
|
+
for (let index = start + 1; index < source.length; index += 1) if (source[index] === "\\") index += 1;
|
|
261
|
+
else if (source[index] === quote) return index;
|
|
262
|
+
return source.length - 1;
|
|
263
|
+
}
|
|
264
|
+
function skipLineComment(source, start) {
|
|
265
|
+
const end = source.indexOf("\n", start + 2);
|
|
266
|
+
return end === -1 ? source.length - 1 : end;
|
|
267
|
+
}
|
|
268
|
+
function skipBlockComment(source, start) {
|
|
269
|
+
const end = source.indexOf("*/", start + 2);
|
|
270
|
+
return end === -1 ? source.length - 1 : end + 1;
|
|
271
|
+
}
|
|
272
|
+
function splitSimpleList(source) {
|
|
273
|
+
return source.split(",").map((part) => part.trim()).filter(Boolean);
|
|
274
|
+
}
|
|
275
|
+
function isConfigEntrySpecifier(specifier) {
|
|
276
|
+
return specifier ? isVpConfigEntrySpecifier(specifier) : false;
|
|
277
|
+
}
|
|
278
|
+
function toConfigName(name) {
|
|
279
|
+
return configNames.includes(name) ? name : void 0;
|
|
280
|
+
}
|
|
281
|
+
function escapeRegExp(value) {
|
|
282
|
+
return value.replaceAll(/[.*+?^${}()|[\]\\]/gu, String.raw`\$&`);
|
|
283
|
+
}
|
|
284
|
+
function findCwdConfigFile() {
|
|
285
|
+
for (const configName of viteConfigNames) {
|
|
286
|
+
const configFile = join(process.cwd(), configName);
|
|
287
|
+
if (existsSync(configFile)) return configFile;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function hasConfigPackageJson(configFile) {
|
|
291
|
+
return existsSync(join(dirname(configFile), "package.json"));
|
|
292
|
+
}
|
|
293
|
+
function isRuntimeInfo(value) {
|
|
294
|
+
return typeof value === "object" && value !== null && "version" in value && value.version === 1 && "project" in value && typeof value.project === "object" && value.project !== null;
|
|
295
|
+
}
|
|
296
|
+
function isWebsiteProject(info) {
|
|
297
|
+
return existsSync(join(info.configDirectory, "index.html")) || info.project.hasViteConfigFields;
|
|
298
|
+
}
|
|
299
|
+
function isLibProject(info) {
|
|
300
|
+
return info.project.hasPack;
|
|
301
|
+
}
|
|
302
|
+
function isProject(info) {
|
|
303
|
+
return isWebsiteProject(info) || isLibProject(info);
|
|
304
|
+
}
|
|
305
|
+
//#endregion
|
|
306
|
+
export { isProject as a, writeRuntimeInfo as c, projectConfigNames as d, rootConfigNames as f, isLibProject as i, packageName as l, ensureRuntimeInfo as n, isWebsiteProject as o, viteConfigNames as p, getInfoPath as r, readRuntimeInfo as s, cleanupRuntimeInfo as t, pluginName as u };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { t as ConfigName } from "../constants-D9DskZO3.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/oxlint-plugin/project.d.ts
|
|
4
|
+
declare function getAllowedConfigNames(filename: string): readonly ConfigName[];
|
|
5
|
+
declare function isVpConfigImportAllowed(filename: string, importedNames: readonly string[]): boolean;
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/oxlint-plugin/info.d.ts
|
|
8
|
+
interface VpConfigRuntimeInfo {
|
|
9
|
+
version: 1;
|
|
10
|
+
configFile: string;
|
|
11
|
+
configDirectory: string;
|
|
12
|
+
category?: ConfigName;
|
|
13
|
+
configKeys: string[];
|
|
14
|
+
project: {
|
|
15
|
+
hasViteConfigFields: boolean;
|
|
16
|
+
hasPack: boolean;
|
|
17
|
+
};
|
|
18
|
+
cleanup: {
|
|
19
|
+
createdNodeModules: boolean;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
interface RuntimeConfigInput {
|
|
23
|
+
category?: ConfigName;
|
|
24
|
+
config: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
declare function getInfoPath(configDirectory: string): string;
|
|
27
|
+
declare function readRuntimeInfo(configDirectory: string): VpConfigRuntimeInfo | undefined;
|
|
28
|
+
declare function writeRuntimeInfo(input: RuntimeConfigInput): void;
|
|
29
|
+
declare function cleanupRuntimeInfo(configDirectory: string): void;
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/oxlint-plugin/index.d.ts
|
|
32
|
+
declare const _default: import("@oxlint/plugins").Plugin;
|
|
33
|
+
//#endregion
|
|
34
|
+
export { cleanupRuntimeInfo, _default as default, getAllowedConfigNames, getInfoPath, isVpConfigImportAllowed, readRuntimeInfo, writeRuntimeInfo };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { a as isProject, c as writeRuntimeInfo, d as projectConfigNames, f as rootConfigNames, i as isLibProject, l as packageName, n as ensureRuntimeInfo, o as isWebsiteProject, p as viteConfigNames, r as getInfoPath, s as readRuntimeInfo, t as cleanupRuntimeInfo, u as pluginName } from "../info-CHtwxjhy.mjs";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import { definePlugin, defineRule } from "@oxlint/plugins";
|
|
5
|
+
//#region src/oxlint-plugin/ast.ts
|
|
6
|
+
function isVpConfigSpecifier(specifier) {
|
|
7
|
+
return specifier === packageName;
|
|
8
|
+
}
|
|
9
|
+
function isStringLiteral(node) {
|
|
10
|
+
return node.type === "Literal" && typeof node.value === "string";
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/oxlint-plugin/project.ts
|
|
14
|
+
function getAllowedConfigNames(filename) {
|
|
15
|
+
if (!isViteConfigFile(filename)) return [];
|
|
16
|
+
const info = readRuntimeInfo(dirname(filename));
|
|
17
|
+
return (info ? isProject(info) : isStaticWebsiteProjectDirectory(dirname(filename))) ? projectConfigNames : rootConfigNames;
|
|
18
|
+
}
|
|
19
|
+
function isVpConfigImportAllowed(filename, importedNames) {
|
|
20
|
+
const allowedNames = getAllowedConfigNames(filename);
|
|
21
|
+
const allowedNameSet = new Set(allowedNames);
|
|
22
|
+
return allowedNames.length > 0 && importedNames.length > 0 && importedNames.every((name) => allowedNameSet.has(name));
|
|
23
|
+
}
|
|
24
|
+
function isViteConfigFile(filename) {
|
|
25
|
+
return viteConfigNames.includes(basename(filename));
|
|
26
|
+
}
|
|
27
|
+
function hasPackageJson(directory) {
|
|
28
|
+
return existsSync(join(directory, "package.json"));
|
|
29
|
+
}
|
|
30
|
+
function isStaticWebsiteProjectDirectory(directory) {
|
|
31
|
+
return existsSync(join(directory, "index.html"));
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/oxlint-plugin/rules.ts
|
|
35
|
+
function canUseRuntimeInfo(filename) {
|
|
36
|
+
return isViteConfigFile(filename) && hasPackageJson(dirname(filename));
|
|
37
|
+
}
|
|
38
|
+
const rules = {
|
|
39
|
+
"no-orphan-vite-config": defineRule({
|
|
40
|
+
meta: {
|
|
41
|
+
type: "problem",
|
|
42
|
+
docs: {
|
|
43
|
+
description: "Require vite.config files to live next to package.json.",
|
|
44
|
+
recommended: true
|
|
45
|
+
},
|
|
46
|
+
messages: { orphan: "Keep vite.config.ts next to package.json." }
|
|
47
|
+
},
|
|
48
|
+
create(context) {
|
|
49
|
+
return { Program(node) {
|
|
50
|
+
if (isViteConfigFile(context.filename) && !hasPackageJson(dirname(context.filename))) context.report({
|
|
51
|
+
node,
|
|
52
|
+
messageId: "orphan"
|
|
53
|
+
});
|
|
54
|
+
} };
|
|
55
|
+
}
|
|
56
|
+
}),
|
|
57
|
+
"no-useless-vp-preset-imports": defineRule({
|
|
58
|
+
meta: {
|
|
59
|
+
type: "problem",
|
|
60
|
+
docs: {
|
|
61
|
+
description: `Disallow importing ${packageName} outside vite.config files.`,
|
|
62
|
+
recommended: true
|
|
63
|
+
},
|
|
64
|
+
messages: { outsideConfig: `Import ${packageName} only from vite.config.ts.` }
|
|
65
|
+
},
|
|
66
|
+
create(context) {
|
|
67
|
+
const reportModuleReference = (node, specifier) => {
|
|
68
|
+
if (!isVpConfigSpecifier(specifier) || isViteConfigFile(context.filename)) return;
|
|
69
|
+
context.report({
|
|
70
|
+
node,
|
|
71
|
+
messageId: "outsideConfig"
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
ImportDeclaration(node) {
|
|
76
|
+
reportModuleReference(node.source, node.source.value);
|
|
77
|
+
},
|
|
78
|
+
ExportAllDeclaration(node) {
|
|
79
|
+
reportModuleReference(node.source, node.source.value);
|
|
80
|
+
},
|
|
81
|
+
ExportNamedDeclaration(node) {
|
|
82
|
+
if (node.source) reportModuleReference(node.source, node.source.value);
|
|
83
|
+
},
|
|
84
|
+
ImportExpression(node) {
|
|
85
|
+
if (isStringLiteral(node.source)) reportModuleReference(node.source, node.source.value);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}),
|
|
90
|
+
"use-preset-vp-config": defineRule({
|
|
91
|
+
meta: {
|
|
92
|
+
type: "problem",
|
|
93
|
+
docs: {
|
|
94
|
+
description: "Require vite.config.ts to be loaded through @liangmi/vp-config.",
|
|
95
|
+
recommended: true
|
|
96
|
+
},
|
|
97
|
+
messages: { missingRuntimeInfo: `Load vite.config.ts through ${packageName} so lint rules can inspect runtime config.` }
|
|
98
|
+
},
|
|
99
|
+
create(context) {
|
|
100
|
+
return { Program(node) {
|
|
101
|
+
if (!canUseRuntimeInfo(context.filename)) return;
|
|
102
|
+
if (!ensureRuntimeInfo(context.filename)) context.report({
|
|
103
|
+
node,
|
|
104
|
+
messageId: "missingRuntimeInfo"
|
|
105
|
+
});
|
|
106
|
+
} };
|
|
107
|
+
}
|
|
108
|
+
}),
|
|
109
|
+
"load-proper-vp-config-category": defineRule({
|
|
110
|
+
meta: {
|
|
111
|
+
type: "problem",
|
|
112
|
+
docs: {
|
|
113
|
+
description: `Load ${packageName} with a category that matches the package role.`,
|
|
114
|
+
recommended: true
|
|
115
|
+
},
|
|
116
|
+
messages: { wrongCategory: `Use {{expected}} from ${packageName} in this vite.config.ts.` }
|
|
117
|
+
},
|
|
118
|
+
create(context) {
|
|
119
|
+
return { Program(node) {
|
|
120
|
+
if (!canUseRuntimeInfo(context.filename)) return;
|
|
121
|
+
const info = ensureRuntimeInfo(context.filename);
|
|
122
|
+
if (!info?.category) return;
|
|
123
|
+
const allowed = isProject(info) ? projectConfigNames : rootConfigNames;
|
|
124
|
+
if (allowed.includes(info.category)) return;
|
|
125
|
+
context.report({
|
|
126
|
+
node,
|
|
127
|
+
messageId: "wrongCategory",
|
|
128
|
+
data: { expected: allowed.map((name) => `{ ${name} }`).join(" or ") }
|
|
129
|
+
});
|
|
130
|
+
} };
|
|
131
|
+
}
|
|
132
|
+
}),
|
|
133
|
+
"no-mixed-project": defineRule({
|
|
134
|
+
meta: {
|
|
135
|
+
type: "problem",
|
|
136
|
+
docs: {
|
|
137
|
+
description: "Disallow a vite config that is both website and library project.",
|
|
138
|
+
recommended: true
|
|
139
|
+
},
|
|
140
|
+
messages: { mixed: "Do not mix website and library project signals in one vite.config.ts." }
|
|
141
|
+
},
|
|
142
|
+
create(context) {
|
|
143
|
+
return { Program(node) {
|
|
144
|
+
if (!canUseRuntimeInfo(context.filename)) return;
|
|
145
|
+
const info = ensureRuntimeInfo(context.filename);
|
|
146
|
+
if (info && isWebsiteProject(info) && isLibProject(info)) context.report({
|
|
147
|
+
node,
|
|
148
|
+
messageId: "mixed"
|
|
149
|
+
});
|
|
150
|
+
} };
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
};
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/oxlint-plugin/index.ts
|
|
156
|
+
var oxlint_plugin_default = definePlugin({
|
|
157
|
+
meta: { name: pluginName },
|
|
158
|
+
rules
|
|
159
|
+
});
|
|
160
|
+
//#endregion
|
|
161
|
+
export { cleanupRuntimeInfo, oxlint_plugin_default as default, getAllowedConfigNames, getInfoPath, isVpConfigImportAllowed, readRuntimeInfo, writeRuntimeInfo };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liangmi/vp-config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Liang's JavaScript development toolchain config with Vite+.",
|
|
5
5
|
"homepage": "https://github.com/liangmiQwQ/vp-config#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -18,22 +18,28 @@
|
|
|
18
18
|
"type": "module",
|
|
19
19
|
"exports": {
|
|
20
20
|
".": "./dist/index.mjs",
|
|
21
|
+
"./oxlint-plugin": "./dist/oxlint-plugin/index.mjs",
|
|
21
22
|
"./package.json": "./package.json"
|
|
22
23
|
},
|
|
23
24
|
"publishConfig": {
|
|
24
25
|
"access": "public"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
27
|
-
"@types/node": "^
|
|
28
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
28
|
+
"@types/node": "^26.0.0",
|
|
29
|
+
"@typescript/native-preview": "7.0.0-dev.20260620.1",
|
|
29
30
|
"bumpp": "^11.1.0",
|
|
30
31
|
"typescript": "^6.0.3",
|
|
31
|
-
"vite-plus": "
|
|
32
|
+
"vite-plus": "^0.2.1"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@oxlint/plugins": "1.61.0",
|
|
36
|
+
"vite-plus": ">=0.2.0"
|
|
32
37
|
},
|
|
33
38
|
"scripts": {
|
|
34
|
-
"build": "vp
|
|
39
|
+
"build": "vp run cpack",
|
|
40
|
+
"check": "vp run cpack && vp run ccheck",
|
|
35
41
|
"dev": "vp pack --watch",
|
|
36
|
-
"
|
|
37
|
-
"
|
|
42
|
+
"release": "bumpp",
|
|
43
|
+
"test": "vp run ctest"
|
|
38
44
|
}
|
|
39
45
|
}
|