@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-bun.mjs
CHANGED
|
@@ -50,6 +50,91 @@ async function loadNCursorRules() {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* @param {{ pass: (msg: string) => void, fail: (msg: string) => void }} reporter репортер для збору результатів
|
|
55
|
+
* @param {Record<string, unknown>} pkg розібраний package.json
|
|
56
|
+
*/
|
|
57
|
+
function checkDevDependencies(reporter, pkg) {
|
|
58
|
+
const { pass, fail } = reporter
|
|
59
|
+
const dev = pkg.devDependencies
|
|
60
|
+
if (dev === undefined) {
|
|
61
|
+
pass('Кореневий package.json без devDependencies')
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
if (dev === null || typeof dev !== 'object' || Array.isArray(dev)) {
|
|
65
|
+
fail(
|
|
66
|
+
'Кореневий package.json: `devDependencies` має бути object з ключами пакетів і діапазонами версій (не null, не масив)'
|
|
67
|
+
)
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
const bad = Object.keys(/** @type {object} */ (dev)).filter(n => !isAllowedRootDevDependency(n))
|
|
71
|
+
if (bad.length > 0) {
|
|
72
|
+
fail(`Кореневі devDependencies: дозволені лише @nitra/* — прибери або перенеси: ${bad.join(', ')} (bun.mdc)`)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
const n = Object.keys(/** @type {object} */ (dev)).length
|
|
76
|
+
pass(
|
|
77
|
+
n === 0
|
|
78
|
+
? 'Кореневі devDependencies порожні або відсутні (лише @nitra/*)'
|
|
79
|
+
: `Кореневі devDependencies: лише @nitra/* (${n} пак.)`
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {{ pass: (msg: string) => void, fail: (msg: string) => void }} reporter репортер для збору результатів
|
|
85
|
+
* @param {Record<string, string>} scripts scripts з package.json
|
|
86
|
+
*/
|
|
87
|
+
function checkLintAggregate(reporter, scripts) {
|
|
88
|
+
const { pass, fail } = reporter
|
|
89
|
+
const lintPrefixed = Object.keys(scripts).filter(name => name.startsWith('lint-'))
|
|
90
|
+
if (lintPrefixed.length === 0) return
|
|
91
|
+
const aggregate = typeof scripts.lint === 'string' ? scripts.lint : ''
|
|
92
|
+
if (!aggregate.trim()) {
|
|
93
|
+
const scriptList = lintPrefixed.map(s => `\`${s}\``).join(', ')
|
|
94
|
+
fail(
|
|
95
|
+
`У package.json є скрипти ${scriptList}, але немає агрегованого \`lint\` — додай скрипт, який запускає їх через \`bun run\``
|
|
96
|
+
)
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
const missing = lintPrefixed.filter(name => !aggregate.includes(`bun run ${name}`))
|
|
100
|
+
if (missing.length > 0) {
|
|
101
|
+
const missingList = missing.map(s => '`' + s + '`').join(', ')
|
|
102
|
+
fail(`Скрипт \`lint\` має викликати всі lint-* через bun run; відсутньо: ${missingList}`)
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
pass('package.json: агрегований `lint` покриває всі `lint-*` скрипти')
|
|
106
|
+
if (OXFMT_END_RE.test(aggregate.trim())) {
|
|
107
|
+
pass('package.json: `lint` завершується `&& oxfmt .`')
|
|
108
|
+
} else {
|
|
109
|
+
fail('Скрипт `lint` має закінчуватися на `&& oxfmt .`')
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @param {{ pass: (msg: string) => void, fail: (msg: string) => void }} reporter репортер для збору результатів
|
|
115
|
+
* @param {Record<string, string>} scripts scripts з package.json
|
|
116
|
+
* @param {Set<string>} cursorRules активні правила з .n-cursor.json
|
|
117
|
+
*/
|
|
118
|
+
function checkCursorRuleScripts(reporter, scripts, cursorRules) {
|
|
119
|
+
const { pass, fail } = reporter
|
|
120
|
+
/** @type {Array<{rule: string, script: string, doc: string}>} */
|
|
121
|
+
const ruleScripts = [
|
|
122
|
+
{ rule: 'docker', script: 'lint-docker', doc: 'docker.mdc' },
|
|
123
|
+
{ rule: 'k8s', script: 'lint-k8s', doc: 'k8s.mdc' }
|
|
124
|
+
]
|
|
125
|
+
for (const { rule, script, doc } of ruleScripts) {
|
|
126
|
+
if (cursorRules.has(rule)) {
|
|
127
|
+
if (scripts[script]) {
|
|
128
|
+
pass(`package.json: є \`${script}\` (правило ${rule} у .n-cursor.json)`)
|
|
129
|
+
} else {
|
|
130
|
+
fail(
|
|
131
|
+
`У .n-cursor.json є правило \`${rule}\` — додай скрипт \`${script}\` у кореневий package.json (див. ${doc})`
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
53
138
|
/**
|
|
54
139
|
* Перевіряє відповідність проєкту правилам bun.mdc
|
|
55
140
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
@@ -58,8 +143,7 @@ export async function check() {
|
|
|
58
143
|
const reporter = createCheckReporter()
|
|
59
144
|
const { pass, fail } = reporter
|
|
60
145
|
|
|
61
|
-
const
|
|
62
|
-
for (const f of forbidden) {
|
|
146
|
+
for (const f of ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', '.yarnrc.yml']) {
|
|
63
147
|
if (existsSync(f)) {
|
|
64
148
|
fail(`Знайдено заборонений файл: ${f} — видали його`)
|
|
65
149
|
} else {
|
|
@@ -80,90 +164,30 @@ export async function check() {
|
|
|
80
164
|
|
|
81
165
|
const cursorRules = await loadNCursorRules()
|
|
82
166
|
|
|
83
|
-
if (existsSync('package.json')) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
fail(`package.json містить поле packageManager: "${pkg.packageManager}" — видали його`)
|
|
87
|
-
} else {
|
|
88
|
-
pass('package.json не містить packageManager')
|
|
89
|
-
}
|
|
167
|
+
if (!existsSync('package.json')) {
|
|
168
|
+
return reporter.getExitCode()
|
|
169
|
+
}
|
|
90
170
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
171
|
+
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
172
|
+
if (pkg.packageManager) {
|
|
173
|
+
fail(`package.json містить поле packageManager: "${pkg.packageManager}" — видали його`)
|
|
174
|
+
} else {
|
|
175
|
+
pass('package.json не містить packageManager')
|
|
176
|
+
}
|
|
98
177
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
} else {
|
|
107
|
-
const bad = Object.keys(dev).filter(n => !isAllowedRootDevDependency(n))
|
|
108
|
-
if (bad.length > 0) {
|
|
109
|
-
fail(
|
|
110
|
-
`Кореневі devDependencies: дозволені лише @nitra/* — прибери або перенеси: ${bad.join(', ')} (bun.mdc)`
|
|
111
|
-
)
|
|
112
|
-
} else {
|
|
113
|
-
const n = Object.keys(dev).length
|
|
114
|
-
pass(
|
|
115
|
-
n === 0
|
|
116
|
-
? 'Кореневі devDependencies порожні або відсутні (лише @nitra/*)'
|
|
117
|
-
: `Кореневі devDependencies: лише @nitra/* (${n} пак.)`
|
|
118
|
-
)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
178
|
+
if (pkg.dependencies === undefined) {
|
|
179
|
+
pass('Кореневий package.json без поля `dependencies`')
|
|
180
|
+
} else {
|
|
181
|
+
fail(
|
|
182
|
+
'Кореневий package.json не повинен містити поле `dependencies` — додай залежності в workspace-пакети (bun.mdc)'
|
|
183
|
+
)
|
|
184
|
+
}
|
|
121
185
|
|
|
122
|
-
|
|
186
|
+
checkDevDependencies(reporter, pkg)
|
|
123
187
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
} else {
|
|
128
|
-
fail(
|
|
129
|
-
'У .n-cursor.json є правило `docker` — додай скрипт `lint-docker` у кореневий package.json (див. docker.mdc)'
|
|
130
|
-
)
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (cursorRules.has('k8s')) {
|
|
135
|
-
if (scripts['lint-k8s']) {
|
|
136
|
-
pass('package.json: є `lint-k8s` (правило k8s у .n-cursor.json)')
|
|
137
|
-
} else {
|
|
138
|
-
fail('У .n-cursor.json є правило `k8s` — додай скрипт `lint-k8s` у кореневий package.json (див. k8s.mdc)')
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
const lintPrefixed = Object.keys(scripts).filter(name => name.startsWith('lint-'))
|
|
142
|
-
if (lintPrefixed.length > 0) {
|
|
143
|
-
const aggregate = typeof scripts.lint === 'string' ? scripts.lint : ''
|
|
144
|
-
if (aggregate.trim()) {
|
|
145
|
-
const missing = lintPrefixed.filter(name => !aggregate.includes(`bun run ${name}`))
|
|
146
|
-
if (missing.length > 0) {
|
|
147
|
-
const missingList = missing.map(s => `\`${s}\``).join(', ')
|
|
148
|
-
fail(
|
|
149
|
-
`Скрипт \`lint\` має викликати всі lint-* через bun run; відсутньо: ${missingList}`
|
|
150
|
-
)
|
|
151
|
-
} else {
|
|
152
|
-
pass('package.json: агрегований `lint` покриває всі `lint-*` скрипти')
|
|
153
|
-
if (OXFMT_END_RE.test(aggregate.trim())) {
|
|
154
|
-
pass('package.json: `lint` завершується `&& oxfmt .`')
|
|
155
|
-
} else {
|
|
156
|
-
fail('Скрипт `lint` має закінчуватися на `&& oxfmt .`')
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
} else {
|
|
160
|
-
const scriptList = lintPrefixed.map(s => `\`${s}\``).join(', ')
|
|
161
|
-
fail(
|
|
162
|
-
`У package.json є скрипти ${scriptList}, але немає агрегованого \`lint\` — додай скрипт, який запускає їх через \`bun run\``
|
|
163
|
-
)
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
188
|
+
const scripts = pkg.scripts && typeof pkg.scripts === 'object' ? pkg.scripts : {}
|
|
189
|
+
checkCursorRuleScripts(reporter, scripts, cursorRules)
|
|
190
|
+
checkLintAggregate(reporter, scripts)
|
|
167
191
|
|
|
168
192
|
return reporter.getExitCode()
|
|
169
193
|
}
|
package/scripts/check-ga.mjs
CHANGED
|
@@ -118,31 +118,135 @@ function verifyNoDirectBunOrCache(relPath, content, failFn, passFn) {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
|
-
* Перевіряє
|
|
122
|
-
* @
|
|
121
|
+
* Перевіряє apply-workflow на наявність paths trigger.
|
|
122
|
+
* @param {string} wfDir директорія workflows
|
|
123
|
+
* @param {string[]} files список файлів у директорії
|
|
124
|
+
* @param {string} filename параметр filename
|
|
125
|
+
* @param {string} expectedPath параметр expectedPath
|
|
126
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
127
|
+
* @param {(msg: string) => void} failFn callback при помилці
|
|
123
128
|
*/
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
129
|
+
async function checkApplyWorkflow(wfDir, files, filename, expectedPath, passFn, failFn) {
|
|
130
|
+
if (!files.includes(filename)) return
|
|
131
|
+
const content = await readFile(`${wfDir}/${filename}`, 'utf8')
|
|
132
|
+
const root = parseWorkflowYaml(content)
|
|
133
|
+
const ok = root ? eventPathsIncludeExact(root, 'push', expectedPath) : content.includes(expectedPath)
|
|
134
|
+
if (ok) {
|
|
135
|
+
passFn(`${filename} має правильний paths trigger`)
|
|
136
|
+
} else {
|
|
137
|
+
failFn(`${filename} не містить paths: ${expectedPath}`)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
127
140
|
|
|
128
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Перевіряє відсутність MegaLinter у workflows та конфіг-файлах.
|
|
143
|
+
* @param {string} wfDir директорія workflows
|
|
144
|
+
* @param {string[]} ymlWorkflows параметр ymlWorkflows
|
|
145
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
146
|
+
* @param {(msg: string) => void} failFn callback при помилці
|
|
147
|
+
*/
|
|
148
|
+
async function checkMegalinter(wfDir, ymlWorkflows, passFn, failFn) {
|
|
149
|
+
let found = false
|
|
150
|
+
for (const f of ymlWorkflows) {
|
|
151
|
+
const content = await readFile(join(wfDir, f), 'utf8')
|
|
152
|
+
if (MEGALINTER_USE_PATTERNS.some(re => re.test(content))) {
|
|
153
|
+
found = true
|
|
154
|
+
failFn(`MegaLinter у workflow ${wfDir}/${f} — видали інтеграцію (ga.mdc: MegaLinter)`)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
for (const name of MEGALINTER_CONFIG_NAMES) {
|
|
158
|
+
if (existsSync(name)) {
|
|
159
|
+
found = true
|
|
160
|
+
failFn(`Файл ${name} — видали конфіг MegaLinter (ga.mdc: MegaLinter)`)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (!found) passFn('Залишків MegaLinter не виявлено')
|
|
164
|
+
}
|
|
129
165
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Перевіряє zizmor конфіг.
|
|
168
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
169
|
+
* @param {(msg: string) => void} failFn callback при помилці
|
|
170
|
+
*/
|
|
171
|
+
async function checkZizmor(passFn, failFn) {
|
|
172
|
+
const zizmorPath = '.github/zizmor.yml'
|
|
173
|
+
if (!existsSync(zizmorPath)) {
|
|
174
|
+
failFn(`Відсутній ${zizmorPath} — потрібен для zizmor (ga.mdc)`)
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
const z = await readFile(zizmorPath, 'utf8')
|
|
178
|
+
passFn(`${zizmorPath} існує`)
|
|
179
|
+
if (z.includes('ref-pin')) {
|
|
180
|
+
passFn(`${zizmorPath} містить політику ref-pin (zizmor)`)
|
|
181
|
+
} else {
|
|
182
|
+
failFn(`${zizmorPath}: додай policies ref-pin для unpinned-uses (ga.mdc)`)
|
|
133
183
|
}
|
|
184
|
+
}
|
|
134
185
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Перевіряє скрипт lint-ga в package.json.
|
|
188
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
189
|
+
* @param {(msg: string) => void} failFn callback при помилці
|
|
190
|
+
*/
|
|
191
|
+
async function checkLintGaScript(passFn, failFn) {
|
|
192
|
+
if (!existsSync('package.json')) {
|
|
193
|
+
failFn('package.json не існує — потрібен lint-ga у scripts')
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
197
|
+
const lg = pkg.scripts?.['lint-ga']
|
|
198
|
+
if (typeof lg !== 'string') {
|
|
199
|
+
failFn('package.json: додай скрипт "lint-ga" (ga.mdc)')
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
passFn('package.json містить lint-ga')
|
|
203
|
+
if (lg.includes('node-actionlint')) {
|
|
204
|
+
passFn('lint-ga викликає node-actionlint')
|
|
138
205
|
} else {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
206
|
+
failFn('lint-ga має містити bunx node-actionlint (ga.mdc)')
|
|
207
|
+
}
|
|
208
|
+
if (lg.includes('zizmor') && lg.includes('--offline')) {
|
|
209
|
+
passFn('lint-ga викликає zizmor з --offline')
|
|
210
|
+
} else {
|
|
211
|
+
failFn('lint-ga має містити zizmor і --offline (ga.mdc)')
|
|
142
212
|
}
|
|
213
|
+
}
|
|
143
214
|
|
|
144
|
-
|
|
215
|
+
/**
|
|
216
|
+
* Перевіряє lint-ga.yml workflow.
|
|
217
|
+
* @param {string} wfDir директорія workflows
|
|
218
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
219
|
+
* @param {(msg: string) => void} failFn callback при помилці
|
|
220
|
+
*/
|
|
221
|
+
async function checkLintGaWorkflow(wfDir, passFn, failFn) {
|
|
222
|
+
const lintGaWf = join(wfDir, 'lint-ga.yml')
|
|
223
|
+
if (!existsSync(lintGaWf)) return
|
|
224
|
+
const lgContent = await readFile(lintGaWf, 'utf8')
|
|
225
|
+
const root = parseWorkflowYaml(lgContent)
|
|
226
|
+
const hasBunRun = root ? anyRunStepIncludes(root, 'bun run lint-ga') : lgContent.includes('bun run lint-ga')
|
|
227
|
+
const hasSetupUv = root
|
|
228
|
+
? hasAnyStepUsesContaining(root, ['astral-sh/setup-uv']) || lgContent.includes('astral-sh/setup-uv')
|
|
229
|
+
: lgContent.includes('astral-sh/setup-uv')
|
|
230
|
+
if (hasBunRun) {
|
|
231
|
+
passFn('lint-ga.yml викликає bun run lint-ga')
|
|
232
|
+
} else {
|
|
233
|
+
failFn('lint-ga.yml: крок має містити bun run lint-ga')
|
|
234
|
+
}
|
|
235
|
+
if (hasSetupUv) {
|
|
236
|
+
passFn('lint-ga.yml містить astral-sh/setup-uv')
|
|
237
|
+
} else {
|
|
238
|
+
failFn('lint-ga.yml: додай astral-sh/setup-uv для uvx zizmor (ga.mdc)')
|
|
239
|
+
}
|
|
240
|
+
}
|
|
145
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Перевіряє розширення workflow-файлів і наявність обов'язкових workflow.
|
|
244
|
+
* @param {string} wfDir шлях до директорії workflows
|
|
245
|
+
* @param {string[]} files список файлів у wfDir
|
|
246
|
+
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
247
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
248
|
+
*/
|
|
249
|
+
function checkGaWorkflowFiles(wfDir, files, pass, fail) {
|
|
146
250
|
const yamlFiles = files.filter(f => f.endsWith('.yaml'))
|
|
147
251
|
if (yamlFiles.length > 0) {
|
|
148
252
|
for (const f of yamlFiles) {
|
|
@@ -159,31 +263,38 @@ export async function check() {
|
|
|
159
263
|
fail(`Відсутній ${wfDir}/${f}`)
|
|
160
264
|
}
|
|
161
265
|
}
|
|
266
|
+
}
|
|
162
267
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Перевіряє відповідність проєкту правилам ga.mdc
|
|
270
|
+
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
271
|
+
*/
|
|
272
|
+
export async function check() {
|
|
273
|
+
const reporter = createCheckReporter()
|
|
274
|
+
const { pass, fail } = reporter
|
|
275
|
+
|
|
276
|
+
const wfDir = '.github/workflows'
|
|
277
|
+
|
|
278
|
+
if (!existsSync(wfDir)) {
|
|
279
|
+
fail(`Директорія ${wfDir} не існує`)
|
|
280
|
+
return reporter.getExitCode()
|
|
173
281
|
}
|
|
174
282
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
} else {
|
|
183
|
-
fail('apply-nats-consumer.yml не містить paths: **/consumer.yaml')
|
|
184
|
-
}
|
|
283
|
+
const setupBunDepsAction = '.github/actions/setup-bun-deps/action.yml'
|
|
284
|
+
if (existsSync(setupBunDepsAction)) {
|
|
285
|
+
pass(`${setupBunDepsAction} існує`)
|
|
286
|
+
} else {
|
|
287
|
+
fail(
|
|
288
|
+
`Відсутній ${setupBunDepsAction} — запустіть npx @nitra/cursor або скопіюйте з пакету (ga.mdc: composite setup-bun-deps)`
|
|
289
|
+
)
|
|
185
290
|
}
|
|
186
291
|
|
|
292
|
+
const files = await readdir(wfDir)
|
|
293
|
+
checkGaWorkflowFiles(wfDir, files, pass, fail)
|
|
294
|
+
|
|
295
|
+
await checkApplyWorkflow(wfDir, files, 'apply-k8s.yml', '**/k8s/**/*.yaml', pass, fail)
|
|
296
|
+
await checkApplyWorkflow(wfDir, files, 'apply-nats-consumer.yml', '**/consumer.yaml', pass, fail)
|
|
297
|
+
|
|
187
298
|
if (existsSync('.vscode/extensions.json')) {
|
|
188
299
|
const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
|
|
189
300
|
if (ext.recommendations?.includes('github.vscode-github-actions')) {
|
|
@@ -196,25 +307,7 @@ export async function check() {
|
|
|
196
307
|
}
|
|
197
308
|
|
|
198
309
|
const ymlWorkflows = files.filter(f => f.endsWith('.yml'))
|
|
199
|
-
|
|
200
|
-
for (const f of ymlWorkflows) {
|
|
201
|
-
const content = await readFile(join(wfDir, f), 'utf8')
|
|
202
|
-
if (MEGALINTER_USE_PATTERNS.some(re => re.test(content))) {
|
|
203
|
-
foundMegalinter = true
|
|
204
|
-
fail(`MegaLinter у workflow ${wfDir}/${f} — видали інтеграцію (ga.mdc: MegaLinter)`)
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
for (const name of MEGALINTER_CONFIG_NAMES) {
|
|
209
|
-
if (existsSync(name)) {
|
|
210
|
-
foundMegalinter = true
|
|
211
|
-
fail(`Файл ${name} — видали конфіг MegaLinter (ga.mdc: MegaLinter)`)
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (!foundMegalinter) {
|
|
216
|
-
pass('Залишків MegaLinter не виявлено')
|
|
217
|
-
}
|
|
310
|
+
await checkMegalinter(wfDir, ymlWorkflows, pass, fail)
|
|
218
311
|
|
|
219
312
|
for (const f of ymlWorkflows) {
|
|
220
313
|
const content = await readFile(join(wfDir, f), 'utf8')
|
|
@@ -222,70 +315,9 @@ export async function check() {
|
|
|
222
315
|
verifyNoDirectBunOrCache(`${wfDir}/${f}`, content, fail, pass)
|
|
223
316
|
}
|
|
224
317
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
pass(`${zizmorPath} існує`)
|
|
229
|
-
if (z.includes('ref-pin')) {
|
|
230
|
-
pass(`${zizmorPath} містить політику ref-pin (zizmor)`)
|
|
231
|
-
} else {
|
|
232
|
-
fail(`${zizmorPath}: додай policies ref-pin для unpinned-uses (ga.mdc)`)
|
|
233
|
-
}
|
|
234
|
-
} else {
|
|
235
|
-
fail(`Відсутній ${zizmorPath} — потрібен для zizmor (ga.mdc)`)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (existsSync('package.json')) {
|
|
239
|
-
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
240
|
-
const lg = pkg.scripts?.['lint-ga']
|
|
241
|
-
if (typeof lg === 'string') {
|
|
242
|
-
pass('package.json містить lint-ga')
|
|
243
|
-
if (lg.includes('node-actionlint')) {
|
|
244
|
-
pass('lint-ga викликає node-actionlint')
|
|
245
|
-
} else {
|
|
246
|
-
fail('lint-ga має містити bunx node-actionlint (ga.mdc)')
|
|
247
|
-
}
|
|
248
|
-
if (lg.includes('zizmor') && lg.includes('--offline')) {
|
|
249
|
-
pass('lint-ga викликає zizmor з --offline')
|
|
250
|
-
} else {
|
|
251
|
-
fail('lint-ga має містити zizmor і --offline (ga.mdc)')
|
|
252
|
-
}
|
|
253
|
-
} else {
|
|
254
|
-
fail('package.json: додай скрипт "lint-ga" (ga.mdc)')
|
|
255
|
-
}
|
|
256
|
-
} else {
|
|
257
|
-
fail('package.json не існує — потрібен lint-ga у scripts')
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const lintGaWf = join(wfDir, 'lint-ga.yml')
|
|
261
|
-
if (existsSync(lintGaWf)) {
|
|
262
|
-
const lgContent = await readFile(lintGaWf, 'utf8')
|
|
263
|
-
const root = parseWorkflowYaml(lgContent)
|
|
264
|
-
if (root) {
|
|
265
|
-
if (anyRunStepIncludes(root, 'bun run lint-ga')) {
|
|
266
|
-
pass('lint-ga.yml викликає bun run lint-ga')
|
|
267
|
-
} else {
|
|
268
|
-
fail('lint-ga.yml: крок має містити bun run lint-ga')
|
|
269
|
-
}
|
|
270
|
-
const usesFlat = hasAnyStepUsesContaining(root, ['astral-sh/setup-uv'])
|
|
271
|
-
if (usesFlat || lgContent.includes('astral-sh/setup-uv')) {
|
|
272
|
-
pass('lint-ga.yml містить astral-sh/setup-uv')
|
|
273
|
-
} else {
|
|
274
|
-
fail('lint-ga.yml: додай astral-sh/setup-uv для uvx zizmor (ga.mdc)')
|
|
275
|
-
}
|
|
276
|
-
} else {
|
|
277
|
-
if (lgContent.includes('bun run lint-ga')) {
|
|
278
|
-
pass('lint-ga.yml викликає bun run lint-ga')
|
|
279
|
-
} else {
|
|
280
|
-
fail('lint-ga.yml: крок має містити bun run lint-ga')
|
|
281
|
-
}
|
|
282
|
-
if (lgContent.includes('astral-sh/setup-uv')) {
|
|
283
|
-
pass('lint-ga.yml містить astral-sh/setup-uv')
|
|
284
|
-
} else {
|
|
285
|
-
fail('lint-ga.yml: додай astral-sh/setup-uv для uvx zizmor (ga.mdc)')
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
318
|
+
await checkZizmor(pass, fail)
|
|
319
|
+
await checkLintGaScript(pass, fail)
|
|
320
|
+
await checkLintGaWorkflow(wfDir, pass, fail)
|
|
289
321
|
|
|
290
322
|
return reporter.getExitCode()
|
|
291
323
|
}
|