@nitra/cursor 1.8.104 → 1.8.106
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/auto-rules.md +45 -0
- package/bin/n-cursor.js +270 -149
- package/mdc/graphql.mdc +15 -1
- package/mdc/k8s.mdc +11 -10
- package/package.json +1 -1
- package/schemas/n-cursor.json +16 -0
- package/scripts/auto-rules.mjs +404 -0
- package/scripts/check-abie.mjs +558 -553
- package/scripts/check-bun.mjs +106 -82
- package/scripts/check-ga.mjs +151 -119
- package/scripts/check-graphql.mjs +112 -34
- package/scripts/check-js-lint.mjs +267 -186
- package/scripts/check-k8s.mjs +1148 -673
- package/scripts/check-nginx-default-tpl.mjs +125 -100
- package/scripts/check-npm-module.mjs +165 -118
- package/scripts/check-style-lint.mjs +74 -61
- package/scripts/check-text.mjs +288 -210
- package/scripts/check-vue.mjs +110 -69
- package/scripts/utils/docker-hadolint.mjs +9 -5
- package/scripts/utils/gha-workflow.mjs +92 -72
- package/scripts/utils/workspaces.mjs +39 -16
package/scripts/check-text.mjs
CHANGED
|
@@ -70,79 +70,109 @@ function verifyUkApostropheRuleParagraph(filePath, body, failFn, passFn) {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
|
-
* Перевіряє
|
|
74
|
-
* @
|
|
73
|
+
* Перевіряє .v8rignore.
|
|
74
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
75
|
+
* @param {(msg: string) => void} failFn callback при помилці
|
|
75
76
|
*/
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
77
|
+
async function checkV8rIgnore(passFn, failFn) {
|
|
78
|
+
const required = ['.vscode/extensions.json', '.vscode/settings.json']
|
|
79
|
+
if (!existsSync('.v8rignore')) {
|
|
80
|
+
failFn('.v8rignore не існує — створи згідно n-text.mdc (мінімум .vscode/extensions.json і .vscode/settings.json)')
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
const raw = await readFile('.v8rignore', 'utf8')
|
|
84
|
+
const lines = new Set(
|
|
85
|
+
raw
|
|
86
|
+
.split('\n')
|
|
87
|
+
.map(l => l.trim())
|
|
88
|
+
.filter(l => l.length > 0 && !l.startsWith('#'))
|
|
89
|
+
)
|
|
90
|
+
for (const path of required) {
|
|
91
|
+
if (lines.has(path)) {
|
|
92
|
+
passFn(`.v8rignore містить ${path}`)
|
|
93
|
+
} else {
|
|
94
|
+
failFn(`.v8rignore: додай рядок "${path}" (JSON без схеми в Schema Store — див. n-text.mdc)`)
|
|
95
95
|
}
|
|
96
|
-
} else {
|
|
97
|
-
fail('.v8rignore не існує — створи згідно n-text.mdc (мінімум .vscode/extensions.json і .vscode/settings.json)')
|
|
98
96
|
}
|
|
97
|
+
}
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Перевіряє VSCode extensions.json для текстового стека.
|
|
101
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
102
|
+
* @param {(msg: string) => void} failFn callback при помилці
|
|
103
|
+
*/
|
|
104
|
+
async function checkVscodeTextExtensions(passFn, failFn) {
|
|
105
|
+
if (!existsSync('.vscode/extensions.json')) {
|
|
106
|
+
failFn('.vscode/extensions.json не існує — створи з recommendations згідно n-text.mdc')
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
|
|
111
|
+
const rec = ext.recommendations
|
|
112
|
+
for (const id of ['DavidAnson.vscode-markdownlint', 'oxc.oxc-vscode']) {
|
|
113
|
+
if (Array.isArray(rec) && rec.includes(id)) {
|
|
114
|
+
passFn(`extensions.json містить ${id}`)
|
|
111
115
|
} else {
|
|
112
|
-
|
|
116
|
+
failFn(`extensions.json: додай "${id}" у recommendations (див. n-text.mdc)`)
|
|
113
117
|
}
|
|
114
|
-
} catch {
|
|
115
|
-
fail('.vscode/extensions.json — невалідний JSON')
|
|
116
118
|
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
+
} catch {
|
|
120
|
+
failFn('.vscode/extensions.json — невалідний JSON')
|
|
119
121
|
}
|
|
122
|
+
}
|
|
120
123
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Перевіряє VSCode settings.json для текстового стека.
|
|
126
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
127
|
+
* @param {(msg: string) => void} failFn callback при помилці
|
|
128
|
+
*/
|
|
129
|
+
async function checkVscodeTextSettings(passFn, failFn) {
|
|
130
|
+
if (!existsSync('.vscode/settings.json')) {
|
|
131
|
+
failFn('.vscode/settings.json не існує — створи згідно n-text.mdc')
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const settings = JSON.parse(await readFile('.vscode/settings.json', 'utf8'))
|
|
136
|
+
if (settings['editor.formatOnSave'] === true) {
|
|
137
|
+
passFn('settings.json: editor.formatOnSave увімкнено')
|
|
138
|
+
} else {
|
|
139
|
+
failFn('settings.json: editor.formatOnSave має бути true')
|
|
140
|
+
}
|
|
141
|
+
for (const t of ['javascript', 'typescript', 'json', 'vue', 'css', 'html']) {
|
|
142
|
+
const key = `[${t}]`
|
|
143
|
+
if (settings[key]?.['editor.defaultFormatter'] === 'oxc.oxc-vscode') {
|
|
144
|
+
passFn(`settings.json: ${key} використовує oxc.oxc-vscode`)
|
|
126
145
|
} else {
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
const fmtTypes = ['javascript', 'typescript', 'json', 'vue', 'css', 'html']
|
|
130
|
-
for (const t of fmtTypes) {
|
|
131
|
-
const key = `[${t}]`
|
|
132
|
-
if (settings[key]?.['editor.defaultFormatter'] === 'oxc.oxc-vscode') {
|
|
133
|
-
pass(`settings.json: ${key} використовує oxc.oxc-vscode`)
|
|
134
|
-
} else {
|
|
135
|
-
fail(`settings.json: ${key} має використовувати oxc.oxc-vscode як defaultFormatter`)
|
|
136
|
-
}
|
|
146
|
+
failFn(`settings.json: ${key} має використовувати oxc.oxc-vscode як defaultFormatter`)
|
|
137
147
|
}
|
|
138
|
-
} catch {
|
|
139
|
-
fail('.vscode/settings.json — невалідний JSON')
|
|
140
148
|
}
|
|
141
|
-
}
|
|
142
|
-
|
|
149
|
+
} catch {
|
|
150
|
+
failFn('.vscode/settings.json — невалідний JSON')
|
|
143
151
|
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Перевіряє VSCode extensions.json та settings.json для текстового стека.
|
|
156
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
157
|
+
* @param {(msg: string) => void} failFn callback при помилці
|
|
158
|
+
*/
|
|
159
|
+
async function checkVscodeText(passFn, failFn) {
|
|
160
|
+
await checkVscodeTextExtensions(passFn, failFn)
|
|
161
|
+
await checkVscodeTextSettings(passFn, failFn)
|
|
162
|
+
}
|
|
144
163
|
|
|
145
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Перевіряє .oxfmtrc.json.
|
|
166
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
167
|
+
* @param {(msg: string) => void} failFn callback при помилці
|
|
168
|
+
*/
|
|
169
|
+
async function checkOxfmtRc(passFn, failFn) {
|
|
170
|
+
if (!existsSync('.oxfmtrc.json')) {
|
|
171
|
+
failFn('.oxfmtrc.json не існує — створи його')
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
const cfg = JSON.parse(await readFile('.oxfmtrc.json', 'utf8'))
|
|
175
|
+
const requiredKeys = [
|
|
146
176
|
'arrowParens',
|
|
147
177
|
'printWidth',
|
|
148
178
|
'bracketSpacing',
|
|
@@ -153,190 +183,238 @@ export async function check() {
|
|
|
153
183
|
'trailingComma',
|
|
154
184
|
'useTabs'
|
|
155
185
|
]
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (cfg.useTabs !== false) fail('.oxfmtrc.json: useTabs має бути false')
|
|
168
|
-
if (cfg.printWidth !== 120) fail('.oxfmtrc.json: printWidth має бути 120')
|
|
186
|
+
const missing = requiredKeys.filter(k => !(k in cfg))
|
|
187
|
+
if (missing.length === 0) {
|
|
188
|
+
passFn('.oxfmtrc.json містить всі обовʼязкові ключі')
|
|
189
|
+
} else {
|
|
190
|
+
failFn(`.oxfmtrc.json відсутні ключі: ${missing.join(', ')}`)
|
|
191
|
+
}
|
|
192
|
+
if (cfg.semi !== false) failFn('.oxfmtrc.json: semi має бути false')
|
|
193
|
+
if (cfg.singleQuote !== true) failFn('.oxfmtrc.json: singleQuote має бути true')
|
|
194
|
+
if (cfg.tabWidth !== 2) failFn('.oxfmtrc.json: tabWidth має бути 2')
|
|
195
|
+
if (cfg.useTabs !== false) failFn('.oxfmtrc.json: useTabs має бути false')
|
|
196
|
+
if (cfg.printWidth !== 120) failFn('.oxfmtrc.json: printWidth має бути 120')
|
|
169
197
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
} else {
|
|
176
|
-
fail(
|
|
177
|
-
`.oxfmtrc.json ignorePatterns: додай відсутні елементи: ${missing.join(', ')} (канонічний приклад у text.mdc)`
|
|
178
|
-
)
|
|
179
|
-
}
|
|
198
|
+
if (Array.isArray(cfg.ignorePatterns)) {
|
|
199
|
+
const set = new Set(cfg.ignorePatterns)
|
|
200
|
+
const missingPatterns = OXFMT_REQUIRED_IGNORE_PATTERNS.filter(p => !set.has(p))
|
|
201
|
+
if (missingPatterns.length === 0) {
|
|
202
|
+
passFn('.oxfmtrc.json: ignorePatterns містить hasura/metadata та schema.graphql')
|
|
180
203
|
} else {
|
|
181
|
-
|
|
182
|
-
`.oxfmtrc.json: додай
|
|
204
|
+
failFn(
|
|
205
|
+
`.oxfmtrc.json ignorePatterns: додай відсутні елементи: ${missingPatterns.join(', ')} (канонічний приклад у text.mdc)`
|
|
183
206
|
)
|
|
184
207
|
}
|
|
185
208
|
} else {
|
|
186
|
-
|
|
209
|
+
failFn(`.oxfmtrc.json: додай масив ignorePatterns з ${OXFMT_REQUIRED_IGNORE_PATTERNS.join(', ')} (див. text.mdc)`)
|
|
187
210
|
}
|
|
211
|
+
}
|
|
188
212
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
213
|
+
/**
|
|
214
|
+
* Перевіряє залежності package.json для текстового стека.
|
|
215
|
+
* @param {{ dependencies?: Record<string, string>, devDependencies?: Record<string, string>, prettier?: unknown }} pkg розібраний package.json
|
|
216
|
+
* @param {Record<string, string>} devDeps devDependencies з package.json
|
|
217
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
218
|
+
* @param {(msg: string) => void} failFn callback при помилці
|
|
219
|
+
*/
|
|
220
|
+
function checkPackageJsonTextDepsUsage(pkg, devDeps, passFn, failFn) {
|
|
221
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
222
|
+
for (const dep of ['prettier', '@nitra/prettier-config']) {
|
|
223
|
+
if (allDeps[dep]) failFn(`package.json містить залежність ${dep} — видали її`)
|
|
192
224
|
}
|
|
225
|
+
if (pkg.prettier) failFn('package.json містить поле "prettier" — видали його')
|
|
193
226
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
pass('.markdownlint-cli2.jsonc: gitignore увімкнено')
|
|
200
|
-
} else {
|
|
201
|
-
fail('.markdownlint-cli2.jsonc: додай на верхньому рівні "gitignore": true (див. n-text.mdc)')
|
|
202
|
-
}
|
|
203
|
-
} catch {
|
|
204
|
-
fail('.markdownlint-cli2.jsonc — невалідний JSON; перевір синтаксис')
|
|
205
|
-
}
|
|
227
|
+
const nonNitraDev = Object.keys(devDeps).filter(n => !isAllowedRootDevDependency(n))
|
|
228
|
+
if (nonNitraDev.length > 0) {
|
|
229
|
+
failFn(
|
|
230
|
+
`Кореневі devDependencies: дозволені лише @nitra/* — прибери або перенеси: ${nonNitraDev.join(', ')} (bun.mdc)`
|
|
231
|
+
)
|
|
206
232
|
} else {
|
|
207
|
-
|
|
233
|
+
passFn('Кореневі devDependencies лише @nitra/*')
|
|
208
234
|
}
|
|
209
235
|
|
|
210
|
-
|
|
211
|
-
|
|
236
|
+
const cspellRange = devDeps['@nitra/cspell-dict']
|
|
237
|
+
if (!cspellRange) {
|
|
238
|
+
failFn('@nitra/cspell-dict у devDependencies обовʼязковий для cspell — bun add -d @nitra/cspell-dict@^2.0.0')
|
|
239
|
+
} else if (cspellDictVersionAtLeast200(cspellRange)) {
|
|
240
|
+
passFn('@nitra/cspell-dict ^2.0.0+')
|
|
241
|
+
} else {
|
|
242
|
+
failFn('@nitra/cspell-dict має бути ^2.0.0 або новіший (словники зібрані в пакеті з 2.x)')
|
|
243
|
+
}
|
|
212
244
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
245
|
+
if (devDeps['markdownlint-cli2'] || (pkg.dependencies || {})['markdownlint-cli2']) {
|
|
246
|
+
failFn(
|
|
247
|
+
'markdownlint-cli2 не додавай у dependencies/devDependencies — лише bunx у lint-text (n-text.mdc); прибери з package.json і bun i'
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
218
251
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
252
|
+
/**
|
|
253
|
+
* Перевіряє відсутність прямих імпортів `@cspell/dict-*` у .cspell.json.
|
|
254
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
255
|
+
* @param {(msg: string) => void} failFn callback при помилці
|
|
256
|
+
*/
|
|
257
|
+
async function checkCspellJsonDictImports(passFn, failFn) {
|
|
258
|
+
if (!existsSync('.cspell.json')) return
|
|
259
|
+
const cfg = JSON.parse(await readFile('.cspell.json', 'utf8'))
|
|
260
|
+
const dictImports = (cfg.import || []).filter(i => typeof i === 'string' && i.includes('@cspell/dict-'))
|
|
261
|
+
if (dictImports.length > 0) {
|
|
262
|
+
failFn(
|
|
263
|
+
`.cspell.json не має імпортувати @cspell/dict-* (${dictImports.join(', ')}) — використовуй лише @nitra/cspell-dict/cspell-ext.json`
|
|
264
|
+
)
|
|
265
|
+
} else {
|
|
266
|
+
passFn('.cspell.json без прямих імпортів @cspell/dict-*')
|
|
267
|
+
}
|
|
268
|
+
}
|
|
224
269
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
270
|
+
/**
|
|
271
|
+
* Перевіряє package.json для текстового стека.
|
|
272
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
273
|
+
* @param {(msg: string) => void} failFn callback при помилці
|
|
274
|
+
*/
|
|
275
|
+
async function checkPackageJsonText(passFn, failFn) {
|
|
276
|
+
if (!existsSync('package.json')) return
|
|
277
|
+
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
278
|
+
const devDeps = pkg.devDependencies || {}
|
|
231
279
|
|
|
232
|
-
|
|
233
|
-
|
|
280
|
+
checkPackageJsonTextDepsUsage(pkg, devDeps, passFn, failFn)
|
|
281
|
+
checkLintTextScript(pkg.scripts?.['lint-text'], passFn, failFn)
|
|
282
|
+
|
|
283
|
+
if (existsSync('.github/workflows/lint-text.yml')) {
|
|
284
|
+
const wf = await readFile('.github/workflows/lint-text.yml', 'utf8')
|
|
285
|
+
const root = parseWorkflowYaml(wf)
|
|
286
|
+
const ok = root ? anyRunStepIncludes(root, 'bun run lint-text') : wf.includes('bun run lint-text')
|
|
287
|
+
if (ok) {
|
|
288
|
+
passFn('lint-text.yml викликає bun run lint-text')
|
|
234
289
|
} else {
|
|
235
|
-
|
|
290
|
+
failFn('lint-text.yml має містити крок bun run lint-text')
|
|
236
291
|
}
|
|
237
292
|
} else {
|
|
238
|
-
|
|
293
|
+
failFn('.github/workflows/lint-text.yml не існує — створи згідно n-text.mdc')
|
|
239
294
|
}
|
|
240
295
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
296
|
+
await checkCspellJsonDictImports(passFn, failFn)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Перевіряє скрипт lint-text на коректність v8r-виклику.
|
|
301
|
+
* @param {unknown} lintText параметр lintText
|
|
302
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
303
|
+
* @param {(msg: string) => void} failFn callback при помилці
|
|
304
|
+
*/
|
|
305
|
+
function checkLintTextScript(lintText, passFn, failFn) {
|
|
306
|
+
const lt = typeof lintText === 'string' ? lintText : ''
|
|
307
|
+
const v8rCalls = (lt.match(/bunx v8r/g) || []).length
|
|
308
|
+
const quietCalls = (lt.match(/run-v8r?\.mjs/g) || []).length
|
|
309
|
+
const eq98Hints = (lt.match(/eq 98/g) || []).length
|
|
310
|
+
const legacyV8r = v8rCalls >= 4 && eq98Hints >= 4
|
|
311
|
+
const quietBundled = quietCalls === 1
|
|
312
|
+
const quietLegacy4x = quietCalls >= 4
|
|
313
|
+
const v8rTextOk = legacyV8r || quietBundled || quietLegacy4x
|
|
314
|
+
const globsRequired = legacyV8r || quietLegacy4x
|
|
315
|
+
const globsOk =
|
|
316
|
+
lt.includes('**/*.json') && lt.includes('**/*.yml') && lt.includes('**/*.yaml') && lt.includes('**/*.toml')
|
|
317
|
+
const ok =
|
|
318
|
+
lt &&
|
|
319
|
+
lt.includes('cspell') &&
|
|
320
|
+
lt.includes('bunx markdownlint-cli2') &&
|
|
321
|
+
lt.includes('**/*.mdc') &&
|
|
322
|
+
v8rTextOk &&
|
|
323
|
+
(!globsRequired || globsOk)
|
|
324
|
+
if (ok) {
|
|
325
|
+
passFn('package.json: lint-text — v8r: run-v8r.mjs (один виклик або чотири) або чотири bunx v8r з || [ $? -eq 98 ]')
|
|
244
326
|
} else {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
327
|
+
failFn(
|
|
328
|
+
'package.json: lint-text — v8r: bun ./…/run-v8r.mjs або чотири (bunx v8r "<glob>" || [ $? -eq 98 ]) для json/yml/yaml/toml (див. n-text.mdc)'
|
|
329
|
+
)
|
|
249
330
|
}
|
|
331
|
+
}
|
|
250
332
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
333
|
+
/**
|
|
334
|
+
* Перевіряє .markdownlint-cli2.jsonc.
|
|
335
|
+
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
336
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
337
|
+
*/
|
|
338
|
+
async function checkMarkdownlintConfig(pass, fail) {
|
|
339
|
+
if (!existsSync('.markdownlint-cli2.jsonc')) {
|
|
340
|
+
fail('.markdownlint-cli2.jsonc не існує — створи згідно n-text.mdc')
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
const ml = JSON.parse(await readFile('.markdownlint-cli2.jsonc', 'utf8'))
|
|
345
|
+
pass('.markdownlint-cli2.jsonc існує і є валідним JSON')
|
|
346
|
+
if (ml.gitignore === true) {
|
|
347
|
+
pass('.markdownlint-cli2.jsonc: gitignore увімкнено')
|
|
265
348
|
} else {
|
|
266
|
-
|
|
349
|
+
fail('.markdownlint-cli2.jsonc: додай на верхньому рівні "gitignore": true (див. n-text.mdc)')
|
|
267
350
|
}
|
|
351
|
+
} catch {
|
|
352
|
+
fail('.markdownlint-cli2.jsonc — невалідний JSON; перевір синтаксис')
|
|
353
|
+
}
|
|
354
|
+
}
|
|
268
355
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
356
|
+
/**
|
|
357
|
+
* Перевіряє .cspell.json на версію, мову, імпорт і ignorePaths.
|
|
358
|
+
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
359
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
360
|
+
*/
|
|
361
|
+
async function checkCspellConfig(pass, fail) {
|
|
362
|
+
if (!existsSync('.cspell.json')) {
|
|
363
|
+
fail('.cspell.json не існує — створи його')
|
|
364
|
+
return
|
|
365
|
+
}
|
|
366
|
+
const cfg = JSON.parse(await readFile('.cspell.json', 'utf8'))
|
|
367
|
+
if (cfg.version === '0.2') {
|
|
368
|
+
pass('.cspell.json version: 0.2')
|
|
369
|
+
} else {
|
|
370
|
+
fail('.cspell.json version має бути "0.2"')
|
|
371
|
+
}
|
|
372
|
+
if (cfg.language) {
|
|
373
|
+
pass(`.cspell.json language: "${cfg.language}"`)
|
|
374
|
+
} else {
|
|
375
|
+
fail('.cspell.json не містить поле language')
|
|
376
|
+
}
|
|
377
|
+
if ((cfg.import || []).some(i => i.includes('@nitra/cspell-dict'))) {
|
|
378
|
+
pass('.cspell.json імпортує @nitra/cspell-dict')
|
|
379
|
+
} else {
|
|
380
|
+
fail('.cspell.json не імпортує @nitra/cspell-dict/cspell-ext.json')
|
|
381
|
+
}
|
|
382
|
+
if (Array.isArray(cfg.ignorePaths)) {
|
|
383
|
+
pass('.cspell.json містить ignorePaths')
|
|
384
|
+
} else {
|
|
385
|
+
fail('.cspell.json не містить ignorePaths')
|
|
386
|
+
}
|
|
387
|
+
}
|
|
277
388
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
389
|
+
/**
|
|
390
|
+
* Перевіряє відповідність проєкту правилам text.mdc (oxfmt, cspell, markdownlint через bunx, v8r)
|
|
391
|
+
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
392
|
+
*/
|
|
393
|
+
export async function check() {
|
|
394
|
+
const reporter = createCheckReporter()
|
|
395
|
+
const { pass, fail } = reporter
|
|
284
396
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const eq98Hints = typeof lintText === 'string' ? (lintText.match(/eq 98/g) || []).length : 0
|
|
289
|
-
const globsOk =
|
|
290
|
-
typeof lintText === 'string' &&
|
|
291
|
-
lintText.includes('**/*.json') &&
|
|
292
|
-
lintText.includes('**/*.yml') &&
|
|
293
|
-
lintText.includes('**/*.yaml') &&
|
|
294
|
-
lintText.includes('**/*.toml')
|
|
295
|
-
const legacyV8r = v8rCalls >= 4 && eq98Hints >= 4
|
|
296
|
-
const quietBundled = quietCalls === 1
|
|
297
|
-
const quietLegacy4x = quietCalls >= 4
|
|
298
|
-
const v8rTextOk = legacyV8r || quietBundled || quietLegacy4x
|
|
299
|
-
const globsRequired = legacyV8r || quietLegacy4x
|
|
300
|
-
if (
|
|
301
|
-
typeof lintText === 'string' &&
|
|
302
|
-
lintText.includes('cspell') &&
|
|
303
|
-
lintText.includes('bunx markdownlint-cli2') &&
|
|
304
|
-
lintText.includes('**/*.mdc') &&
|
|
305
|
-
v8rTextOk &&
|
|
306
|
-
(!globsRequired || globsOk)
|
|
307
|
-
) {
|
|
308
|
-
pass('package.json: lint-text — v8r: run-v8r.mjs (один виклик або чотири) або чотири bunx v8r з || [ $? -eq 98 ]')
|
|
309
|
-
} else {
|
|
310
|
-
fail(
|
|
311
|
-
'package.json: lint-text — v8r: bun ./…/run-v8r.mjs або чотири (bunx v8r "<glob>" || [ $? -eq 98 ]) для json/yml/yaml/toml (див. n-text.mdc)'
|
|
312
|
-
)
|
|
313
|
-
}
|
|
397
|
+
await checkV8rIgnore(pass, fail)
|
|
398
|
+
await checkVscodeText(pass, fail)
|
|
399
|
+
await checkOxfmtRc(pass, fail)
|
|
314
400
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const ok = root ? anyRunStepIncludes(root, 'bun run lint-text') : wf.includes('bun run lint-text')
|
|
319
|
-
if (ok) {
|
|
320
|
-
pass('lint-text.yml викликає bun run lint-text')
|
|
321
|
-
} else {
|
|
322
|
-
fail('lint-text.yml має містити крок bun run lint-text')
|
|
323
|
-
}
|
|
324
|
-
} else {
|
|
325
|
-
fail('.github/workflows/lint-text.yml не існує — створи згідно n-text.mdc')
|
|
326
|
-
}
|
|
401
|
+
for (const f of ['.prettierrc', '.prettierrc.json', '.prettierrc.js', 'prettier.config.js', '.prettierrc.yml']) {
|
|
402
|
+
if (existsSync(f)) fail(`Знайдено конфіг prettier: ${f} — видали його`)
|
|
403
|
+
}
|
|
327
404
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
405
|
+
await checkMarkdownlintConfig(pass, fail)
|
|
406
|
+
await checkCspellConfig(pass, fail)
|
|
407
|
+
|
|
408
|
+
const textRulePaths = ['.cursor/rules/n-text.mdc', 'npm/mdc/text.mdc'].filter(p => existsSync(p))
|
|
409
|
+
if (textRulePaths.length === 0) {
|
|
410
|
+
pass('n-text.mdc / npm/mdc/text.mdc відсутні — перевірку абзацу про апостроф пропущено')
|
|
411
|
+
} else {
|
|
412
|
+
for (const p of textRulePaths) {
|
|
413
|
+
verifyUkApostropheRuleParagraph(p, await readFile(p, 'utf8'), fail, pass)
|
|
338
414
|
}
|
|
339
415
|
}
|
|
340
416
|
|
|
417
|
+
await checkPackageJsonText(pass, fail)
|
|
418
|
+
|
|
341
419
|
return reporter.getExitCode()
|
|
342
420
|
}
|