@nitra/cursor 1.8.105 → 1.8.108
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 +2 -2
- package/bin/n-cursor.js +5 -15
- package/mdc/js-pino.mdc +2 -2
- package/mdc/k8s.mdc +22 -12
- package/mdc/text.mdc +3 -3
- package/package.json +1 -1
- package/scripts/check-abie.mjs +515 -528
- package/scripts/check-bun.mjs +106 -78
- package/scripts/check-ga.mjs +151 -119
- package/scripts/check-js-lint.mjs +256 -179
- package/scripts/check-js-pino.mjs +48 -3
- package/scripts/check-k8s.mjs +403 -34
- package/scripts/check-nginx-default-tpl.mjs +109 -91
- package/scripts/check-npm-module.mjs +163 -116
- package/scripts/check-style-lint.mjs +74 -61
- package/scripts/check-text.mjs +289 -209
- package/scripts/check-vue.mjs +108 -67
- package/scripts/utils/bunyan-imports.mjs +182 -0
- package/scripts/utils/gha-workflow.mjs +3 -1
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,188 +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
|
-
|
|
204
|
+
failFn(
|
|
205
|
+
`.oxfmtrc.json ignorePatterns: додай відсутні елементи: ${missingPatterns.join(', ')} (канонічний приклад у text.mdc)`
|
|
206
|
+
)
|
|
182
207
|
}
|
|
183
208
|
} else {
|
|
184
|
-
|
|
209
|
+
failFn(`.oxfmtrc.json: додай масив ignorePatterns з ${OXFMT_REQUIRED_IGNORE_PATTERNS.join(', ')} (див. text.mdc)`)
|
|
185
210
|
}
|
|
211
|
+
}
|
|
186
212
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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} — видали її`)
|
|
190
224
|
}
|
|
225
|
+
if (pkg.prettier) failFn('package.json містить поле "prettier" — видали його')
|
|
191
226
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
pass('.markdownlint-cli2.jsonc: gitignore увімкнено')
|
|
198
|
-
} else {
|
|
199
|
-
fail('.markdownlint-cli2.jsonc: додай на верхньому рівні "gitignore": true (див. n-text.mdc)')
|
|
200
|
-
}
|
|
201
|
-
} catch {
|
|
202
|
-
fail('.markdownlint-cli2.jsonc — невалідний JSON; перевір синтаксис')
|
|
203
|
-
}
|
|
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
|
+
)
|
|
204
232
|
} else {
|
|
205
|
-
|
|
233
|
+
passFn('Кореневі devDependencies лише @nitra/*')
|
|
206
234
|
}
|
|
207
235
|
|
|
208
|
-
|
|
209
|
-
|
|
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
|
+
}
|
|
210
244
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
+
}
|
|
216
251
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
+
}
|
|
222
269
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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 || {}
|
|
279
|
+
|
|
280
|
+
checkPackageJsonTextDepsUsage(pkg, devDeps, passFn, failFn)
|
|
281
|
+
checkLintTextScript(pkg.scripts?.['lint-text'], passFn, failFn)
|
|
229
282
|
|
|
230
|
-
|
|
231
|
-
|
|
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')
|
|
232
289
|
} else {
|
|
233
|
-
|
|
290
|
+
failFn('lint-text.yml має містити крок bun run lint-text')
|
|
234
291
|
}
|
|
235
292
|
} else {
|
|
236
|
-
|
|
293
|
+
failFn('.github/workflows/lint-text.yml не існує — створи згідно n-text.mdc')
|
|
237
294
|
}
|
|
238
295
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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 ]')
|
|
242
326
|
} else {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
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
|
+
)
|
|
247
330
|
}
|
|
331
|
+
}
|
|
248
332
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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 увімкнено')
|
|
263
348
|
} else {
|
|
264
|
-
|
|
349
|
+
fail('.markdownlint-cli2.jsonc: додай на верхньому рівні "gitignore": true (див. n-text.mdc)')
|
|
265
350
|
}
|
|
351
|
+
} catch {
|
|
352
|
+
fail('.markdownlint-cli2.jsonc — невалідний JSON; перевір синтаксис')
|
|
353
|
+
}
|
|
354
|
+
}
|
|
266
355
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
+
}
|
|
275
388
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
|
282
396
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const eq98Hints = typeof lintText === 'string' ? (lintText.match(/eq 98/g) || []).length : 0
|
|
287
|
-
const globsOk =
|
|
288
|
-
typeof lintText === 'string' &&
|
|
289
|
-
lintText.includes('**/*.json') &&
|
|
290
|
-
lintText.includes('**/*.yml') &&
|
|
291
|
-
lintText.includes('**/*.yaml') &&
|
|
292
|
-
lintText.includes('**/*.toml')
|
|
293
|
-
const legacyV8r = v8rCalls >= 4 && eq98Hints >= 4
|
|
294
|
-
const quietBundled = quietCalls === 1
|
|
295
|
-
const quietLegacy4x = quietCalls >= 4
|
|
296
|
-
const v8rTextOk = legacyV8r || quietBundled || quietLegacy4x
|
|
297
|
-
const globsRequired = legacyV8r || quietLegacy4x
|
|
298
|
-
if (
|
|
299
|
-
typeof lintText === 'string' &&
|
|
300
|
-
lintText.includes('cspell') &&
|
|
301
|
-
lintText.includes('bunx markdownlint-cli2') &&
|
|
302
|
-
lintText.includes('**/*.mdc') &&
|
|
303
|
-
v8rTextOk &&
|
|
304
|
-
(!globsRequired || globsOk)
|
|
305
|
-
) {
|
|
306
|
-
pass('package.json: lint-text — v8r: run-v8r.mjs (один виклик або чотири) або чотири bunx v8r з || [ $? -eq 98 ]')
|
|
307
|
-
} else {
|
|
308
|
-
fail(
|
|
309
|
-
'package.json: lint-text — v8r: bun ./…/run-v8r.mjs або чотири (bunx v8r "<glob>" || [ $? -eq 98 ]) для json/yml/yaml/toml (див. n-text.mdc)'
|
|
310
|
-
)
|
|
311
|
-
}
|
|
397
|
+
await checkV8rIgnore(pass, fail)
|
|
398
|
+
await checkVscodeText(pass, fail)
|
|
399
|
+
await checkOxfmtRc(pass, fail)
|
|
312
400
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const ok = root ? anyRunStepIncludes(root, 'bun run lint-text') : wf.includes('bun run lint-text')
|
|
317
|
-
if (ok) {
|
|
318
|
-
pass('lint-text.yml викликає bun run lint-text')
|
|
319
|
-
} else {
|
|
320
|
-
fail('lint-text.yml має містити крок bun run lint-text')
|
|
321
|
-
}
|
|
322
|
-
} else {
|
|
323
|
-
fail('.github/workflows/lint-text.yml не існує — створи згідно n-text.mdc')
|
|
324
|
-
}
|
|
401
|
+
for (const f of ['.prettierrc', '.prettierrc.json', '.prettierrc.js', 'prettier.config.js', '.prettierrc.yml']) {
|
|
402
|
+
if (existsSync(f)) fail(`Знайдено конфіг prettier: ${f} — видали його`)
|
|
403
|
+
}
|
|
325
404
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
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)
|
|
336
414
|
}
|
|
337
415
|
}
|
|
338
416
|
|
|
417
|
+
await checkPackageJsonText(pass, fail)
|
|
418
|
+
|
|
339
419
|
return reporter.getExitCode()
|
|
340
420
|
}
|