@archpublicwebsite/eslint-config 1.0.22 → 1.0.30
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 +75 -3
- package/lint-staged.config.mjs +3 -1
- package/package.json +1 -1
- package/tools/git-hooks/pre-commit.mjs +91 -1
- package/tools/git-hooks/pre-push.mjs +74 -0
- package/tools/security/scanner.mjs +22 -1
- package/tools/setup/install.mjs +80 -0
- package/tools/setup/vscode.mjs +9 -3
package/README.md
CHANGED
|
@@ -2,6 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
Reusable ESLint flat config and git-hook toolkit for Archipelago projects.
|
|
4
4
|
|
|
5
|
+
## Last Month Updates (May-Jun 2026)
|
|
6
|
+
|
|
7
|
+
Here are the most important recent changes:
|
|
8
|
+
|
|
9
|
+
- The installer can now create key setup files automatically when they are missing.
|
|
10
|
+
- The `pre-commit` hook is now more resilient when project config is incomplete.
|
|
11
|
+
- The security scanner is more accurate and produces fewer false positives.
|
|
12
|
+
- A strict account/signature validation mode is available via `REQUIRE_VERIFIED_ACCOUNT=1`.
|
|
13
|
+
|
|
14
|
+
When `REQUIRE_VERIFIED_ACCOUNT=1` is enabled:
|
|
15
|
+
|
|
16
|
+
- `pre-commit` checks `user.name`, `user.email`, and `commit.gpgsign=true`.
|
|
17
|
+
- `pre-push` rejects commits whose signature status is not `G`.
|
|
18
|
+
|
|
19
|
+
Quick start:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm install
|
|
23
|
+
node node_modules/@archpublicwebsite/eslint-config/tools/setup/install.mjs
|
|
24
|
+
pnpm lint:check
|
|
25
|
+
pnpm typecheck
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Enable strict verification only when needed:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
REQUIRE_VERIFIED_ACCOUNT=1 git commit
|
|
32
|
+
REQUIRE_VERIFIED_ACCOUNT=1 git push
|
|
33
|
+
```
|
|
34
|
+
|
|
5
35
|
## What this package includes
|
|
6
36
|
|
|
7
37
|
This package ships a ready-to-use flat config plus setup scripts for local project automation:
|
|
@@ -82,9 +112,14 @@ The setup merges ESLint-related settings into `.vscode/settings.json`:
|
|
|
82
112
|
{
|
|
83
113
|
"eslint.useFlatConfig": true,
|
|
84
114
|
"eslint.validate": [
|
|
85
|
-
"javascript",
|
|
86
|
-
"
|
|
87
|
-
"
|
|
115
|
+
"javascript",
|
|
116
|
+
"javascriptreact",
|
|
117
|
+
"typescript",
|
|
118
|
+
"typescriptreact",
|
|
119
|
+
"vue",
|
|
120
|
+
"json",
|
|
121
|
+
"jsonc",
|
|
122
|
+
"markdown"
|
|
88
123
|
],
|
|
89
124
|
"editor.codeActionsOnSave": {
|
|
90
125
|
"source.fixAll.eslint": "explicit",
|
|
@@ -96,6 +131,43 @@ The setup merges ESLint-related settings into `.vscode/settings.json`:
|
|
|
96
131
|
|
|
97
132
|
Existing settings are preserved. Only the ESLint-related keys are added or updated.
|
|
98
133
|
|
|
134
|
+
## Troubleshooting (Nuxt)
|
|
135
|
+
|
|
136
|
+
If you see errors like:
|
|
137
|
+
|
|
138
|
+
- `Cannot find module '~/composables'`
|
|
139
|
+
- `Cannot find name 'useRoute'`
|
|
140
|
+
- `Cannot find name 'useRuntimeConfig'`
|
|
141
|
+
- `'<script setup ...>' expected` in `.vue` files
|
|
142
|
+
|
|
143
|
+
Use this checklist:
|
|
144
|
+
|
|
145
|
+
1. Ensure root `tsconfig.json` extends Nuxt types:
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"extends": "./.nuxt/tsconfig.json"
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
2. Generate Nuxt type files:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npx nuxi prepare
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
3. Confirm VS Code uses Volar for Vue files (disable Vetur).
|
|
160
|
+
4. Re-run setup script:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
node node_modules/@archpublicwebsite/eslint-config/tools/setup/install.mjs
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Note:
|
|
167
|
+
|
|
168
|
+
- The setup now writes workspace settings that disable Vetur validators to prevent false Vue/TS errors.
|
|
169
|
+
- After running setup, restart VS Code (`Developer: Reload Window`) so the language server state is refreshed.
|
|
170
|
+
|
|
99
171
|
## Public API
|
|
100
172
|
|
|
101
173
|
The package exports:
|
package/lint-staged.config.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const config = {
|
|
2
|
-
|
|
2
|
+
// Keep JS/TS/Vue formatting and lint fixes in one engine (ESLint)
|
|
3
|
+
// so save behavior and pre-commit output stay consistent.
|
|
4
|
+
'*.{js,mjs,cjs,ts,tsx,vue}': ['eslint --fix'],
|
|
3
5
|
'*.{json,md,yml,yaml,css,scss,html}': ['prettier --write'],
|
|
4
6
|
}
|
|
5
7
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { dirname, join } from 'node:path'
|
|
2
2
|
import { fileURLToPath } from 'node:url'
|
|
3
3
|
import { existsSync } from 'node:fs'
|
|
4
|
-
import { hasCommand, run } from './shared.mjs'
|
|
4
|
+
import { hasCommand, run, runSafe } from './shared.mjs'
|
|
5
5
|
|
|
6
6
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
7
7
|
const SCAN_SCRIPT = join(__dirname, '..', 'security', 'scan.mjs')
|
|
@@ -15,11 +15,96 @@ function runIfExists(scriptPath, command) {
|
|
|
15
15
|
run(command, { stdio: 'inherit' })
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function quoteShellArg(value) {
|
|
19
|
+
const escaped = String(value).replaceAll("'", '\'"\'"\'')
|
|
20
|
+
return `'${escaped}'`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getStagedLintTargets() {
|
|
24
|
+
const output = runSafe('git diff --cached --name-only --diff-filter=ACMR')
|
|
25
|
+
if (!output) {
|
|
26
|
+
return []
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return output
|
|
30
|
+
.split('\n')
|
|
31
|
+
.map(line => line.trim())
|
|
32
|
+
.filter(Boolean)
|
|
33
|
+
// Keep this check focused on app/source code files that should match
|
|
34
|
+
// editor save behavior. Tooling files are validated by lint-staged itself.
|
|
35
|
+
.filter(filePath => /\.(js|ts|tsx|vue)$/.test(filePath))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function ensureEslintFlatConfig() {
|
|
39
|
+
const hasFlatConfig =
|
|
40
|
+
existsSync('./eslint.config.mjs') || existsSync('./eslint.config.js') || existsSync('./eslint.config.cjs')
|
|
41
|
+
|
|
42
|
+
if (hasFlatConfig) {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const installerPath = join(__dirname, '..', 'setup', 'install.mjs')
|
|
47
|
+
console.log('\nMissing eslint.config.* detected. Running setup installer to auto-create required config...')
|
|
48
|
+
run(`node "${installerPath}"`, { stdio: 'inherit' })
|
|
49
|
+
|
|
50
|
+
const configCreated =
|
|
51
|
+
existsSync('./eslint.config.mjs') || existsSync('./eslint.config.js') || existsSync('./eslint.config.cjs')
|
|
52
|
+
|
|
53
|
+
if (!configCreated) {
|
|
54
|
+
console.error('\nCOMMIT FAILED: eslint.config.* is still missing after setup.')
|
|
55
|
+
console.error('Run: node node_modules/@archpublicwebsite/eslint-config/tools/setup/install.mjs')
|
|
56
|
+
process.exit(1)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function verifySaveEquivalentLintState() {
|
|
61
|
+
const lintTargets = getStagedLintTargets()
|
|
62
|
+
if (lintTargets.length === 0) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const targetArgs = lintTargets.map(quoteShellArg).join(' ')
|
|
67
|
+
console.log('\nVerifying staged code with ESLint check...')
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
run(`pnpm exec eslint --no-error-on-unmatched-pattern ${targetArgs}`, { stdio: 'inherit' })
|
|
71
|
+
} catch {
|
|
72
|
+
console.error('\nCOMMIT FAILED: staged code still has ESLint issues after auto-fix.')
|
|
73
|
+
console.error('Fix the remaining issues, stage again, then commit.')
|
|
74
|
+
process.exit(1)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
18
78
|
if (!hasCommand('pnpm')) {
|
|
19
79
|
console.error('\nCOMMIT FAILED: pnpm is required but not found.\n')
|
|
20
80
|
process.exit(1)
|
|
21
81
|
}
|
|
22
82
|
|
|
83
|
+
function enforceVerifiedAccountIfEnabled() {
|
|
84
|
+
if (process.env.REQUIRE_VERIFIED_ACCOUNT !== '1') {
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const userEmail = runSafe('git config --get user.email')
|
|
89
|
+
const userName = runSafe('git config --get user.name')
|
|
90
|
+
const commitSign = runSafe('git config --get commit.gpgsign').toLowerCase()
|
|
91
|
+
|
|
92
|
+
if (!userEmail || !userName) {
|
|
93
|
+
console.error('\nCOMMIT FAILED: REQUIRE_VERIFIED_ACCOUNT=1 but git user identity is incomplete.\n')
|
|
94
|
+
console.error('Set both user.name and user.email first.')
|
|
95
|
+
process.exit(1)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (commitSign !== 'true') {
|
|
99
|
+
console.error('\nCOMMIT FAILED: REQUIRE_VERIFIED_ACCOUNT=1 but commit signing is disabled.\n')
|
|
100
|
+
console.error('Run: git config commit.gpgsign true')
|
|
101
|
+
process.exit(1)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── 0. Optional strict verified-account gate ─────────────────────────────────
|
|
106
|
+
enforceVerifiedAccountIfEnabled()
|
|
107
|
+
|
|
23
108
|
// ── 1. Security scan — blocks on critical/high findings ───────────────────────
|
|
24
109
|
run(`node "${SCAN_SCRIPT}"`, { stdio: 'inherit' })
|
|
25
110
|
|
|
@@ -33,6 +118,8 @@ if (process.env.SKIP_GLOBAL_SCAN === '1') {
|
|
|
33
118
|
}
|
|
34
119
|
|
|
35
120
|
// ── 3. Lint-staged — auto-fix & format staged files ───────────────────────────
|
|
121
|
+
ensureEslintFlatConfig()
|
|
122
|
+
|
|
36
123
|
console.log('\nRunning lint-staged (auto-fix staged files)...')
|
|
37
124
|
if (existsSync('./lint-staged.config.mjs')) {
|
|
38
125
|
run('pnpm exec lint-staged --config lint-staged.config.mjs', { stdio: 'inherit' })
|
|
@@ -41,4 +128,7 @@ if (existsSync('./lint-staged.config.mjs')) {
|
|
|
41
128
|
run('pnpm exec lint-staged', { stdio: 'inherit' })
|
|
42
129
|
}
|
|
43
130
|
|
|
131
|
+
// ── 4. Save-equivalent verification (same lint result as editor save) ────────
|
|
132
|
+
verifySaveEquivalentLintState()
|
|
133
|
+
|
|
44
134
|
console.log('\nCOMMIT CHECKS PASSED\n')
|
|
@@ -90,6 +90,76 @@ function collectPushFiles(refs) {
|
|
|
90
90
|
return [...seen]
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Collect commits in push range(s).
|
|
95
|
+
* @param {{ localSha: string, remoteSha: string }[]} refs
|
|
96
|
+
* @returns {string[]}
|
|
97
|
+
*/
|
|
98
|
+
function collectPushCommits(refs) {
|
|
99
|
+
const seen = new Set()
|
|
100
|
+
for (const { localSha, remoteSha } of refs) {
|
|
101
|
+
const range = remoteSha === ZERO_SHA ? localSha : `${remoteSha}..${localSha}`
|
|
102
|
+
const output = runSafe(`git rev-list ${range}`)
|
|
103
|
+
if (!output) continue
|
|
104
|
+
for (const sha of output.split('\n').filter(Boolean)) {
|
|
105
|
+
seen.add(sha)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return [...seen]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Enforce signed + verified commits in push range when REQUIRE_VERIFIED_ACCOUNT=1.
|
|
113
|
+
* Git signature status reference (%G?):
|
|
114
|
+
* G = good signature, N = no signature, B = bad signature, etc.
|
|
115
|
+
* @param {string[]} commits
|
|
116
|
+
*/
|
|
117
|
+
function enforceVerifiedAccountIfEnabled(commits) {
|
|
118
|
+
if (process.env.REQUIRE_VERIFIED_ACCOUNT !== '1') {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const userEmail = runSafe('git config --get user.email')
|
|
123
|
+
const userName = runSafe('git config --get user.name')
|
|
124
|
+
const commitSign = runSafe('git config --get commit.gpgsign').toLowerCase()
|
|
125
|
+
|
|
126
|
+
if (!userEmail || !userName) {
|
|
127
|
+
process.stderr.write(
|
|
128
|
+
`\n${YLW}${BOLD}✖ PUSH BLOCKED${R} — REQUIRE_VERIFIED_ACCOUNT=1 but git user identity is incomplete.\n`
|
|
129
|
+
)
|
|
130
|
+
process.exit(1)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (commitSign !== 'true') {
|
|
134
|
+
process.stderr.write(
|
|
135
|
+
`\n${YLW}${BOLD}✖ PUSH BLOCKED${R} — REQUIRE_VERIFIED_ACCOUNT=1 but commit signing is disabled.\n`
|
|
136
|
+
)
|
|
137
|
+
process.stderr.write(`${DIM}Run: git config commit.gpgsign true${R}\n\n`)
|
|
138
|
+
process.exit(1)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const invalid = []
|
|
142
|
+
for (const sha of commits) {
|
|
143
|
+
const status = runSafe(`git log -1 --format=%G? ${sha}`)
|
|
144
|
+
if (status !== 'G') {
|
|
145
|
+
const subject = runSafe(`git log -1 --format=%s ${sha}`)
|
|
146
|
+
invalid.push({ sha, status: status || '?', subject })
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (invalid.length > 0) {
|
|
151
|
+
process.stderr.write(`\n${YLW}${BOLD}✖ PUSH BLOCKED${R} — found unverified commit signature(s).\n`)
|
|
152
|
+
invalid.slice(0, 10).forEach(({ sha, status, subject }) => {
|
|
153
|
+
process.stderr.write(` - ${sha.slice(0, 10)} status=${status} ${subject}\n`)
|
|
154
|
+
})
|
|
155
|
+
if (invalid.length > 10) {
|
|
156
|
+
process.stderr.write(` ...and ${invalid.length - 10} more\n`)
|
|
157
|
+
}
|
|
158
|
+
process.stderr.write(`${DIM}Set REQUIRE_VERIFIED_ACCOUNT=0 to disable this gate.${R}\n\n`)
|
|
159
|
+
process.exit(1)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
93
163
|
// ─── Scan committed files ──────────────────────────────────────────────────────
|
|
94
164
|
|
|
95
165
|
/**
|
|
@@ -222,6 +292,10 @@ function main() {
|
|
|
222
292
|
|
|
223
293
|
const repoRoot = getRepoRoot()
|
|
224
294
|
const refs = parsePushRefs()
|
|
295
|
+
const commits = refs.length > 0 ? collectPushCommits(refs) : [runSafe('git rev-parse HEAD')].filter(Boolean)
|
|
296
|
+
|
|
297
|
+
// ── 0. Optional strict verified-account gate ───────────────────────────────
|
|
298
|
+
enforceVerifiedAccountIfEnabled(commits)
|
|
225
299
|
|
|
226
300
|
printScanHeader('@archipelago/pre-push', 'full-branch security gate')
|
|
227
301
|
|
|
@@ -160,7 +160,28 @@ export function levenshtein(a, b) {
|
|
|
160
160
|
* @returns {string|null}
|
|
161
161
|
*/
|
|
162
162
|
export function detectTyposquat(pkgName) {
|
|
163
|
-
const
|
|
163
|
+
const trustedScopes = new Set(['@storybook', '@types', '@vitejs', '@vitest', '@nuxt', '@vue', '@typescript-eslint'])
|
|
164
|
+
|
|
165
|
+
if (pkgName.startsWith('@')) {
|
|
166
|
+
const [scope = '', scopedName = ''] = pkgName.split('/')
|
|
167
|
+
// Trusted namespaces publish many package variants (vue3, react-vite, etc.)
|
|
168
|
+
// and should not be treated as typosquat candidates.
|
|
169
|
+
if (trustedScopes.has(scope)) return null
|
|
170
|
+
|
|
171
|
+
const bareScoped = scopedName || pkgName
|
|
172
|
+
if (bareScoped.length < 3) return null
|
|
173
|
+
for (const popular of POPULAR_PACKAGES) {
|
|
174
|
+
if (bareScoped === popular) return null
|
|
175
|
+
// Skip legit version-suffixed variants like vue3/react18.
|
|
176
|
+
if (bareScoped === `${popular}2` || bareScoped === `${popular}3` || bareScoped === `${popular}18`) {
|
|
177
|
+
return null
|
|
178
|
+
}
|
|
179
|
+
if (levenshtein(bareScoped.toLowerCase(), popular.toLowerCase()) === 1) return popular
|
|
180
|
+
}
|
|
181
|
+
return null
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const bare = pkgName
|
|
164
185
|
if (bare.length < 3) return null
|
|
165
186
|
for (const popular of POPULAR_PACKAGES) {
|
|
166
187
|
if (bare === popular) return null
|
package/tools/setup/install.mjs
CHANGED
|
@@ -27,6 +27,11 @@ function writeIfMissing(filePath, content) {
|
|
|
27
27
|
return true
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function isNuxtProject(projectRoot) {
|
|
31
|
+
const nuxtConfigNames = ['nuxt.config.ts', 'nuxt.config.js', 'nuxt.config.mjs', 'nuxt.config.cjs']
|
|
32
|
+
return nuxtConfigNames.some(fileName => existsSync(join(projectRoot, fileName)))
|
|
33
|
+
}
|
|
34
|
+
|
|
30
35
|
// ─── .editorconfig ──────────────────────────────────────────────────────────
|
|
31
36
|
|
|
32
37
|
function ensureEditorConfig(projectRoot) {
|
|
@@ -137,6 +142,79 @@ export default config
|
|
|
137
142
|
}
|
|
138
143
|
}
|
|
139
144
|
|
|
145
|
+
// ─── tsconfig.base.json ──────────────────────────────────────────────────────
|
|
146
|
+
// Shared TypeScript compiler settings used by every package and app.
|
|
147
|
+
// Includes @vue/typescript-plugin so the IDE delegates .vue file handling to
|
|
148
|
+
// Volar instead of the plain TS server (eliminates "Cannot find name 'div'" etc.)
|
|
149
|
+
|
|
150
|
+
function ensureTsConfigBase(projectRoot) {
|
|
151
|
+
const tsconfigBasePath = join(projectRoot, 'tsconfig.base.json')
|
|
152
|
+
const content = `{
|
|
153
|
+
"compilerOptions": {
|
|
154
|
+
"target": "ES2020",
|
|
155
|
+
"module": "ESNext",
|
|
156
|
+
"moduleResolution": "Bundler",
|
|
157
|
+
"lib": ["DOM", "DOM.Iterable", "ES2020"],
|
|
158
|
+
"strict": true,
|
|
159
|
+
"jsx": "preserve",
|
|
160
|
+
"resolveJsonModule": true,
|
|
161
|
+
"skipLibCheck": true,
|
|
162
|
+
"types": ["vite/client"],
|
|
163
|
+
"plugins": [
|
|
164
|
+
{
|
|
165
|
+
"name": "@vue/typescript-plugin",
|
|
166
|
+
"languages": ["vue"]
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
`
|
|
172
|
+
if (writeIfMissing(tsconfigBasePath, content)) log('Created tsconfig.base.json')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ─── tsconfig.json ───────────────────────────────────────────────────────────
|
|
176
|
+
// Root TypeScript project file that extends tsconfig.base.json.
|
|
177
|
+
// Covers src/ and packages/ source; excludes apps/ (each app owns its tsconfig).
|
|
178
|
+
// Add path aliases here as workspace packages grow.
|
|
179
|
+
|
|
180
|
+
function ensureTsConfig(projectRoot) {
|
|
181
|
+
const tsconfigPath = join(projectRoot, 'tsconfig.json')
|
|
182
|
+
const nonNuxtContent = `{
|
|
183
|
+
"extends": "./tsconfig.base.json",
|
|
184
|
+
"compilerOptions": {
|
|
185
|
+
"paths": {}
|
|
186
|
+
},
|
|
187
|
+
"include": [
|
|
188
|
+
"**/*.ts",
|
|
189
|
+
"**/*.tsx",
|
|
190
|
+
"**/*.vue",
|
|
191
|
+
"**/*.d.ts",
|
|
192
|
+
"vite.config.ts"
|
|
193
|
+
],
|
|
194
|
+
"exclude": [
|
|
195
|
+
"node_modules",
|
|
196
|
+
"**/dist/**",
|
|
197
|
+
"**/.nuxt/**",
|
|
198
|
+
"**/.output/**"
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
`
|
|
202
|
+
|
|
203
|
+
// Nuxt projects must extend .nuxt/tsconfig.json so aliases (`~/`, `#imports`)
|
|
204
|
+
// and auto-imported composables (`useRoute`, `useRuntimeConfig`, etc.) resolve.
|
|
205
|
+
const nuxtContent = `{
|
|
206
|
+
"extends": "./.nuxt/tsconfig.json"
|
|
207
|
+
}
|
|
208
|
+
`
|
|
209
|
+
|
|
210
|
+
const nuxtProject = isNuxtProject(projectRoot)
|
|
211
|
+
const content = nuxtProject ? nuxtContent : nonNuxtContent
|
|
212
|
+
|
|
213
|
+
if (writeIfMissing(tsconfigPath, content)) {
|
|
214
|
+
log(nuxtProject ? 'Created tsconfig.json (Nuxt mode)' : 'Created tsconfig.json')
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
140
218
|
function ensureCommitlintConfig(projectRoot) {
|
|
141
219
|
const commitlintConfigPath = join(projectRoot, 'commitlint.config.cjs')
|
|
142
220
|
const content = `module.exports = require('@archpublicwebsite/eslint-config/commitlint')
|
|
@@ -357,6 +435,8 @@ function main() {
|
|
|
357
435
|
|
|
358
436
|
ensureEditorConfig(projectRoot)
|
|
359
437
|
ensureEslintUserIgnore(projectRoot)
|
|
438
|
+
ensureTsConfigBase(projectRoot)
|
|
439
|
+
ensureTsConfig(projectRoot)
|
|
360
440
|
ensureEslintConfig(projectRoot)
|
|
361
441
|
ensurePrettierConfig(projectRoot)
|
|
362
442
|
ensurePrettierIgnore(projectRoot)
|
package/tools/setup/vscode.mjs
CHANGED
|
@@ -20,7 +20,13 @@ const VSCODE_ESLINT_SETTINGS = {
|
|
|
20
20
|
|
|
21
21
|
// Volar + TS integration for Vue 3 SFC template diagnostics
|
|
22
22
|
'vue.server.hybridMode': true,
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
// Defensive guard: if Vetur is installed globally, disable its validators
|
|
25
|
+
// to avoid conflicting diagnostics like "'<script setup ...>' expected".
|
|
26
|
+
'vetur.validation.template': false,
|
|
27
|
+
'vetur.validation.script': false,
|
|
28
|
+
'vetur.validation.style': false,
|
|
29
|
+
'vetur.experimental.templateInterpolationService': false,
|
|
24
30
|
|
|
25
31
|
// Prevent duplicate/broken diagnostics from built-in TS/JS validators
|
|
26
32
|
// (vue-tsc + Volar remain the source of truth for Vue type diagnostics).
|
|
@@ -36,7 +42,7 @@ const VSCODE_ESLINT_SETTINGS = {
|
|
|
36
42
|
|
|
37
43
|
// Auto-fix on save via ESLint
|
|
38
44
|
'editor.codeActionsOnSave': {
|
|
39
|
-
'source.fixAll.eslint':
|
|
45
|
+
'source.fixAll.eslint': true,
|
|
40
46
|
'source.organizeImports': 'never',
|
|
41
47
|
},
|
|
42
48
|
|
|
@@ -57,7 +63,7 @@ const VSCODE_ESLINT_SETTINGS = {
|
|
|
57
63
|
'editor.defaultFormatter': 'dbaeumer.vscode-eslint',
|
|
58
64
|
},
|
|
59
65
|
'[vue]': {
|
|
60
|
-
'editor.defaultFormatter': '
|
|
66
|
+
'editor.defaultFormatter': 'dbaeumer.vscode-eslint',
|
|
61
67
|
},
|
|
62
68
|
'[json]': {
|
|
63
69
|
'editor.defaultFormatter': 'dbaeumer.vscode-eslint',
|