@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 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 (!name.endsWith('.mdc') || !name.startsWith('nitra-')) {
111
- continue
112
- }
113
- const rest = name.slice('nitra-'.length)
114
- const newName = `${RULE_PREFIX}${rest}`
115
- const from = join(rulesDir, name)
116
- const to = join(rulesDir, newName)
117
- if (existsSync(to)) {
118
- await unlink(from)
119
- console.log(`📝 Видалено застарілий ${RULES_DIR}/${name} (вже є ${newName})\n`)
120
- continue
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} має бути непоророжній масив "rules"`)
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-cursor»)
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-cursor
236
+ * @returns {string} наприклад n-fix
223
237
  */
224
238
  function managedSkillDirName(skillId) {
225
- return `${RULE_PREFIX}${normalizeSkillId(skillId)}`
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(` ⬇ ${skillId} → ${SKILLS_DIR}/${dirName} ... `)
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(` ⬇ ${skillId} → ${SKILLS_DIR}/${dirName} ... `)
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`, то прибрати їх, також прибрати всі директорії та файлидля yarn
48
+ Якщо в package.json є поля `packageManager`, то прибрати їх, також прибрати всі директорії та файли для yarn
49
49
 
50
50
  Якщо в проекті використовується npx, то не заміняти його на bunx, а використовувати npx.
51
51
  Коли зміна відбувається в Dockerfile, то використовувати
@@ -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. Якщо в репозиторії є **`n-lint`** / навичка n-lint — можна пройтись повним набором `lint-*` з `package.json`.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -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) ? fail(`Знайдено заборонений файл: ${f} — видали його`) : pass(`Немає ${f}`)
19
+ if (existsSync(f)) {
20
+ fail(`Знайдено заборонений файл: ${f} — видали його`)
21
+ } else {
22
+ pass(`Немає ${f}`)
23
+ }
19
24
  }
20
25
 
