@nitra/cursor 1.8.104 → 1.8.105
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 +280 -149
- package/mdc/graphql.mdc +15 -1
- package/package.json +1 -1
- package/schemas/n-cursor.json +16 -0
- package/scripts/auto-rules.mjs +404 -0
- package/scripts/check-abie.mjs +54 -36
- package/scripts/check-bun.mjs +2 -6
- package/scripts/check-graphql.mjs +112 -34
- package/scripts/check-js-lint.mjs +15 -11
- package/scripts/check-k8s.mjs +1165 -674
- package/scripts/check-nginx-default-tpl.mjs +17 -10
- package/scripts/check-npm-module.mjs +3 -3
- package/scripts/check-text.mjs +1 -3
- package/scripts/check-vue.mjs +2 -2
- package/scripts/utils/docker-hadolint.mjs +9 -5
- package/scripts/utils/gha-workflow.mjs +90 -72
- package/scripts/utils/workspaces.mjs +39 -16
|
@@ -21,6 +21,15 @@ import { findDockerfilePaths } from './check-docker.mjs'
|
|
|
21
21
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
22
22
|
import { walkDir } from './utils/walkDir.mjs'
|
|
23
23
|
|
|
24
|
+
const LINE_SPLIT_RE = /\r?\n/u
|
|
25
|
+
const INI_KEY_RE = /^([A-Za-z_]\w*)\s*=/u
|
|
26
|
+
const RETURN_200_RE = /return\s+200/u
|
|
27
|
+
const GZIP_STATIC_ON_RE = /gzip_static\s+on/gu
|
|
28
|
+
const PROXY_LIKE_RE = /\b(proxy_pass|proxy_redirect|proxy_set_header|proxy_http_version|fastcgi_pass|grpc_pass|uwsgi_pass)\b/u
|
|
29
|
+
const FIND_CMD_RE = /\bfind\b/u
|
|
30
|
+
const GZIP_CMD_RE = /\bgzip\b/u
|
|
31
|
+
const GZIP_EXTENSION_RE = /\*\.(?:js|css)/u
|
|
32
|
+
|
|
24
33
|
/**
|
|
25
34
|
* Збирає абсолютні шляхи до **default.conf.template** у репозиторії; шлях `tests/fixtures` не обходиться як проєктний шаблон.
|
|
26
35
|
* @param {string} root корінь cwd
|
|
@@ -82,10 +91,10 @@ export async function migrateDefaultTplConfFiles(root) {
|
|
|
82
91
|
export function parseIniVariableNames(iniText) {
|
|
83
92
|
/** @type {string[]} */
|
|
84
93
|
const keys = []
|
|
85
|
-
for (const line of iniText.split(
|
|
94
|
+
for (const line of iniText.split(LINE_SPLIT_RE)) {
|
|
86
95
|
const t = line.trim()
|
|
87
96
|
if (t !== '' && !t.startsWith('#') && !t.startsWith(';')) {
|
|
88
|
-
const m = t.match(
|
|
97
|
+
const m = t.match(INI_KEY_RE)
|
|
89
98
|
if (m) keys.push(m[1])
|
|
90
99
|
}
|
|
91
100
|
}
|
|
@@ -111,7 +120,7 @@ export function nginxTemplateViolations(content) {
|
|
|
111
120
|
{ msg: 'відсутнє root /usr/share/nginx/html', ok: c => c.includes('root /usr/share/nginx/html') },
|
|
112
121
|
{
|
|
113
122
|
msg: 'location /healthz має повертати healthy (див. nginx-default-tpl.mdc)',
|
|
114
|
-
ok: c => c.includes('/healthz') && (c.includes('healthy') ||
|
|
123
|
+
ok: c => c.includes('/healthz') && (c.includes('healthy') || RETURN_200_RE.test(c))
|
|
115
124
|
},
|
|
116
125
|
{
|
|
117
126
|
msg: 'відсутній location для статики без gzip (gif|jpeg|png|ico|woff2|xlsx) з Cache-Control 31536000',
|
|
@@ -126,7 +135,7 @@ export function nginxTemplateViolations(content) {
|
|
|
126
135
|
},
|
|
127
136
|
{
|
|
128
137
|
msg: 'gzip_static on має бути принаймні двічі (два location зі стисненням)',
|
|
129
|
-
ok: c => (c.match(
|
|
138
|
+
ok: c => (c.match(GZIP_STATIC_ON_RE) ?? []).length >= 2
|
|
130
139
|
},
|
|
131
140
|
{ msg: 'відсутнє використання $PUBLIC_PATH у location', ok: c => c.includes('$PUBLIC_PATH') },
|
|
132
141
|
{
|
|
@@ -144,9 +153,7 @@ export function nginxTemplateViolations(content) {
|
|
|
144
153
|
}
|
|
145
154
|
|
|
146
155
|
// cspell:ignore fastcgi uwsgi
|
|
147
|
-
|
|
148
|
-
/\b(proxy_pass|proxy_redirect|proxy_set_header|proxy_http_version|fastcgi_pass|grpc_pass|uwsgi_pass)\b/u
|
|
149
|
-
if (proxyLike.test(content)) {
|
|
156
|
+
if (PROXY_LIKE_RE.test(content)) {
|
|
150
157
|
return 'знайдено proxy, gRPC або інший *_pass до бекенду — прибери з шаблону, логіку винеси в HTTPRoute (k8s) (див. nginx-default-tpl.mdc)'
|
|
151
158
|
}
|
|
152
159
|
|
|
@@ -242,11 +249,11 @@ export function iniKeysMissingInTemplate(keys, template) {
|
|
|
242
249
|
function dockerfileHasGzipStaticPipeline(dockerfileContent) {
|
|
243
250
|
const c = dockerfileContent
|
|
244
251
|
return (
|
|
245
|
-
|
|
252
|
+
FIND_CMD_RE.test(c) &&
|
|
246
253
|
c.includes('/usr/share/nginx/html') &&
|
|
247
|
-
|
|
254
|
+
GZIP_CMD_RE.test(c) &&
|
|
248
255
|
c.includes('-k') &&
|
|
249
|
-
|
|
256
|
+
GZIP_EXTENSION_RE.test(c)
|
|
250
257
|
)
|
|
251
258
|
}
|
|
252
259
|
|
|
@@ -16,8 +16,6 @@ import { readFile, stat } from 'node:fs/promises'
|
|
|
16
16
|
import { join } from 'node:path'
|
|
17
17
|
|
|
18
18
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
19
|
-
|
|
20
|
-
const TYPES_FILE_RE = /^\.\/types\/.+\.d\.(ts|mts)$/
|
|
21
19
|
import {
|
|
22
20
|
hasIdTokenWritePermission,
|
|
23
21
|
hasNpmPublishStepWithPackage,
|
|
@@ -27,6 +25,8 @@ import {
|
|
|
27
25
|
} from './utils/gha-workflow.mjs'
|
|
28
26
|
import { walkDir } from './utils/walkDir.mjs'
|
|
29
27
|
|
|
28
|
+
const TYPES_FILE_RE = /^\.\/types\/.+\.d\.(ts|mts)$/
|
|
29
|
+
|
|
30
30
|
/** Канонічний entrypoint типів для пакетів із вихідним `.js` під каталогом `npm/src` */
|
|
31
31
|
const TYPES_INDEX = './types/index.d.ts'
|
|
32
32
|
|
|
@@ -195,7 +195,7 @@ export async function check() {
|
|
|
195
195
|
fail(`npm/package.json: при наявності .js під npm/src очікується "types": "${TYPES_INDEX}"`)
|
|
196
196
|
}
|
|
197
197
|
} else {
|
|
198
|
-
if (typeof typesField === 'string' &&
|
|
198
|
+
if (typeof typesField === 'string' && TYPES_FILE_RE.test(typesField)) {
|
|
199
199
|
pass(`npm/package.json: "types" вказує на файл під ./types/… (${typesField})`)
|
|
200
200
|
} else {
|
|
201
201
|
fail(
|
package/scripts/check-text.mjs
CHANGED
|
@@ -178,9 +178,7 @@ export async function check() {
|
|
|
178
178
|
)
|
|
179
179
|
}
|
|
180
180
|
} else {
|
|
181
|
-
fail(
|
|
182
|
-
`.oxfmtrc.json: додай масив ignorePatterns з ${OXFMT_REQUIRED_IGNORE_PATTERNS.join(', ')} (див. text.mdc)`
|
|
183
|
-
)
|
|
181
|
+
fail(`.oxfmtrc.json: додай масив ignorePatterns з ${OXFMT_REQUIRED_IGNORE_PATTERNS.join(', ')} (див. text.mdc)`)
|
|
184
182
|
}
|
|
185
183
|
} else {
|
|
186
184
|
fail('.oxfmtrc.json не існує — створи його')
|
package/scripts/check-vue.mjs
CHANGED
|
@@ -12,8 +12,6 @@ import { readFile } from 'node:fs/promises'
|
|
|
12
12
|
import { join, relative } from 'node:path'
|
|
13
13
|
|
|
14
14
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
15
|
-
|
|
16
|
-
const MAJOR_VERSION_RE = /(\d+)/
|
|
17
15
|
import {
|
|
18
16
|
findForbiddenVueImportsInSourceFile,
|
|
19
17
|
isVueImportScanSourceFile,
|
|
@@ -22,6 +20,8 @@ import {
|
|
|
22
20
|
import { walkDir } from './utils/walkDir.mjs'
|
|
23
21
|
import { getMonorepoPackageRootDirs } from './utils/workspaces.mjs'
|
|
24
22
|
|
|
23
|
+
const MAJOR_VERSION_RE = /(\d+)/
|
|
24
|
+
|
|
25
25
|
/**
|
|
26
26
|
* Формує зрозумілий для людини підпис пакета для повідомлень перевірки.
|
|
27
27
|
* @param {string} rootDir відносний шлях (`'.'` або `site` тощо)
|
|
@@ -59,11 +59,15 @@ export function lintDockerfileWithHadolint(root, absPath) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
const docker = spawnSync(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
const docker = spawnSync(
|
|
63
|
+
dockerPath,
|
|
64
|
+
['run', '--rm', '-v', `${root}:/workdir`, '-w', '/workdir', HADOLINT_IMAGE, rel],
|
|
65
|
+
{
|
|
66
|
+
cwd: root,
|
|
67
|
+
encoding: 'utf8',
|
|
68
|
+
maxBuffer: 10 * 1024 * 1024
|
|
69
|
+
}
|
|
70
|
+
)
|
|
67
71
|
if (docker.error) {
|
|
68
72
|
return {
|
|
69
73
|
ok: false,
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { parse } from 'yaml'
|
|
8
8
|
|
|
9
|
+
const CHECKOUT_USES_MARKER = 'actions/checkout@'
|
|
10
|
+
const CHECKOUT_V6_USES = 'actions/checkout@v6'
|
|
11
|
+
const LOCAL_SETUP_BUN_DEPS_MARKER = './.github/actions/setup-bun-deps'
|
|
12
|
+
const BUNX_OXLINT_FIX_RE = /bunx\s+oxlint[^\n]*--fix/u
|
|
13
|
+
|
|
9
14
|
/**
|
|
10
15
|
* Парсить workflow YAML у звичайний об’єкт; при синтаксичній помилці — `null`.
|
|
11
16
|
* @param {string} content вміст файлу
|
|
@@ -26,26 +31,12 @@ export function parseWorkflowYaml(content) {
|
|
|
26
31
|
* @returns {{ jobId: string, stepIndex: number, step: Record<string, unknown> }[]} плоский список кроків з метаданими
|
|
27
32
|
*/
|
|
28
33
|
export function flattenWorkflowSteps(root) {
|
|
29
|
-
const jobs = root?.jobs
|
|
30
|
-
if (!jobs || typeof jobs !== 'object') {
|
|
31
|
-
return []
|
|
32
|
-
}
|
|
33
34
|
/** @type {{ jobId: string, stepIndex: number, step: Record<string, unknown> }[]} */
|
|
34
35
|
const out = []
|
|
35
|
-
for (const [jobId, job] of
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
for (const [stepIndex, step] of steps.entries()) {
|
|
40
|
-
if (step && typeof step === 'object') {
|
|
41
|
-
out.push({
|
|
42
|
-
jobId,
|
|
43
|
-
stepIndex,
|
|
44
|
-
step: /** @type {Record<string, unknown>} */ (step)
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
36
|
+
for (const [jobId, job] of workflowJobsEntries(root)) {
|
|
37
|
+
const steps = workflowJobSteps(job)
|
|
38
|
+
for (const [stepIndex, step] of steps.entries()) {
|
|
39
|
+
out.push({ jobId, stepIndex, step })
|
|
49
40
|
}
|
|
50
41
|
}
|
|
51
42
|
return out
|
|
@@ -100,37 +91,15 @@ export function hasAnyStepUsesContaining(root, substrings) {
|
|
|
100
91
|
* @returns {boolean} `false`, якщо є setup без попереднього checkout
|
|
101
92
|
*/
|
|
102
93
|
export function hasCheckoutBeforeLocalSetupBunDeps(root, setupPathSubstrings) {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
const step = steps[i]
|
|
113
|
-
if (step && typeof step === 'object') {
|
|
114
|
-
const uses = getStepUses(/** @type {Record<string, unknown>} */ (step))
|
|
115
|
-
const isSetup = setupPathSubstrings.some(s => uses.includes(s))
|
|
116
|
-
if (isSetup) {
|
|
117
|
-
let foundCheckout = false
|
|
118
|
-
for (let j = 0; j < i; j++) {
|
|
119
|
-
const prev = steps[j]
|
|
120
|
-
if (prev && typeof prev === 'object') {
|
|
121
|
-
const u = getStepUses(/** @type {Record<string, unknown>} */ (prev))
|
|
122
|
-
if (u.includes('actions/checkout@')) {
|
|
123
|
-
foundCheckout = true
|
|
124
|
-
break
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
if (!foundCheckout) {
|
|
129
|
-
return false
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
94
|
+
for (const [, job] of workflowJobsEntries(root)) {
|
|
95
|
+
let hasCheckoutStep = false
|
|
96
|
+
for (const step of workflowJobSteps(job)) {
|
|
97
|
+
const uses = getStepUses(step)
|
|
98
|
+
if (uses.includes(CHECKOUT_USES_MARKER)) {
|
|
99
|
+
hasCheckoutStep = true
|
|
100
|
+
}
|
|
101
|
+
if (setupPathSubstrings.some(s => uses.includes(s)) && !hasCheckoutStep) {
|
|
102
|
+
return false
|
|
134
103
|
}
|
|
135
104
|
}
|
|
136
105
|
}
|
|
@@ -279,26 +248,15 @@ export function verifyLintJsWorkflowStructure(root) {
|
|
|
279
248
|
const usesList = steps.map(s => getStepUses(s.step))
|
|
280
249
|
const runBlob = steps.map(s => getStepRun(s.step)).join('\n')
|
|
281
250
|
|
|
282
|
-
if (!usesList.some(u => u.includes(
|
|
251
|
+
if (!usesList.some(u => u.includes(CHECKOUT_V6_USES))) {
|
|
283
252
|
failures.push('немає кроку uses: actions/checkout@v6')
|
|
284
253
|
}
|
|
285
254
|
|
|
286
|
-
|
|
287
|
-
for (const { step } of steps) {
|
|
288
|
-
const u = getStepUses(step)
|
|
289
|
-
if (u.includes('actions/checkout@v6')) {
|
|
290
|
-
const w = step.with
|
|
291
|
-
if (w && typeof w === 'object' && /** @type {Record<string, unknown>} */ (w)['persist-credentials'] === false) {
|
|
292
|
-
checkoutOk = true
|
|
293
|
-
break
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
if (!checkoutOk) {
|
|
255
|
+
if (!hasCheckoutWithPersistCredentialsFalse(steps)) {
|
|
298
256
|
failures.push('checkout@v6 без with.persist-credentials: false')
|
|
299
257
|
}
|
|
300
258
|
|
|
301
|
-
if (!usesList.some(u => u.includes(
|
|
259
|
+
if (!usesList.some(u => u.includes(LOCAL_SETUP_BUN_DEPS_MARKER))) {
|
|
302
260
|
failures.push('немає uses: ./.github/actions/setup-bun-deps')
|
|
303
261
|
}
|
|
304
262
|
|
|
@@ -312,15 +270,7 @@ export function verifyLintJsWorkflowStructure(root) {
|
|
|
312
270
|
failures.push('у run немає bunx jscpd .')
|
|
313
271
|
}
|
|
314
272
|
|
|
315
|
-
|
|
316
|
-
const run = getStepRun(step)
|
|
317
|
-
if (/bunx\s+oxlint[^\n]*--fix/u.test(run)) {
|
|
318
|
-
failures.push('у run є oxlint з --fix (у CI заборонено)')
|
|
319
|
-
}
|
|
320
|
-
if (run.includes('eslint --fix')) {
|
|
321
|
-
failures.push('у run є eslint --fix (у CI заборонено)')
|
|
322
|
-
}
|
|
323
|
-
}
|
|
273
|
+
appendCiFixFlagFailures(failures, steps)
|
|
324
274
|
|
|
325
275
|
return failures.length === 0 ? { ok: true, failures: [] } : { ok: false, failures }
|
|
326
276
|
}
|
|
@@ -348,3 +298,71 @@ export function anyRunStepIncludes(root, needle) {
|
|
|
348
298
|
export function anyRunStepIncludesStylelint(root) {
|
|
349
299
|
return anyRunStepIncludes(root, 'npx stylelint')
|
|
350
300
|
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Повертає jobs як список пар [jobId, job], якщо структура валідна.
|
|
304
|
+
* @param {Record<string, unknown>} root корінь workflow
|
|
305
|
+
* @returns {[string, Record<string, unknown>][]} список jobs
|
|
306
|
+
*/
|
|
307
|
+
function workflowJobsEntries(root) {
|
|
308
|
+
const jobs = root?.jobs
|
|
309
|
+
if (!jobs || typeof jobs !== 'object') {
|
|
310
|
+
return []
|
|
311
|
+
}
|
|
312
|
+
return Object.entries(jobs).flatMap(([jobId, job]) =>
|
|
313
|
+
job && typeof job === 'object' ? [[jobId, /** @type {Record<string, unknown>} */ (job)]] : []
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Повертає валідні кроки job.
|
|
319
|
+
* @param {Record<string, unknown>} job job-об’єкт
|
|
320
|
+
* @returns {Record<string, unknown>[]} кроки job
|
|
321
|
+
*/
|
|
322
|
+
function workflowJobSteps(job) {
|
|
323
|
+
const steps = /** @type {{ steps?: unknown }} */ (job).steps
|
|
324
|
+
if (!Array.isArray(steps)) {
|
|
325
|
+
return []
|
|
326
|
+
}
|
|
327
|
+
return steps.flatMap(step => (step && typeof step === 'object' ? [/** @type {Record<string, unknown>} */ (step)] : []))
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Чи є checkout@v6 з `persist-credentials: false`.
|
|
332
|
+
* @param {{ step: Record<string, unknown> }[]} steps кроки flattenWorkflowSteps
|
|
333
|
+
* @returns {boolean} true, якщо знайдено очікуваний checkout
|
|
334
|
+
*/
|
|
335
|
+
function hasCheckoutWithPersistCredentialsFalse(steps) {
|
|
336
|
+
for (const { step } of steps) {
|
|
337
|
+
const uses = getStepUses(step)
|
|
338
|
+
if (uses.includes(CHECKOUT_V6_USES)) {
|
|
339
|
+
const withObj = step.with
|
|
340
|
+
if (
|
|
341
|
+
withObj &&
|
|
342
|
+
typeof withObj === 'object' &&
|
|
343
|
+
/** @type {Record<string, unknown>} */ (withObj)['persist-credentials'] === false
|
|
344
|
+
) {
|
|
345
|
+
return true
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return false
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Додає порушення для `--fix` у CI-кроках lint-js workflow.
|
|
354
|
+
* @param {string[]} failures акумулятор порушень
|
|
355
|
+
* @param {{ step: Record<string, unknown> }[]} steps кроки flattenWorkflowSteps
|
|
356
|
+
* @returns {void}
|
|
357
|
+
*/
|
|
358
|
+
function appendCiFixFlagFailures(failures, steps) {
|
|
359
|
+
for (const { step } of steps) {
|
|
360
|
+
const run = getStepRun(step)
|
|
361
|
+
if (BUNX_OXLINT_FIX_RE.test(run)) {
|
|
362
|
+
failures.push('у run є oxlint з --fix (у CI заборонено)')
|
|
363
|
+
}
|
|
364
|
+
if (run.includes('eslint --fix')) {
|
|
365
|
+
failures.push('у run є eslint --fix (у CI заборонено)')
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
@@ -10,6 +10,43 @@ import { dirname, join, relative } from 'node:path'
|
|
|
10
10
|
|
|
11
11
|
const TRAILING_SLASH_RE = /\/$/
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Нормалізує workspace-патерн до POSIX-формату і прибирає хвостові `/`.
|
|
15
|
+
* @param {string} pattern сирий workspace-патерн
|
|
16
|
+
* @returns {string} нормалізований патерн або `.`
|
|
17
|
+
*/
|
|
18
|
+
function normalizeWorkspacePattern(pattern) {
|
|
19
|
+
let normalized = pattern.replaceAll('\\', '/')
|
|
20
|
+
while (TRAILING_SLASH_RE.test(normalized)) {
|
|
21
|
+
normalized = normalized.slice(0, -1)
|
|
22
|
+
}
|
|
23
|
+
return normalized || '.'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Додає каталоги пакетів до set за workspace-патерном.
|
|
28
|
+
* @param {Set<string>} roots set коренів пакетів
|
|
29
|
+
* @param {string} repoRoot корінь репозиторію
|
|
30
|
+
* @param {string} workspacePattern нормалізований workspace-патерн
|
|
31
|
+
* @returns {Promise<void>}
|
|
32
|
+
*/
|
|
33
|
+
async function addWorkspaceRootsByPattern(roots, repoRoot, workspacePattern) {
|
|
34
|
+
if (workspacePattern.includes('*')) {
|
|
35
|
+
const globPat = `${workspacePattern}/package.json`
|
|
36
|
+
for await (const relPkgJsonPath of glob(globPat, { cwd: repoRoot })) {
|
|
37
|
+
const absPkgJsonPath = join(repoRoot, relPkgJsonPath)
|
|
38
|
+
const relRoot = relative(repoRoot, dirname(absPkgJsonPath))
|
|
39
|
+
roots.add(relRoot === '' ? '.' : relRoot)
|
|
40
|
+
}
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const pkgJsonPath = join(repoRoot, workspacePattern, 'package.json')
|
|
45
|
+
if (existsSync(pkgJsonPath)) {
|
|
46
|
+
roots.add(workspacePattern)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
13
50
|
/**
|
|
14
51
|
* Нормалізує поле `workspaces` з package.json до масиву шляхів / glob-патернів.
|
|
15
52
|
* @param {unknown} workspaces значення `workspaces` з кореневого package.json
|
|
@@ -37,22 +74,8 @@ export async function getMonorepoPackageRootDirs(repoRoot = '.') {
|
|
|
37
74
|
}
|
|
38
75
|
const pkg = JSON.parse(await readFile(rootPkgPath, 'utf8'))
|
|
39
76
|
for (const raw of normalizeWorkspacePatterns(pkg.workspaces)) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
w = w.slice(0, -1)
|
|
43
|
-
}
|
|
44
|
-
w = w || '.'
|
|
45
|
-
if (w.includes('*')) {
|
|
46
|
-
const globPat = `${w}/package.json`
|
|
47
|
-
for await (const f of glob(globPat, { cwd: repoRoot })) {
|
|
48
|
-
const abs = join(repoRoot, f)
|
|
49
|
-
const rel = relative(repoRoot, dirname(abs))
|
|
50
|
-
roots.add(rel === '' ? '.' : rel)
|
|
51
|
-
}
|
|
52
|
-
} else {
|
|
53
|
-
const pkgJson = join(repoRoot, w, 'package.json')
|
|
54
|
-
if (existsSync(pkgJson)) roots.add(w)
|
|
55
|
-
}
|
|
77
|
+
const workspacePattern = normalizeWorkspacePattern(raw)
|
|
78
|
+
await addWorkspaceRootsByPattern(roots, repoRoot, workspacePattern)
|
|
56
79
|
}
|
|
57
80
|
const list = [...roots]
|
|
58
81
|
list.sort((a, b) => {
|