@nitra/cursor 1.4.1 → 1.5.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/bin/n-cursor.js +34 -19
- package/mdc/bun.mdc +1 -1
- package/mdc/style-lint.mdc +1 -1
- package/package.json +1 -1
- package/scripts/check-bun.mjs +22 -7
- package/scripts/check-ga.mjs +22 -11
- package/scripts/check-js-format.mjs +26 -12
- package/scripts/check-js-lint.mjs +29 -14
- package/scripts/check-js-pino.mjs +2 -1
- package/scripts/check-nginx-default-tpl.mjs +27 -16
- package/scripts/check-npm-module.mjs +22 -9
- package/scripts/check-spell.mjs +45 -16
- package/scripts/check-style-lint.mjs +37 -18
- package/scripts/check-vue.mjs +42 -19
- package/scripts/utils/pass.mjs +7 -0
- package/skills/{n-fix-cursor → n-fix}/SKILL.md +6 -2
- package/skills/n-lint/SKILL.md +0 -26
package/bin/n-cursor.js
CHANGED
|
@@ -107,20 +107,19 @@ async function migrateLegacyManagedRuleFilenames(rulesDir) {
|
|
|
107
107
|
}
|
|
108
108
|
const names = await readdir(rulesDir)
|
|
109
109
|
for (const name of names) {
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
110
|
+
if (name.endsWith('.mdc') && name.startsWith('nitra-')) {
|
|
111
|
+
const rest = name.slice('nitra-'.length)
|
|
112
|
+
const newName = `${RULE_PREFIX}${rest}`
|
|
113
|
+
const from = join(rulesDir, name)
|
|
114
|
+
const to = join(rulesDir, newName)
|
|
115
|
+
if (existsSync(to)) {
|
|
116
|
+
await unlink(from)
|
|
117
|
+
console.log(`📝 Видалено застарілий ${RULES_DIR}/${name} (вже є ${newName})\n`)
|
|
118
|
+
} else {
|
|
119
|
+
await rename(from, to)
|
|
120
|
+
console.log(`📝 Перейменовано ${RULES_DIR}/${name} → ${RULES_DIR}/${newName}\n`)
|
|
121
|
+
}
|
|
121
122
|
}
|
|
122
|
-
await rename(from, to)
|
|
123
|
-
console.log(`📝 Перейменовано ${RULES_DIR}/${name} → ${RULES_DIR}/${newName}\n`)
|
|
124
123
|
}
|
|
125
124
|
}
|
|
126
125
|
|
|
@@ -168,7 +167,7 @@ async function readConfig() {
|
|
|
168
167
|
throw new Error(`Невірний JSON у файлі ${CONFIG_FILE}`)
|
|
169
168
|
}
|
|
170
169
|
if (!Array.isArray(config.rules) || config.rules.length === 0) {
|
|
171
|
-
throw new Error(`У ${CONFIG_FILE} має бути
|
|
170
|
+
throw new Error(`У ${CONFIG_FILE} має бути непорожній масив "rules"`)
|
|
172
171
|
}
|
|
173
172
|
if (!Array.isArray(config.skills)) {
|
|
174
173
|
if ('skills' in config) {
|
|
@@ -204,7 +203,7 @@ function normalizeRuleName(ruleName) {
|
|
|
204
203
|
}
|
|
205
204
|
|
|
206
205
|
/**
|
|
207
|
-
* Нормалізує id skill з конфігу до форми без префікса n- (як «fix
|
|
206
|
+
* Нормалізує id skill з конфігу до форми без префікса n- (як «fix»)
|
|
208
207
|
* @param {string} skillName елемент масиву skills або ім'я каталогу
|
|
209
208
|
* @returns {string} id без префікса n-
|
|
210
209
|
*/
|
|
@@ -216,13 +215,28 @@ function normalizeSkillId(skillName) {
|
|
|
216
215
|
return s
|
|
217
216
|
}
|
|
218
217
|
|
|
218
|
+
/** Legacy id у `.n-cursor.json` → поточний bundled id (каталог `n-<id>` у пакеті) */
|
|
219
|
+
const LEGACY_SKILL_ID_MAP = {
|
|
220
|
+
'fix-cursor': 'fix'
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Поточний id skill для шляхів у пакеті та `.cursor/skills`
|
|
225
|
+
* @param {string} skillName елемент масиву skills або ім'я каталогу
|
|
226
|
+
* @returns {string} canonical id без префікса n-
|
|
227
|
+
*/
|
|
228
|
+
function canonicalSkillId(skillName) {
|
|
229
|
+
const id = normalizeSkillId(skillName)
|
|
230
|
+
return LEGACY_SKILL_ID_MAP[id] ?? id
|
|
231
|
+
}
|
|
232
|
+
|
|
219
233
|
/**
|
|
220
234
|
* Ім'я керованого каталогу skill у .cursor/skills (префікс n-)
|
|
221
235
|
* @param {string} skillId id без префікса
|
|
222
|
-
* @returns {string} наприклад n-fix
|
|
236
|
+
* @returns {string} наприклад n-fix
|
|
223
237
|
*/
|
|
224
238
|
function managedSkillDirName(skillId) {
|
|
225
|
-
return `${RULE_PREFIX}${
|
|
239
|
+
return `${RULE_PREFIX}${canonicalSkillId(skillId)}`
|
|
226
240
|
}
|
|
227
241
|
|
|
228
242
|
/**
|
|
@@ -429,12 +443,13 @@ async function syncSkills(configSkills) {
|
|
|
429
443
|
let fail = 0
|
|
430
444
|
|
|
431
445
|
for (const skillId of configSkills) {
|
|
446
|
+
const id = canonicalSkillId(skillId)
|
|
432
447
|
const dirName = managedSkillDirName(skillId)
|
|
433
448
|
const srcDir = join(BUNDLED_SKILLS_DIR, dirName)
|
|
434
449
|
const destDir = join(skillsRoot, dirName)
|
|
435
450
|
|
|
436
451
|
if (existsSync(srcDir)) {
|
|
437
|
-
process.stdout.write(` ⬇ ${
|
|
452
|
+
process.stdout.write(` ⬇ ${id} → ${SKILLS_DIR}/${dirName} ... `)
|
|
438
453
|
try {
|
|
439
454
|
await mkdir(destDir, { recursive: true })
|
|
440
455
|
const files = await readdir(srcDir)
|
|
@@ -450,7 +465,7 @@ async function syncSkills(configSkills) {
|
|
|
450
465
|
fail++
|
|
451
466
|
}
|
|
452
467
|
} else {
|
|
453
|
-
process.stdout.write(` ⬇ ${
|
|
468
|
+
process.stdout.write(` ⬇ ${id} → ${SKILLS_DIR}/${dirName} ... `)
|
|
454
469
|
console.log(`❌`)
|
|
455
470
|
console.error(` Немає каталогу в пакеті: ${dirName}`)
|
|
456
471
|
fail++
|
package/mdc/bun.mdc
CHANGED
|
@@ -45,7 +45,7 @@ Lockfile у репозиторії: `bun.lock`.
|
|
|
45
45
|
- Якщо залежність потрібна лише одному пакету, додавати її в директорії цього пакета.
|
|
46
46
|
- У CI та локально запускати скрипти через `bun run`.
|
|
47
47
|
|
|
48
|
-
Якщо в package.json є поля `packageManager`, то прибрати їх, також прибрати всі директорії та
|
|
48
|
+
Якщо в package.json є поля `packageManager`, то прибрати їх, також прибрати всі директорії та файли для yarn
|
|
49
49
|
|
|
50
50
|
Якщо в проекті використовується npx, то не заміняти його на bunx, а використовувати npx.
|
|
51
51
|
Коли зміна відбувається в Dockerfile, то використовувати
|
package/mdc/style-lint.mdc
CHANGED
|
@@ -8,7 +8,7 @@ version: '1.1'
|
|
|
8
8
|
|
|
9
9
|
- **Джерело правил:** перед тим як писати або суттєво змінювати **`.css`**, **`.scss`** або стилі в **`.vue`**, переглянь у корені проєкту (і в релевантних пакетах монорепо, якщо є) поле **`stylelint`** у **`package.json`** (зокрема `extends`), наявні **`.stylelintrc.*`**, **`stylelint.config.*`** та **`.stylelintignore`**. Не покладайся на «типові» правила stylelint з пам’яті — дотримуйся **проєктного** **`@nitra/stylelint-config`** і будь-яких локальних доповнень у репозиторії.
|
|
10
10
|
- **Форматування** узгоджуй з **`n-js-format.mdc`** (oxfmt / `.oxfmtrc.json` для css, scss тощо), щоб форматер і stylelint не суперечили один одному.
|
|
11
|
-
- **Після змін:** запускай **`bun run lint-style`** (або `bunx stylelint` з тими ж glob-ами та прапорцями, що в скрипті та CI) і виправляй усе, що лишилось після auto-fix.
|
|
11
|
+
- **Після змін:** запускай **`bun run lint-style`** (або `bunx stylelint` з тими ж glob-ами та прапорцями, що в скрипті та CI) і виправляй усе, що лишилось після auto-fix. За потреби пройдись повним набором `lint-*` з `package.json` (див. навичку **`n-fix`**).
|
|
12
12
|
- **Не розширюй винятки:** не додавай зайві **`stylelint-disable`** / вузькі придушення правил без потреби; краще змінити стилі під правила проєкту.
|
|
13
13
|
|
|
14
14
|
В файлі .vscode/extensions.json є налаштування для офіційного плагіну:
|
package/package.json
CHANGED
package/scripts/check-bun.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
3
|
|
|
4
|
+
import { pass } from './utils/pass.mjs'
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Перевіряє відповідність проєкту правилам bun.mdc
|
|
6
8
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
9
|
*/
|
|
8
10
|
export async function check() {
|
|
9
11
|
let exitCode = 0
|
|
10
|
-
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
12
|
const fail = msg => {
|
|
12
13
|
console.log(` ❌ ${msg}`)
|
|
13
14
|
exitCode = 1
|
|
@@ -15,17 +16,31 @@ export async function check() {
|
|
|
15
16
|
|
|
16
17
|
const forbidden = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', '.yarnrc.yml']
|
|
17
18
|
for (const f of forbidden) {
|
|
18
|
-
existsSync(f)
|
|
19
|
+
if (existsSync(f)) {
|
|
20
|
+
fail(`Знайдено заборонений файл: ${f} — видали його`)
|
|
21
|
+
} else {
|
|
22
|
+
pass(`Немає ${f}`)
|
|
23
|
+
}
|
|
19
24
|
}
|
|
20
25
|
|
|
21
|
-
existsSync('.yarn')
|
|
22
|
-
|
|
26
|
+
if (existsSync('.yarn')) {
|
|
27
|
+
fail('Знайдено директорію .yarn — видали її')
|
|
28
|
+
} else {
|
|
29
|
+
pass('Немає .yarn/')
|
|
30
|
+
}
|
|
31
|
+
if (existsSync('bun.lock')) {
|
|
32
|
+
pass('bun.lock є')
|
|
33
|
+
} else {
|
|
34
|
+
fail('Відсутній bun.lock — запусти bun i')
|
|
35
|
+
}
|
|
23
36
|
|
|
24
37
|
if (existsSync('package.json')) {
|
|
25
38
|
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
26
|
-
pkg.packageManager
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
if (pkg.packageManager) {
|
|
40
|
+
fail(`package.json містить поле packageManager: "${pkg.packageManager}" — видали його`)
|
|
41
|
+
} else {
|
|
42
|
+
pass('package.json не містить packageManager')
|
|
43
|
+
}
|
|
29
44
|
}
|
|
30
45
|
|
|
31
46
|
return exitCode
|
package/scripts/check-ga.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { readdir, readFile } from 'node:fs/promises'
|
|
3
3
|
|
|
4
|
+
import { pass } from './utils/pass.mjs'
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Перевіряє відповідність проєкту правилам ga.mdc
|
|
6
8
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
9
|
*/
|
|
8
10
|
export async function check() {
|
|
9
11
|
let exitCode = 0
|
|
10
|
-
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
12
|
const fail = msg => {
|
|
12
13
|
console.log(` ❌ ${msg}`)
|
|
13
14
|
exitCode = 1
|
|
@@ -30,28 +31,38 @@ export async function check() {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
for (const f of ['clean-ga-workflows.yml', 'clean-merged-branch.yml']) {
|
|
33
|
-
files.includes(f)
|
|
34
|
+
if (files.includes(f)) {
|
|
35
|
+
pass(`${f} існує`)
|
|
36
|
+
} else {
|
|
37
|
+
fail(`Відсутній ${wfDir}/${f}`)
|
|
38
|
+
}
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
if (files.includes('apply-k8s.yml')) {
|
|
37
42
|
const content = await readFile(`${wfDir}/apply-k8s.yml`, 'utf8')
|
|
38
|
-
content.includes('**/k8s/*.yaml')
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
if (content.includes('**/k8s/*.yaml')) {
|
|
44
|
+
pass('apply-k8s.yml має правильний paths trigger')
|
|
45
|
+
} else {
|
|
46
|
+
fail('apply-k8s.yml не містить paths: **/k8s/*.yaml')
|
|
47
|
+
}
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
if (files.includes('apply-nats-consumer.yml')) {
|
|
44
51
|
const content = await readFile(`${wfDir}/apply-nats-consumer.yml`, 'utf8')
|
|
45
|
-
content.includes('**/consumer.yaml')
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
if (content.includes('**/consumer.yaml')) {
|
|
53
|
+
pass('apply-nats-consumer.yml має правильний paths trigger')
|
|
54
|
+
} else {
|
|
55
|
+
fail('apply-nats-consumer.yml не містить paths: **/consumer.yaml')
|
|
56
|
+
}
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
if (existsSync('.vscode/extensions.json')) {
|
|
51
60
|
const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
|
|
52
|
-
ext.recommendations?.includes('github.vscode-github-actions')
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
if (ext.recommendations?.includes('github.vscode-github-actions')) {
|
|
62
|
+
pass('extensions.json містить github.vscode-github-actions')
|
|
63
|
+
} else {
|
|
64
|
+
fail('extensions.json не містить github.vscode-github-actions')
|
|
65
|
+
}
|
|
55
66
|
} else {
|
|
56
67
|
fail('.vscode/extensions.json не існує')
|
|
57
68
|
}
|
|
@@ -1,29 +1,39 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
3
|
|
|
4
|
+
import { pass } from './utils/pass.mjs'
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Перевіряє відповідність проєкту правилам js-format.mdc
|
|
6
8
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
9
|
*/
|
|
8
10
|
export async function check() {
|
|
9
11
|
let exitCode = 0
|
|
10
|
-
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
12
|
const fail = msg => {
|
|
12
13
|
console.log(` ❌ ${msg}`)
|
|
13
14
|
exitCode = 1
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
const expectedKeys = [
|
|
17
|
-
'arrowParens',
|
|
18
|
-
'
|
|
18
|
+
'arrowParens',
|
|
19
|
+
'printWidth',
|
|
20
|
+
'bracketSpacing',
|
|
21
|
+
'bracketSameLine',
|
|
22
|
+
'semi',
|
|
23
|
+
'singleQuote',
|
|
24
|
+
'tabWidth',
|
|
25
|
+
'trailingComma',
|
|
26
|
+
'useTabs'
|
|
19
27
|
]
|
|
20
28
|
|
|
21
29
|
if (existsSync('.oxfmtrc.json')) {
|
|
22
30
|
const cfg = JSON.parse(await readFile('.oxfmtrc.json', 'utf8'))
|
|
23
31
|
const missing = expectedKeys.filter(k => !(k in cfg))
|
|
24
|
-
missing.length === 0
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
if (missing.length === 0) {
|
|
33
|
+
pass('.oxfmtrc.json містить всі обовʼязкові ключі')
|
|
34
|
+
} else {
|
|
35
|
+
fail(`.oxfmtrc.json відсутні ключі: ${missing.join(', ')}`)
|
|
36
|
+
}
|
|
27
37
|
|
|
28
38
|
if (cfg.semi !== false) fail('.oxfmtrc.json: semi має бути false')
|
|
29
39
|
if (cfg.singleQuote !== true) fail('.oxfmtrc.json: singleQuote має бути true')
|
|
@@ -36,18 +46,22 @@ export async function check() {
|
|
|
36
46
|
|
|
37
47
|
if (existsSync('.vscode/extensions.json')) {
|
|
38
48
|
const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
|
|
39
|
-
ext.recommendations?.includes('oxc.oxc-vscode')
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
if (ext.recommendations?.includes('oxc.oxc-vscode')) {
|
|
50
|
+
pass('extensions.json містить oxc.oxc-vscode')
|
|
51
|
+
} else {
|
|
52
|
+
fail('extensions.json не містить oxc.oxc-vscode')
|
|
53
|
+
}
|
|
42
54
|
} else {
|
|
43
55
|
fail('.vscode/extensions.json не існує')
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
if (existsSync('.vscode/settings.json')) {
|
|
47
59
|
const settings = JSON.parse(await readFile('.vscode/settings.json', 'utf8'))
|
|
48
|
-
settings['editor.formatOnSave'] === true
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
if (settings['editor.formatOnSave'] === true) {
|
|
61
|
+
pass('settings.json: editor.formatOnSave увімкнено')
|
|
62
|
+
} else {
|
|
63
|
+
fail('settings.json: editor.formatOnSave має бути true')
|
|
64
|
+
}
|
|
51
65
|
|
|
52
66
|
const fmtTypes = ['javascript', 'typescript', 'json', 'vue', 'css', 'html']
|
|
53
67
|
for (const t of fmtTypes) {
|
|
@@ -1,34 +1,41 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
3
|
|
|
4
|
+
import { pass } from './utils/pass.mjs'
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Перевіряє відповідність проєкту правилам js-lint.mdc
|
|
6
8
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
9
|
*/
|
|
8
10
|
export async function check() {
|
|
9
11
|
let exitCode = 0
|
|
10
|
-
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
12
|
const fail = msg => {
|
|
12
13
|
console.log(` ❌ ${msg}`)
|
|
13
14
|
exitCode = 1
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
existsSync('eslint.config.js')
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
if (existsSync('eslint.config.js')) {
|
|
18
|
+
pass('eslint.config.js існує')
|
|
19
|
+
} else if (existsSync('eslint.config.mjs')) {
|
|
20
|
+
pass('eslint.config.mjs існує')
|
|
21
|
+
} else {
|
|
22
|
+
fail('Відсутній eslint.config.js — створи його з getConfig від @nitra/eslint-config')
|
|
23
|
+
}
|
|
21
24
|
|
|
22
25
|
if (existsSync('package.json')) {
|
|
23
26
|
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
24
27
|
|
|
25
|
-
pkg.scripts?.['lint-js']
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
if (pkg.scripts?.['lint-js']) {
|
|
29
|
+
pass('package.json містить скрипт lint-js')
|
|
30
|
+
} else {
|
|
31
|
+
fail('package.json не містить скрипт "lint-js" — додай: "oxlint --fix && bunx eslint --fix ."')
|
|
32
|
+
}
|
|
28
33
|
|
|
29
|
-
pkg.devDependencies?.['@nitra/eslint-config']
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
if (pkg.devDependencies?.['@nitra/eslint-config']) {
|
|
35
|
+
pass('@nitra/eslint-config є в devDependencies')
|
|
36
|
+
} else {
|
|
37
|
+
fail('@nitra/eslint-config відсутній в devDependencies — додай: bun add -d @nitra/eslint-config')
|
|
38
|
+
}
|
|
32
39
|
|
|
33
40
|
const nodeEngine = pkg.engines?.node
|
|
34
41
|
if (nodeEngine) {
|
|
@@ -46,8 +53,16 @@ export async function check() {
|
|
|
46
53
|
if (existsSync('.github/workflows/lint-js.yml')) {
|
|
47
54
|
const content = await readFile('.github/workflows/lint-js.yml', 'utf8')
|
|
48
55
|
pass('lint-js.yml існує')
|
|
49
|
-
content.includes('oxlint')
|
|
50
|
-
|
|
56
|
+
if (content.includes('oxlint')) {
|
|
57
|
+
pass('lint-js.yml містить oxlint')
|
|
58
|
+
} else {
|
|
59
|
+
fail('lint-js.yml не містить oxlint')
|
|
60
|
+
}
|
|
61
|
+
if (content.includes('eslint')) {
|
|
62
|
+
pass('lint-js.yml містить eslint')
|
|
63
|
+
} else {
|
|
64
|
+
fail('lint-js.yml не містить eslint')
|
|
65
|
+
}
|
|
51
66
|
} else {
|
|
52
67
|
fail('.github/workflows/lint-js.yml не існує — створи його')
|
|
53
68
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
3
|
|
|
4
|
+
import { pass } from './utils/pass.mjs'
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Перевіряє відповідність проєкту правилам js-pino.mdc
|
|
6
8
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
9
|
*/
|
|
8
10
|
export async function check() {
|
|
9
11
|
let exitCode = 0
|
|
10
|
-
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
12
|
const fail = msg => {
|
|
12
13
|
console.log(` ❌ ${msg}`)
|
|
13
14
|
exitCode = 1
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
3
|
|
|
4
|
+
import { pass } from './utils/pass.mjs'
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Перевіряє відповідність проєкту правилам nginx-default-tpl.mdc
|
|
6
8
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
9
|
*/
|
|
8
10
|
export async function check() {
|
|
9
11
|
let exitCode = 0
|
|
10
|
-
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
12
|
const fail = msg => {
|
|
12
13
|
console.log(` ❌ ${msg}`)
|
|
13
14
|
exitCode = 1
|
|
@@ -24,17 +25,23 @@ export async function check() {
|
|
|
24
25
|
pass(`${found} існує`)
|
|
25
26
|
const content = await readFile(found, 'utf8')
|
|
26
27
|
|
|
27
|
-
content.includes('listen 8080')
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
if (content.includes('listen 8080')) {
|
|
29
|
+
pass('Nginx слухає порт 8080')
|
|
30
|
+
} else {
|
|
31
|
+
fail(`${found}: має містити listen 8080`)
|
|
32
|
+
}
|
|
30
33
|
|
|
31
|
-
content.includes('/healthz')
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
if (content.includes('/healthz')) {
|
|
35
|
+
pass('Є location /healthz')
|
|
36
|
+
} else {
|
|
37
|
+
fail(`${found}: відсутній location /healthz`)
|
|
38
|
+
}
|
|
34
39
|
|
|
35
|
-
content.includes('gzip_static on')
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
if (content.includes('gzip_static on')) {
|
|
41
|
+
pass('gzip_static увімкнено')
|
|
42
|
+
} else {
|
|
43
|
+
fail(`${found}: має містити gzip_static on`)
|
|
44
|
+
}
|
|
38
45
|
|
|
39
46
|
if (content.includes('proxy_pass')) {
|
|
40
47
|
fail(`${found} містить proxy_pass — перенеси проксі-логіку до HTTPRoute в k8s`)
|
|
@@ -43,16 +50,20 @@ export async function check() {
|
|
|
43
50
|
|
|
44
51
|
if (existsSync('.vscode/extensions.json')) {
|
|
45
52
|
const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
|
|
46
|
-
ext.recommendations?.includes('ahmadalli.vscode-nginx-conf')
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
if (ext.recommendations?.includes('ahmadalli.vscode-nginx-conf')) {
|
|
54
|
+
pass('extensions.json містить ahmadalli.vscode-nginx-conf')
|
|
55
|
+
} else {
|
|
56
|
+
fail('extensions.json не містить ahmadalli.vscode-nginx-conf')
|
|
57
|
+
}
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
if (existsSync('.vscode/settings.json')) {
|
|
52
61
|
const s = JSON.parse(await readFile('.vscode/settings.json', 'utf8'))
|
|
53
|
-
s['[nginx]']?.['editor.defaultFormatter'] === 'ahmadalli.vscode-nginx-conf'
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
if (s['[nginx]']?.['editor.defaultFormatter'] === 'ahmadalli.vscode-nginx-conf') {
|
|
63
|
+
pass('settings.json: nginx formatter налаштовано')
|
|
64
|
+
} else {
|
|
65
|
+
fail('settings.json: [nginx] defaultFormatter має бути ahmadalli.vscode-nginx-conf')
|
|
66
|
+
}
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
return exitCode
|
|
@@ -1,23 +1,32 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { readFile, stat } from 'node:fs/promises'
|
|
3
3
|
|
|
4
|
+
import { pass } from './utils/pass.mjs'
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Перевіряє відповідність проєкту правилам npm-module.mdc
|
|
6
8
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
9
|
*/
|
|
8
10
|
export async function check() {
|
|
9
11
|
let exitCode = 0
|
|
10
|
-
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
12
|
const fail = msg => {
|
|
12
13
|
console.log(` ❌ ${msg}`)
|
|
13
14
|
exitCode = 1
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
existsSync('package.json')
|
|
17
|
+
if (existsSync('package.json')) {
|
|
18
|
+
pass('package.json існує')
|
|
19
|
+
} else {
|
|
20
|
+
fail('package.json не існує')
|
|
21
|
+
}
|
|
17
22
|
|
|
18
23
|
if (existsSync('npm')) {
|
|
19
24
|
const s = await stat('npm')
|
|
20
|
-
s.isDirectory()
|
|
25
|
+
if (s.isDirectory()) {
|
|
26
|
+
pass('npm/ директорія існує')
|
|
27
|
+
} else {
|
|
28
|
+
fail('npm має бути директорією')
|
|
29
|
+
}
|
|
21
30
|
} else {
|
|
22
31
|
fail('npm/ директорія не існує')
|
|
23
32
|
}
|
|
@@ -32,13 +41,17 @@ export async function check() {
|
|
|
32
41
|
}
|
|
33
42
|
}
|
|
34
43
|
|
|
35
|
-
existsSync('npm/package.json')
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
if (existsSync('npm/package.json')) {
|
|
45
|
+
pass('npm/package.json існує')
|
|
46
|
+
} else {
|
|
47
|
+
fail('npm/package.json не існує — створи package.json для npm модуля')
|
|
48
|
+
}
|
|
38
49
|
|
|
39
|
-
existsSync('.github/workflows')
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
if (existsSync('.github/workflows')) {
|
|
51
|
+
pass('.github/workflows/ існує')
|
|
52
|
+
} else {
|
|
53
|
+
fail('.github/workflows/ не існує')
|
|
54
|
+
}
|
|
42
55
|
|
|
43
56
|
return exitCode
|
|
44
57
|
}
|
package/scripts/check-spell.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
3
|
|
|
4
|
+
import { pass } from './utils/pass.mjs'
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Перевіряє відповідність проєкту правилам spell.mdc
|
|
6
8
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
9
|
*/
|
|
8
10
|
export async function check() {
|
|
9
11
|
let exitCode = 0
|
|
10
|
-
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
12
|
const fail = msg => {
|
|
12
13
|
console.log(` ❌ ${msg}`)
|
|
13
14
|
exitCode = 1
|
|
@@ -16,22 +17,30 @@ export async function check() {
|
|
|
16
17
|
if (existsSync('.cspell.json')) {
|
|
17
18
|
const cfg = JSON.parse(await readFile('.cspell.json', 'utf8'))
|
|
18
19
|
|
|
19
|
-
cfg.version === '0.2'
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
if (cfg.version === '0.2') {
|
|
21
|
+
pass('.cspell.json version: 0.2')
|
|
22
|
+
} else {
|
|
23
|
+
fail('.cspell.json version має бути "0.2"')
|
|
24
|
+
}
|
|
22
25
|
|
|
23
|
-
cfg.language
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
if (cfg.language) {
|
|
27
|
+
pass(`.cspell.json language: "${cfg.language}"`)
|
|
28
|
+
} else {
|
|
29
|
+
fail('.cspell.json не містить поле language')
|
|
30
|
+
}
|
|
26
31
|
|
|
27
32
|
const imports = cfg.import || []
|
|
28
|
-
imports.some(i => i.includes('@nitra/cspell-dict'))
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
if (imports.some(i => i.includes('@nitra/cspell-dict'))) {
|
|
34
|
+
pass('.cspell.json імпортує @nitra/cspell-dict')
|
|
35
|
+
} else {
|
|
36
|
+
fail('.cspell.json не імпортує @nitra/cspell-dict/cspell-ext.json')
|
|
37
|
+
}
|
|
31
38
|
|
|
32
|
-
Array.isArray(cfg.ignorePaths)
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
if (Array.isArray(cfg.ignorePaths)) {
|
|
40
|
+
pass('.cspell.json містить ignorePaths')
|
|
41
|
+
} else {
|
|
42
|
+
fail('.cspell.json не містить ignorePaths')
|
|
43
|
+
}
|
|
35
44
|
} else {
|
|
36
45
|
fail('.cspell.json не існує — створи його')
|
|
37
46
|
}
|
|
@@ -40,9 +49,29 @@ export async function check() {
|
|
|
40
49
|
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
41
50
|
const devDeps = pkg.devDependencies || {}
|
|
42
51
|
|
|
43
|
-
devDeps['@nitra/cspell-dict']
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
if (devDeps['@nitra/cspell-dict']) {
|
|
53
|
+
pass('@nitra/cspell-dict є в devDependencies')
|
|
54
|
+
} else {
|
|
55
|
+
fail('@nitra/cspell-dict відсутній — bun add -d @nitra/cspell-dict')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const lintSpell = pkg.scripts?.['lint-spell']
|
|
59
|
+
if (typeof lintSpell === 'string' && lintSpell.includes('cspell')) {
|
|
60
|
+
pass('package.json містить скрипт lint-spell з cspell')
|
|
61
|
+
} else {
|
|
62
|
+
fail('package.json не містить скрипт "lint-spell": "npx cspell ." (див. n-spell.mdc)')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (existsSync('.github/workflows/lint-spell.yml')) {
|
|
66
|
+
const wf = await readFile('.github/workflows/lint-spell.yml', 'utf8')
|
|
67
|
+
if (wf.includes('lint-spell')) {
|
|
68
|
+
pass('lint-spell.yml існує і викликає lint-spell')
|
|
69
|
+
} else {
|
|
70
|
+
fail('lint-spell.yml має містити виклик bun run lint-spell')
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
fail('.github/workflows/lint-spell.yml не існує — створи згідно n-spell.mdc')
|
|
74
|
+
}
|
|
46
75
|
|
|
47
76
|
if (existsSync('.cspell.json')) {
|
|
48
77
|
const cfg = JSON.parse(await readFile('.cspell.json', 'utf8'))
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
3
|
|
|
4
|
+
import { pass } from './utils/pass.mjs'
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Перевіряє відповідність проєкту правилам style-lint.mdc
|
|
6
8
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
9
|
*/
|
|
8
10
|
export async function check() {
|
|
9
11
|
let exitCode = 0
|
|
10
|
-
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
12
|
const fail = msg => {
|
|
12
13
|
console.log(` ❌ ${msg}`)
|
|
13
14
|
exitCode = 1
|
|
@@ -16,13 +17,17 @@ export async function check() {
|
|
|
16
17
|
if (existsSync('package.json')) {
|
|
17
18
|
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
18
19
|
|
|
19
|
-
pkg.scripts?.['lint-style']
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
if (pkg.scripts?.['lint-style']) {
|
|
21
|
+
pass('package.json містить скрипт lint-style')
|
|
22
|
+
} else {
|
|
23
|
+
fail('package.json не містить скрипт "lint-style"')
|
|
24
|
+
}
|
|
22
25
|
|
|
23
|
-
pkg.devDependencies?.['@nitra/stylelint-config']
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
if (pkg.devDependencies?.['@nitra/stylelint-config']) {
|
|
27
|
+
pass('@nitra/stylelint-config є в devDependencies')
|
|
28
|
+
} else {
|
|
29
|
+
fail('@nitra/stylelint-config відсутній — bun add -d @nitra/stylelint-config')
|
|
30
|
+
}
|
|
26
31
|
|
|
27
32
|
const stylelintCfg = pkg.stylelint
|
|
28
33
|
if (stylelintCfg?.extends === '@nitra/stylelint-config') {
|
|
@@ -34,33 +39,47 @@ export async function check() {
|
|
|
34
39
|
}
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
existsSync('.stylelintignore')
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
if (existsSync('.stylelintignore')) {
|
|
43
|
+
pass('.stylelintignore існує')
|
|
44
|
+
} else {
|
|
45
|
+
fail('.stylelintignore не існує — створи з вмістом: dist/')
|
|
46
|
+
}
|
|
40
47
|
|
|
41
48
|
if (existsSync('.github/workflows/lint-style.yml')) {
|
|
42
49
|
const content = await readFile('.github/workflows/lint-style.yml', 'utf8')
|
|
43
50
|
pass('lint-style.yml існує')
|
|
44
|
-
content.includes('stylelint')
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
if (content.includes('stylelint')) {
|
|
52
|
+
pass('lint-style.yml містить stylelint')
|
|
53
|
+
} else {
|
|
54
|
+
fail('lint-style.yml не містить виклик stylelint')
|
|
55
|
+
}
|
|
47
56
|
} else {
|
|
48
57
|
fail('.github/workflows/lint-style.yml не існує — створи його')
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
if (existsSync('.vscode/extensions.json')) {
|
|
52
61
|
const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
|
|
53
|
-
ext.recommendations?.includes('stylelint.vscode-stylelint')
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
if (ext.recommendations?.includes('stylelint.vscode-stylelint')) {
|
|
63
|
+
pass('extensions.json містить stylelint.vscode-stylelint')
|
|
64
|
+
} else {
|
|
65
|
+
fail('extensions.json не містить stylelint.vscode-stylelint')
|
|
66
|
+
}
|
|
56
67
|
} else {
|
|
57
68
|
fail('.vscode/extensions.json не існує')
|
|
58
69
|
}
|
|
59
70
|
|
|
60
71
|
if (existsSync('.vscode/settings.json')) {
|
|
61
72
|
const s = JSON.parse(await readFile('.vscode/settings.json', 'utf8'))
|
|
62
|
-
s['css.validate'] === false
|
|
63
|
-
|
|
73
|
+
if (s['css.validate'] === false) {
|
|
74
|
+
pass('css.validate вимкнено')
|
|
75
|
+
} else {
|
|
76
|
+
fail('settings.json: css.validate має бути false')
|
|
77
|
+
}
|
|
78
|
+
if (s['scss.validate'] === false) {
|
|
79
|
+
pass('scss.validate вимкнено')
|
|
80
|
+
} else {
|
|
81
|
+
fail('settings.json: scss.validate має бути false')
|
|
82
|
+
}
|
|
64
83
|
}
|
|
65
84
|
|
|
66
85
|
return exitCode
|
package/scripts/check-vue.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
3
|
|
|
4
|
+
import { pass } from './utils/pass.mjs'
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Перевіряє відповідність проєкту правилам vue.mdc
|
|
6
8
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
9
|
*/
|
|
8
10
|
export async function check() {
|
|
9
11
|
let exitCode = 0
|
|
10
|
-
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
12
|
const fail = msg => {
|
|
12
13
|
console.log(` ❌ ${msg}`)
|
|
13
14
|
exitCode = 1
|
|
@@ -15,9 +16,11 @@ export async function check() {
|
|
|
15
16
|
|
|
16
17
|
if (existsSync('.vscode/extensions.json')) {
|
|
17
18
|
const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
|
|
18
|
-
ext.recommendations?.includes('Vue.volar')
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
if (ext.recommendations?.includes('Vue.volar')) {
|
|
20
|
+
pass('extensions.json містить Vue.volar')
|
|
21
|
+
} else {
|
|
22
|
+
fail('extensions.json не містить Vue.volar — додай до recommendations')
|
|
23
|
+
}
|
|
21
24
|
} else {
|
|
22
25
|
fail('.vscode/extensions.json не існує')
|
|
23
26
|
}
|
|
@@ -28,7 +31,11 @@ export async function check() {
|
|
|
28
31
|
const devDeps = pkg.devDependencies || {}
|
|
29
32
|
const allDeps = { ...deps, ...devDeps }
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
if (deps.vue) {
|
|
35
|
+
pass(`vue в dependencies: ${deps.vue}`)
|
|
36
|
+
} else {
|
|
37
|
+
fail('vue відсутній в dependencies')
|
|
38
|
+
}
|
|
32
39
|
|
|
33
40
|
if (devDeps.vite) {
|
|
34
41
|
const match = devDeps.vite.match(/(\d+)/)
|
|
@@ -41,29 +48,45 @@ export async function check() {
|
|
|
41
48
|
fail('vite відсутній в devDependencies')
|
|
42
49
|
}
|
|
43
50
|
|
|
44
|
-
devDeps['@vitejs/plugin-vue']
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
if (devDeps['@vitejs/plugin-vue']) {
|
|
52
|
+
pass(`@vitejs/plugin-vue: ${devDeps['@vitejs/plugin-vue']}`)
|
|
53
|
+
} else {
|
|
54
|
+
fail('@vitejs/plugin-vue відсутній в devDependencies')
|
|
55
|
+
}
|
|
47
56
|
|
|
48
|
-
allDeps['vue-macros']
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
if (allDeps['vue-macros']) {
|
|
58
|
+
pass(`vue-macros: ${allDeps['vue-macros']}`)
|
|
59
|
+
} else {
|
|
60
|
+
fail('vue-macros відсутній — bun add -d vue-macros')
|
|
61
|
+
}
|
|
51
62
|
|
|
52
|
-
allDeps['unplugin-auto-import']
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
if (allDeps['unplugin-auto-import']) {
|
|
64
|
+
pass('unplugin-auto-import присутній')
|
|
65
|
+
} else {
|
|
66
|
+
fail('unplugin-auto-import відсутній — bun add -d unplugin-auto-import')
|
|
67
|
+
}
|
|
55
68
|
|
|
56
|
-
allDeps['vite-plugin-vue-layouts-next']
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
if (allDeps['vite-plugin-vue-layouts-next']) {
|
|
70
|
+
pass('vite-plugin-vue-layouts-next присутній')
|
|
71
|
+
} else {
|
|
72
|
+
fail('vite-plugin-vue-layouts-next відсутній — bun add -d vite-plugin-vue-layouts-next')
|
|
73
|
+
}
|
|
59
74
|
}
|
|
60
75
|
|
|
61
76
|
const configFiles = ['vite.config.js', 'vite.config.ts', 'vite.config.mjs']
|
|
62
77
|
const viteConfig = configFiles.find(f => existsSync(f))
|
|
63
78
|
if (viteConfig) {
|
|
64
79
|
const content = await readFile(viteConfig, 'utf8')
|
|
65
|
-
content.includes('VueMacros')
|
|
66
|
-
|
|
80
|
+
if (content.includes('VueMacros')) {
|
|
81
|
+
pass('vite.config використовує VueMacros')
|
|
82
|
+
} else {
|
|
83
|
+
fail(`${viteConfig} не містить VueMacros`)
|
|
84
|
+
}
|
|
85
|
+
if (content.includes('AutoImport')) {
|
|
86
|
+
pass('vite.config використовує AutoImport')
|
|
87
|
+
} else {
|
|
88
|
+
fail(`${viteConfig} не містить AutoImport`)
|
|
89
|
+
}
|
|
67
90
|
} else {
|
|
68
91
|
fail('vite.config.js не існує')
|
|
69
92
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: n-fix
|
|
2
|
+
name: n-fix
|
|
3
3
|
description: >-
|
|
4
4
|
Виправити проєкт відповідно до всіх правил в .cursor/rules/
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
#
|
|
7
|
+
# n-fix — автоматичне виправлення проєкту
|
|
8
8
|
|
|
9
9
|
## Workflow
|
|
10
10
|
|
|
@@ -43,6 +43,10 @@ bun run lint-js
|
|
|
43
43
|
bun run lint-style
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
На помилках: auto-fix (якщо є), інакше правки в коді/конфігах; повторюй доки скрипт завершується з `0`.
|
|
47
|
+
|
|
48
|
+
Підсумок: Перелічи запущені скрипти та зміни; опиши блокери, якщо лишились.
|
|
49
|
+
|
|
46
50
|
7. **Верифікація** — перевір що все виправлено:
|
|
47
51
|
|
|
48
52
|
```bash
|
package/skills/n-lint/SKILL.md
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: n-lint
|
|
3
|
-
description: >-
|
|
4
|
-
Запускає всі скрипти lint-* з package.json і виправляє зауваження лінтерів (fix + ручні правки)
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# n-lint — усі lint-скрипти та нуль зауважень
|
|
8
|
-
|
|
9
|
-
## Мета
|
|
10
|
-
|
|
11
|
-
Виконати кожен скрипт з префіксом `lint-` у `package.json` і усунути всі зауваження лінтерів.
|
|
12
|
-
|
|
13
|
-
## Охоплення
|
|
14
|
-
|
|
15
|
-
1. Корінь: усі `lint-*` з `scripts`.
|
|
16
|
-
2. Якщо є `workspaces` — те саме в кожному workspace-пакеті; `bun run` з каталогу цього пакета.
|
|
17
|
-
|
|
18
|
-
## Кроки
|
|
19
|
-
|
|
20
|
-
1. За потреби: `bun i` у корені / у пакеті.
|
|
21
|
-
2. Для кожного `lint-*`: `bun run <ім'я>` з відповідного каталогу.
|
|
22
|
-
3. На помилках: auto-fix (якщо є), інакше правки в коді/конфігах; повторюй доки скрипт завершується з `0`.
|
|
23
|
-
|
|
24
|
-
## Підсумок
|
|
25
|
-
|
|
26
|
-
Перелічи запущені скрипти та зміни; опиши блокери, якщо лишились.
|