@archpublicwebsite/eslint-config 1.0.21 → 1.0.23
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 +48 -3
- package/eslint.config.mjs +29 -24
- package/lint-staged.config.mjs +2 -7
- package/package.json +1 -1
- package/tools/git-hooks/pre-commit.mjs +34 -5
- package/tools/git-hooks/pre-push.mjs +134 -40
- package/tools/security/patterns.mjs +79 -27
- package/tools/security/risks.mjs +34 -34
- package/tools/security/scan.mjs +23 -21
- package/tools/security/scanner.mjs +40 -18
- package/tools/security/test-patterns.mjs +13 -16
- package/tools/setup/install.mjs +106 -49
- package/tools/setup/vscode.mjs +44 -21
package/tools/setup/install.mjs
CHANGED
|
@@ -13,19 +13,16 @@ function log(msg) {
|
|
|
13
13
|
|
|
14
14
|
function getProjectRoot() {
|
|
15
15
|
const root = process.env.INIT_CWD || process.cwd()
|
|
16
|
-
if (!existsSync(join(root, 'package.json')))
|
|
17
|
-
return null
|
|
16
|
+
if (!existsSync(join(root, 'package.json'))) return null
|
|
18
17
|
return root
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
function ensureDir(dirPath) {
|
|
22
|
-
if (!existsSync(dirPath))
|
|
23
|
-
mkdirSync(dirPath, { recursive: true })
|
|
21
|
+
if (!existsSync(dirPath)) mkdirSync(dirPath, { recursive: true })
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
function writeIfMissing(filePath, content) {
|
|
27
|
-
if (existsSync(filePath))
|
|
28
|
-
return false
|
|
25
|
+
if (existsSync(filePath)) return false
|
|
29
26
|
writeFileSync(filePath, content, 'utf8')
|
|
30
27
|
return true
|
|
31
28
|
}
|
|
@@ -56,8 +53,7 @@ indent_size = 2
|
|
|
56
53
|
[Makefile]
|
|
57
54
|
indent_style = tab
|
|
58
55
|
`
|
|
59
|
-
if (writeIfMissing(editorConfigPath, content))
|
|
60
|
-
log('Created .editorconfig')
|
|
56
|
+
if (writeIfMissing(editorConfigPath, content)) log('Created .editorconfig')
|
|
61
57
|
}
|
|
62
58
|
|
|
63
59
|
// ─── .eslint-user-ignore ────────────────────────────────────────────────────
|
|
@@ -80,8 +76,7 @@ function ensureEslintUserIgnore(projectRoot) {
|
|
|
80
76
|
# docs/**
|
|
81
77
|
# *.pdf
|
|
82
78
|
`
|
|
83
|
-
if (writeIfMissing(ignorePath, content))
|
|
84
|
-
log('Created .eslint-user-ignore')
|
|
79
|
+
if (writeIfMissing(ignorePath, content)) log('Created .eslint-user-ignore')
|
|
85
80
|
}
|
|
86
81
|
|
|
87
82
|
// ─── eslint.config.mjs ─────────────────────────────────────────────────────
|
|
@@ -97,8 +92,7 @@ export default createArchipelagoConfig({
|
|
|
97
92
|
},
|
|
98
93
|
})
|
|
99
94
|
`
|
|
100
|
-
if (writeIfMissing(eslintConfigPath, content))
|
|
101
|
-
log('Created eslint.config.mjs')
|
|
95
|
+
if (writeIfMissing(eslintConfigPath, content)) log('Created eslint.config.mjs')
|
|
102
96
|
}
|
|
103
97
|
|
|
104
98
|
// ─── .prettierrc ────────────────────────────────────────────────────────────
|
|
@@ -110,8 +104,7 @@ function ensurePrettierConfig(projectRoot) {
|
|
|
110
104
|
export default config
|
|
111
105
|
`
|
|
112
106
|
|
|
113
|
-
if (writeIfMissing(prettierModuleConfigPath, prettierModuleConfigContent))
|
|
114
|
-
log('Created prettier.config.mjs')
|
|
107
|
+
if (writeIfMissing(prettierModuleConfigPath, prettierModuleConfigContent)) log('Created prettier.config.mjs')
|
|
115
108
|
|
|
116
109
|
const prettierPath = join(projectRoot, '.prettierrc')
|
|
117
110
|
const defaults = {
|
|
@@ -135,24 +128,81 @@ export default config
|
|
|
135
128
|
const plugins = Array.isArray(current.plugins) ? current.plugins : []
|
|
136
129
|
const deduped = [...new Set([...plugins, 'prettier-plugin-tailwindcss'])]
|
|
137
130
|
current.plugins = deduped
|
|
138
|
-
if (typeof current.singleQuote !== 'boolean')
|
|
139
|
-
|
|
140
|
-
if (typeof current.semi !== 'boolean')
|
|
141
|
-
current.semi = false
|
|
131
|
+
if (typeof current.singleQuote !== 'boolean') current.singleQuote = true
|
|
132
|
+
if (typeof current.semi !== 'boolean') current.semi = false
|
|
142
133
|
writeFileSync(prettierPath, `${JSON.stringify(current, null, 2)}\n`, 'utf8')
|
|
143
134
|
log('Updated .prettierrc')
|
|
144
|
-
}
|
|
145
|
-
catch {
|
|
135
|
+
} catch {
|
|
146
136
|
// Keep existing file untouched if it is not JSON.
|
|
147
137
|
}
|
|
148
138
|
}
|
|
149
139
|
|
|
140
|
+
// ─── tsconfig.base.json ──────────────────────────────────────────────────────
|
|
141
|
+
// Shared TypeScript compiler settings used by every package and app.
|
|
142
|
+
// Includes @vue/typescript-plugin so the IDE delegates .vue file handling to
|
|
143
|
+
// Volar instead of the plain TS server (eliminates "Cannot find name 'div'" etc.)
|
|
144
|
+
|
|
145
|
+
function ensureTsConfigBase(projectRoot) {
|
|
146
|
+
const tsconfigBasePath = join(projectRoot, 'tsconfig.base.json')
|
|
147
|
+
const content = `{
|
|
148
|
+
"compilerOptions": {
|
|
149
|
+
"target": "ES2020",
|
|
150
|
+
"module": "ESNext",
|
|
151
|
+
"moduleResolution": "Bundler",
|
|
152
|
+
"lib": ["DOM", "DOM.Iterable", "ES2020"],
|
|
153
|
+
"strict": true,
|
|
154
|
+
"jsx": "preserve",
|
|
155
|
+
"resolveJsonModule": true,
|
|
156
|
+
"skipLibCheck": true,
|
|
157
|
+
"types": ["vite/client"],
|
|
158
|
+
"plugins": [
|
|
159
|
+
{
|
|
160
|
+
"name": "@vue/typescript-plugin",
|
|
161
|
+
"languages": ["vue"]
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
`
|
|
167
|
+
if (writeIfMissing(tsconfigBasePath, content)) log('Created tsconfig.base.json')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ─── tsconfig.json ───────────────────────────────────────────────────────────
|
|
171
|
+
// Root TypeScript project file that extends tsconfig.base.json.
|
|
172
|
+
// Covers src/ and packages/ source; excludes apps/ (each app owns its tsconfig).
|
|
173
|
+
// Add path aliases here as workspace packages grow.
|
|
174
|
+
|
|
175
|
+
function ensureTsConfig(projectRoot) {
|
|
176
|
+
const tsconfigPath = join(projectRoot, 'tsconfig.json')
|
|
177
|
+
const content = `{
|
|
178
|
+
"extends": "./tsconfig.base.json",
|
|
179
|
+
"compilerOptions": {
|
|
180
|
+
"paths": {}
|
|
181
|
+
},
|
|
182
|
+
"include": [
|
|
183
|
+
"src/**/*.ts",
|
|
184
|
+
"src/**/*.vue",
|
|
185
|
+
"packages/**/*.ts",
|
|
186
|
+
"packages/**/*.vue",
|
|
187
|
+
"vite.config.ts"
|
|
188
|
+
],
|
|
189
|
+
"exclude": [
|
|
190
|
+
"apps/**",
|
|
191
|
+
"node_modules",
|
|
192
|
+
"**/dist/**",
|
|
193
|
+
"**/.nuxt/**",
|
|
194
|
+
"**/.output/**"
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
`
|
|
198
|
+
if (writeIfMissing(tsconfigPath, content)) log('Created tsconfig.json')
|
|
199
|
+
}
|
|
200
|
+
|
|
150
201
|
function ensureCommitlintConfig(projectRoot) {
|
|
151
202
|
const commitlintConfigPath = join(projectRoot, 'commitlint.config.cjs')
|
|
152
203
|
const content = `module.exports = require('@archpublicwebsite/eslint-config/commitlint')
|
|
153
204
|
`
|
|
154
|
-
if (writeIfMissing(commitlintConfigPath, content))
|
|
155
|
-
log('Created commitlint.config.cjs')
|
|
205
|
+
if (writeIfMissing(commitlintConfigPath, content)) log('Created commitlint.config.cjs')
|
|
156
206
|
}
|
|
157
207
|
|
|
158
208
|
function ensureLintStagedConfig(projectRoot) {
|
|
@@ -161,8 +211,7 @@ function ensureLintStagedConfig(projectRoot) {
|
|
|
161
211
|
|
|
162
212
|
export default config
|
|
163
213
|
`
|
|
164
|
-
if (writeIfMissing(lintStagedConfigPath, content))
|
|
165
|
-
log('Created lint-staged.config.mjs')
|
|
214
|
+
if (writeIfMissing(lintStagedConfigPath, content)) log('Created lint-staged.config.mjs')
|
|
166
215
|
}
|
|
167
216
|
|
|
168
217
|
function ensurePrettierIgnore(projectRoot) {
|
|
@@ -178,8 +227,7 @@ pnpm-lock.yaml
|
|
|
178
227
|
package-lock.json
|
|
179
228
|
yarn.lock
|
|
180
229
|
`
|
|
181
|
-
if (writeIfMissing(prettierIgnorePath, content))
|
|
182
|
-
log('Created .prettierignore')
|
|
230
|
+
if (writeIfMissing(prettierIgnorePath, content)) log('Created .prettierignore')
|
|
183
231
|
}
|
|
184
232
|
|
|
185
233
|
// ─── .hooks ─────────────────────────────────────────────────────────────────
|
|
@@ -233,18 +281,15 @@ node node_modules/@archpublicwebsite/eslint-config/tools/git-hooks/pre-push.mjs
|
|
|
233
281
|
created = true
|
|
234
282
|
}
|
|
235
283
|
})
|
|
236
|
-
if (created)
|
|
237
|
-
log('Created .hooks/ (pre-commit, prepare-commit-msg, commit-msg, post-commit, pre-push)')
|
|
284
|
+
if (created) log('Created .hooks/ (pre-commit, prepare-commit-msg, commit-msg, post-commit, pre-push)')
|
|
238
285
|
}
|
|
239
286
|
|
|
240
287
|
function ensureHooksPath(projectRoot) {
|
|
241
|
-
if (!existsSync(join(projectRoot, '.git')))
|
|
242
|
-
return
|
|
288
|
+
if (!existsSync(join(projectRoot, '.git'))) return
|
|
243
289
|
try {
|
|
244
290
|
execSync('git config core.hooksPath .hooks', { cwd: projectRoot, stdio: 'ignore' })
|
|
245
291
|
log('Set git core.hooksPath → .hooks')
|
|
246
|
-
}
|
|
247
|
-
catch {
|
|
292
|
+
} catch {
|
|
248
293
|
// Ignore setup failures in non-git contexts.
|
|
249
294
|
}
|
|
250
295
|
}
|
|
@@ -284,8 +329,7 @@ function ensurePackageScripts(projectRoot) {
|
|
|
284
329
|
let pkg
|
|
285
330
|
try {
|
|
286
331
|
pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
|
|
287
|
-
}
|
|
288
|
-
catch {
|
|
332
|
+
} catch {
|
|
289
333
|
return
|
|
290
334
|
}
|
|
291
335
|
|
|
@@ -299,7 +343,8 @@ function ensurePackageScripts(projectRoot) {
|
|
|
299
343
|
precommit: 'node node_modules/@archpublicwebsite/eslint-config/tools/git-hooks/pre-commit.mjs',
|
|
300
344
|
prepush: 'node node_modules/@archpublicwebsite/eslint-config/tools/git-hooks/pre-push.mjs',
|
|
301
345
|
'security:global-scan': 'bash ./node_modules/@archpublicwebsite/eslint-config/tools/security/scan-global.sh',
|
|
302
|
-
'security:safe-check':
|
|
346
|
+
'security:safe-check':
|
|
347
|
+
'bash ./node_modules/@archpublicwebsite/eslint-config/tools/security/safe-reinstall.sh --check-only',
|
|
303
348
|
'security:pre-push': 'node node_modules/@archpublicwebsite/eslint-config/tools/git-hooks/pre-push.mjs',
|
|
304
349
|
}
|
|
305
350
|
|
|
@@ -311,13 +356,26 @@ function ensurePackageScripts(projectRoot) {
|
|
|
311
356
|
}
|
|
312
357
|
}
|
|
313
358
|
|
|
359
|
+
const lintStagedConfigPath = join(projectRoot, 'lint-staged.config.mjs')
|
|
360
|
+
const hasLintStagedConfig = existsSync(lintStagedConfigPath)
|
|
361
|
+
|
|
362
|
+
// Auto-heal legacy/broken lint-staged script when it points to a missing file.
|
|
363
|
+
if (
|
|
364
|
+
typeof scripts['lint-staged'] === 'string' &&
|
|
365
|
+
scripts['lint-staged'].includes('--config lint-staged.config.mjs') &&
|
|
366
|
+
!hasLintStagedConfig
|
|
367
|
+
) {
|
|
368
|
+
scripts['lint-staged'] = 'lint-staged'
|
|
369
|
+
updated = true
|
|
370
|
+
}
|
|
371
|
+
|
|
314
372
|
if (!updated) {
|
|
315
373
|
return
|
|
316
374
|
}
|
|
317
375
|
|
|
318
376
|
pkg.scripts = scripts
|
|
319
377
|
writeFileSync(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8')
|
|
320
|
-
log('Updated package.json scripts (precommit, security:global-scan, security:safe-check)')
|
|
378
|
+
log('Updated package.json scripts (lint-staged, precommit, security:global-scan, security:safe-check)')
|
|
321
379
|
}
|
|
322
380
|
|
|
323
381
|
// ─── .vscode/extensions.json ────────────────────────────────────────────────
|
|
@@ -328,26 +386,24 @@ function ensureVscodeExtensions(projectRoot) {
|
|
|
328
386
|
|
|
329
387
|
ensureDir(vscodeDir)
|
|
330
388
|
|
|
331
|
-
const recommended = [
|
|
332
|
-
|
|
333
|
-
'esbenp.prettier-vscode',
|
|
334
|
-
'vue.volar',
|
|
335
|
-
]
|
|
389
|
+
const recommended = ['dbaeumer.vscode-eslint', 'esbenp.prettier-vscode', 'vue.volar']
|
|
390
|
+
const unwanted = ['octref.vetur', 'vue.vscode-typescript-vue-plugin']
|
|
336
391
|
|
|
337
|
-
let current = { recommendations: [] }
|
|
392
|
+
let current = { recommendations: [], unwantedRecommendations: [] }
|
|
338
393
|
if (existsSync(extPath)) {
|
|
339
394
|
try {
|
|
340
395
|
current = JSON.parse(readFileSync(extPath, 'utf8'))
|
|
341
|
-
if (!Array.isArray(current.recommendations))
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
current = { recommendations: [] }
|
|
396
|
+
if (!Array.isArray(current.recommendations)) current.recommendations = []
|
|
397
|
+
if (!Array.isArray(current.unwantedRecommendations)) current.unwantedRecommendations = []
|
|
398
|
+
} catch {
|
|
399
|
+
current = { recommendations: [], unwantedRecommendations: [] }
|
|
346
400
|
}
|
|
347
401
|
}
|
|
348
402
|
|
|
349
403
|
const merged = [...new Set([...current.recommendations, ...recommended])]
|
|
404
|
+
const mergedUnwanted = [...new Set([...current.unwantedRecommendations, ...unwanted])]
|
|
350
405
|
current.recommendations = merged
|
|
406
|
+
current.unwantedRecommendations = mergedUnwanted
|
|
351
407
|
writeFileSync(extPath, `${JSON.stringify(current, null, 2)}\n`, 'utf8')
|
|
352
408
|
log('Created/updated .vscode/extensions.json')
|
|
353
409
|
}
|
|
@@ -356,13 +412,14 @@ function ensureVscodeExtensions(projectRoot) {
|
|
|
356
412
|
|
|
357
413
|
function main() {
|
|
358
414
|
const projectRoot = getProjectRoot()
|
|
359
|
-
if (!projectRoot)
|
|
360
|
-
return
|
|
415
|
+
if (!projectRoot) return
|
|
361
416
|
|
|
362
417
|
log('Setting up project...')
|
|
363
418
|
|
|
364
419
|
ensureEditorConfig(projectRoot)
|
|
365
420
|
ensureEslintUserIgnore(projectRoot)
|
|
421
|
+
ensureTsConfigBase(projectRoot)
|
|
422
|
+
ensureTsConfig(projectRoot)
|
|
366
423
|
ensureEslintConfig(projectRoot)
|
|
367
424
|
ensurePrettierConfig(projectRoot)
|
|
368
425
|
ensurePrettierIgnore(projectRoot)
|
package/tools/setup/vscode.mjs
CHANGED
|
@@ -9,20 +9,30 @@ const TAG = '[@archpublicwebsite/eslint-config]'
|
|
|
9
9
|
* the correct file types.
|
|
10
10
|
*/
|
|
11
11
|
const VSCODE_ESLINT_SETTINGS = {
|
|
12
|
+
// Use workspace TypeScript for consistent Volar/TS plugin resolution
|
|
13
|
+
'typescript.tsdk': 'node_modules/typescript/lib',
|
|
14
|
+
'typescript.enablePromptUseWorkspaceTsdk': true,
|
|
15
|
+
|
|
16
|
+
// Ensure Vue SFC files are always handled as Vue documents
|
|
17
|
+
'files.associations': {
|
|
18
|
+
'*.vue': 'vue',
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
// Volar + TS integration for Vue 3 SFC template diagnostics
|
|
22
|
+
'vue.server.hybridMode': true,
|
|
23
|
+
'volar.takeOverMode.enabled': true,
|
|
24
|
+
|
|
25
|
+
// Prevent duplicate/broken diagnostics from built-in TS/JS validators
|
|
26
|
+
// (vue-tsc + Volar remain the source of truth for Vue type diagnostics).
|
|
27
|
+
'typescript.validate.enable': false,
|
|
28
|
+
'javascript.validate.enable': false,
|
|
29
|
+
|
|
12
30
|
// Use flat config mode (required for eslint.config.mjs)
|
|
13
31
|
'eslint.useFlatConfig': true,
|
|
32
|
+
'eslint.workingDirectories': [{ mode: 'auto' }],
|
|
14
33
|
|
|
15
34
|
// Enable ESLint for these languages
|
|
16
|
-
'eslint.validate': [
|
|
17
|
-
'javascript',
|
|
18
|
-
'javascriptreact',
|
|
19
|
-
'typescript',
|
|
20
|
-
'typescriptreact',
|
|
21
|
-
'vue',
|
|
22
|
-
'json',
|
|
23
|
-
'jsonc',
|
|
24
|
-
'markdown',
|
|
25
|
-
],
|
|
35
|
+
'eslint.validate': ['javascript', 'javascriptreact', 'typescript', 'typescriptreact', 'vue', 'json', 'jsonc'],
|
|
26
36
|
|
|
27
37
|
// Auto-fix on save via ESLint
|
|
28
38
|
'editor.codeActionsOnSave': {
|
|
@@ -33,7 +43,22 @@ const VSCODE_ESLINT_SETTINGS = {
|
|
|
33
43
|
// Let ESLint handle formatting instead of the built-in formatter
|
|
34
44
|
'editor.formatOnSave': false,
|
|
35
45
|
|
|
36
|
-
//
|
|
46
|
+
// Formatters per language — ESLint enforces no-semi, single-quote on save
|
|
47
|
+
'[javascript]': {
|
|
48
|
+
'editor.defaultFormatter': 'dbaeumer.vscode-eslint',
|
|
49
|
+
},
|
|
50
|
+
'[javascriptreact]': {
|
|
51
|
+
'editor.defaultFormatter': 'dbaeumer.vscode-eslint',
|
|
52
|
+
},
|
|
53
|
+
'[typescript]': {
|
|
54
|
+
'editor.defaultFormatter': 'dbaeumer.vscode-eslint',
|
|
55
|
+
},
|
|
56
|
+
'[typescriptreact]': {
|
|
57
|
+
'editor.defaultFormatter': 'dbaeumer.vscode-eslint',
|
|
58
|
+
},
|
|
59
|
+
'[vue]': {
|
|
60
|
+
'editor.defaultFormatter': 'Vue.volar',
|
|
61
|
+
},
|
|
37
62
|
'[json]': {
|
|
38
63
|
'editor.defaultFormatter': 'dbaeumer.vscode-eslint',
|
|
39
64
|
},
|
|
@@ -71,16 +96,15 @@ function deepMerge(target, source) {
|
|
|
71
96
|
const srcVal = source[key]
|
|
72
97
|
const tgtVal = result[key]
|
|
73
98
|
if (
|
|
74
|
-
srcVal !== null
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
99
|
+
srcVal !== null &&
|
|
100
|
+
typeof srcVal === 'object' &&
|
|
101
|
+
!Array.isArray(srcVal) &&
|
|
102
|
+
tgtVal !== null &&
|
|
103
|
+
typeof tgtVal === 'object' &&
|
|
104
|
+
!Array.isArray(tgtVal)
|
|
80
105
|
) {
|
|
81
106
|
result[key] = deepMerge(tgtVal, srcVal)
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
107
|
+
} else {
|
|
84
108
|
result[key] = srcVal
|
|
85
109
|
}
|
|
86
110
|
}
|
|
@@ -107,8 +131,7 @@ export function ensureVscodeSettings(projectRoot) {
|
|
|
107
131
|
try {
|
|
108
132
|
const raw = readFileSync(settingsPath, 'utf8')
|
|
109
133
|
current = JSON.parse(raw)
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
134
|
+
} catch {
|
|
112
135
|
// File exists but is not valid JSON – back it up and start fresh
|
|
113
136
|
const backupPath = `${settingsPath}.backup`
|
|
114
137
|
writeFileSync(backupPath, readFileSync(settingsPath, 'utf8'), 'utf8')
|