21
- existsSync('.yarn') ? fail('Знайдено директорію .yarn — видали її') : pass('Немає .yarn/')
22
- existsSync('bun.lock') ? pass('bun.lock є') : fail('Відсутній bun.lock запусти bun i')
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
- ? fail(`package.json містить поле packageManager: "${pkg.packageManager}" — видали його`)
28
- : pass('package.json не містить packageManager')
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
@@ -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) ? pass(`${f} існує`) : fail(`Відсутній ${wfDir}/${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
- ? pass('apply-k8s.yml має правильний paths trigger')
40
- : fail('apply-k8s.yml не містить paths: **/k8s/*.yaml')
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
- ? pass('apply-nats-consumer.yml має правильний paths trigger')
47
- : fail('apply-nats-consumer.yml не містить paths: **/consumer.yaml')
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
- ? pass('extensions.json містить github.vscode-github-actions')
54
- : fail('extensions.json не містить github.vscode-github-actions')
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', 'printWidth', 'bracketSpacing', 'bracketSameLine',
18
- 'semi', 'singleQuote', 'tabWidth', 'trailingComma', 'useTabs'
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
- ? pass('.oxfmtrc.json містить всі обовʼязкові ключі')
26
- : fail(`.oxfmtrc.json відсутні ключі: ${missing.join(', ')}`)
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
- ? pass('extensions.json містить oxc.oxc-vscode')
41
- : fail('extensions.json не містить oxc.oxc-vscode')
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
- ? pass('settings.json: editor.formatOnSave увімкнено')
50
- : fail('settings.json: editor.formatOnSave має бути true')
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
- ? pass('eslint.config.js існує')
18
- : existsSync('eslint.config.mjs')
19
- ? pass('eslint.config.mjs існує')
20
- : fail('Відсутній eslint.config.js — створи його з getConfig від @nitra/eslint-config')
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
- ? pass('package.json містить скрипт lint-js')
27
- : fail('package.json не містить скрипт "lint-js" — додай: "oxlint --fix && bunx eslint --fix ."')
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
- ? pass('@nitra/eslint-config є в devDependencies')
31
- : fail('@nitra/eslint-config відсутній в devDependencies — додай: bun add -d @nitra/eslint-config')
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') ? pass('lint-js.yml містить oxlint') : fail('lint-js.yml не містить oxlint')
50
- content.includes('eslint') ? pass('lint-js.yml містить eslint') : fail('lint-js.yml не містить eslint')
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
- ? pass('Nginx слухає порт 8080')
29
- : fail(`${found}: має містити listen 8080`)
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
- ? pass('Є location /healthz')
33
- : fail(`${found}: відсутній location /healthz`)
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
- ? pass('gzip_static увімкнено')
37
- : fail(`${found}: має містити gzip_static on`)
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
- ? pass('extensions.json містить ahmadalli.vscode-nginx-conf')
48
- : fail('extensions.json не містить ahmadalli.vscode-nginx-conf')
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
- ? pass('settings.json: nginx formatter налаштовано')
55
- : fail('settings.json: [nginx] defaultFormatter має бути ahmadalli.vscode-nginx-conf')
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') ? pass('package.json існує') : fail('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() ? pass('npm/ директорія існує') : fail('npm має бути директорією')
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
- ? pass('npm/package.json існує')
37
- : fail('npm/package.json не існує — створи package.json для npm модуля')
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
- ? pass('.github/workflows/ існує')
41
- : fail('.github/workflows/ не існує')
50
+ if (existsSync('.github/workflows')) {
51
+ pass('.github/workflows/ існує')
52
+ } else {
53
+ fail('.github/workflows/ не існує')
54
+ }
42
55
 
43
56
  return exitCode
44
57
  }
@@ -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
- ? pass('.cspell.json version: 0.2')
21
- : fail('.cspell.json version має бути "0.2"')
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
- ? pass(`.cspell.json language: "${cfg.language}"`)
25
- : fail('.cspell.json не містить поле language')
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
- ? pass('.cspell.json імпортує @nitra/cspell-dict')
30
- : fail('.cspell.json не імпортує @nitra/cspell-dict/cspell-ext.json')
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
- ? pass('.cspell.json містить ignorePaths')
34
- : fail('.cspell.json не містить ignorePaths')
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
- ? pass('@nitra/cspell-dict є в devDependencies')
45
- : fail('@nitra/cspell-dict відсутній — bun add -d @nitra/cspell-dict')
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
- ? pass('package.json містить скрипт lint-style')
21
- : fail('package.json не містить скрипт "lint-style"')
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
- ? pass('@nitra/stylelint-config є в devDependencies')
25
- : fail('@nitra/stylelint-config відсутній — bun add -d @nitra/stylelint-config')
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
- ? pass('.stylelintignore існує')
39
- : fail('.stylelintignore не існує — створи з вмістом: dist/')
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
- ? pass('lint-style.yml містить stylelint')
46
- : fail('lint-style.yml не містить виклик stylelint')
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
- ? pass('extensions.json містить stylelint.vscode-stylelint')
55
- : fail('extensions.json не містить stylelint.vscode-stylelint')
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 ? pass('css.validate вимкнено') : fail('settings.json: css.validate має бути false')
63
- s['scss.validate'] === false ? pass('scss.validate вимкнено') : fail('settings.json: scss.validate має бути false')
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
@@ -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
- ? pass('extensions.json містить Vue.volar')
20
- : fail('extensions.json не містить Vue.volar — додай до recommendations')
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
- deps.vue ? pass(`vue в dependencies: ${deps.vue}`) : fail('vue відсутній в dependencies')
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
- ? pass(`@vitejs/plugin-vue: ${devDeps['@vitejs/plugin-vue']}`)
46
- : fail('@vitejs/plugin-vue відсутній в devDependencies')
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
- ? pass(`vue-macros: ${allDeps['vue-macros']}`)
50
- : fail('vue-macros відсутній — bun add -d vue-macros')
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
- ? pass('unplugin-auto-import присутній')
54
- : fail('unplugin-auto-import відсутній — bun add -d unplugin-auto-import')
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
- ? pass('vite-plugin-vue-layouts-next присутній')
58
- : fail('vite-plugin-vue-layouts-next відсутній — bun add -d vite-plugin-vue-layouts-next')
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') ? pass('vite.config використовує VueMacros') : fail(`${viteConfig} не містить VueMacros`)
66
- content.includes('AutoImport') ? pass('vite.config використовує AutoImport') : fail(`${viteConfig} не містить AutoImport`)
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
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Логує успішний результат перевірки (рядок з галочкою)
3
+ * @param {string} msg текст повідомлення
4
+ */
5
+ export function pass(msg) {
6
+ console.log(` ✅ ${msg}`)
7
+ }
@@ -1,10 +1,10 @@
1
1
  ---
2
- name: n-fix-cursor
2
+ name: n-fix
3
3
  description: >-
4
4
  Виправити проєкт відповідно до всіх правил в .cursor/rules/
5
5
  ---
6
6
 
7
- # Fix Cursor — автоматичне виправлення проєкту
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
@@ -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
- Перелічи запущені скрипти та зміни; опиши блокери, якщо лишились.