@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.
@@ -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` тощо)
@@ -52,99 +52,94 @@ function ukFilesCountPhrase(n) {
52
52
  }
53
53
 
54
54
  /**
55
- * Перевіряє залежності та vite.config одного Vue-пакета.
56
- * @param {string} rootDir відносний шлях до пакета
57
- * @param {(msg: string) => void} fail функція зворотного виклику для реєстрації помилки перевірки
58
- * @param {(msg: string) => void} passFn успішне повідомлення (як у check-reporter)
59
- * @returns {Promise<void>} завершується після перевірок залежностей, `vite.config` і сканування джерел на імпорти з `vue`
55
+ * Перевіряє наявність залежності в об'єкті deps.
56
+ * @param {Record<string,string>} deps об'єкт залежностей
57
+ * @param {string} name ім'я пакета
58
+ * @param {string} prefix префікс повідомлення
59
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
60
+ * @param {(msg: string) => void} fail callback при помилці
61
+ * @param {string} hint підказка при відсутності
60
62
  */
61
- async function checkVuePackage(rootDir, fail, passFn) {
62
- const label = packageLabel(rootDir)
63
- const prefix = `[${label}] `
64
-
65
- const pkgPath = join(rootDir, 'package.json')
66
- const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
67
- const deps = pkg.dependencies || {}
68
- const devDeps = pkg.devDependencies || {}
69
- const allDeps = { ...deps, ...devDeps }
70
-
71
- if (deps.vue) {
72
- passFn(`${prefix}vue в dependencies: ${deps.vue}`)
63
+ function checkRequiredDep(deps, name, prefix, passFn, fail, hint = `${name} відсутній`) {
64
+ if (deps[name]) {
65
+ passFn(`${prefix}${name}: ${deps[name]}`)
73
66
  } else {
74
- fail(`${prefix}vue відсутній в dependencies`)
67
+ fail(`${prefix}${hint}`)
75
68
  }
69
+ }
76
70
 
77
- if (devDeps.vite) {
78
- const match = devDeps.vite.match(MAJOR_VERSION_RE)
79
- if (match && Number(match[1]) >= 8) {
80
- passFn(`${prefix}vite >= 8: ${devDeps.vite}`)
81
- } else {
82
- fail(`${prefix}vite має бути >= 8, знайдено: ${devDeps.vite}`)
83
- }
84
- } else {
71
+ /**
72
+ * Перевіряє версію vite у devDependencies.
73
+ * @param {Record<string,string>} devDeps devDependencies з package.json
74
+ * @param {string} prefix параметр prefix
75
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
76
+ * @param {(msg: string) => void} fail callback при помилці
77
+ */
78
+ function checkViteVersion(devDeps, prefix, passFn, fail) {
79
+ const v = devDeps.vite
80
+ if (!v) {
85
81
  fail(`${prefix}vite відсутній в devDependencies`)
82
+ return
86
83
  }
87
-
88
- if (devDeps['@vitejs/plugin-vue']) {
89
- passFn(`${prefix}@vitejs/plugin-vue: ${devDeps['@vitejs/plugin-vue']}`)
90
- } else {
91
- fail(`${prefix}@vitejs/plugin-vue відсутній в devDependencies`)
92
- }
93
-
94
- if (allDeps['vue-macros']) {
95
- passFn(`${prefix}vue-macros: ${allDeps['vue-macros']}`)
96
- } else {
97
- fail(`${prefix}vue-macros відсутній — bun add -d vue-macros`)
98
- }
99
-
100
- if (allDeps['unplugin-auto-import']) {
101
- passFn(`${prefix}unplugin-auto-import присутній`)
102
- } else {
103
- fail(`${prefix}unplugin-auto-import відсутній — bun add -d unplugin-auto-import`)
104
- }
105
-
106
- if (allDeps['vite-plugin-vue-layouts-next']) {
107
- passFn(`${prefix}vite-plugin-vue-layouts-next присутній`)
84
+ const match = v.match(MAJOR_VERSION_RE)
85
+ if (match && Number(match[1]) >= 8) {
86
+ passFn(`${prefix}vite >= 8: ${v}`)
108
87
  } else {
109
- fail(`${prefix}vite-plugin-vue-layouts-next відсутній bun add -d vite-plugin-vue-layouts-next`)
88
+ fail(`${prefix}vite має бути >= 8, знайдено: ${v}`)
110
89
  }
90
+ }
111
91
 
92
+ /**
93
+ * Перевіряє vite.config на наявність VueMacros і AutoImport.
94
+ * @param {string} rootDir параметр rootDir
95
+ * @param {string} prefix параметр prefix
96
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
97
+ * @param {(msg: string) => void} fail callback при помилці
98
+ */
99
+ async function checkViteConfig(rootDir, prefix, passFn, fail) {
112
100
  const configFiles = ['vite.config.js', 'vite.config.ts', 'vite.config.mjs']
113
101
  const viteConfig = configFiles.find(f => existsSync(join(rootDir, f)))
114
- if (viteConfig) {
115
- const relConfig = join(rootDir, viteConfig)
116
- const content = await readFile(relConfig, 'utf8')
117
- if (content.includes('VueMacros')) {
118
- passFn(`${prefix}${viteConfig} використовує VueMacros`)
119
- } else {
120
- fail(`${prefix}${viteConfig} не містить VueMacros`)
121
- }
122
- if (content.includes('AutoImport')) {
123
- passFn(`${prefix}${viteConfig} використовує AutoImport`)
102
+ if (!viteConfig) {
103
+ fail(`${prefix}немає vite.config.js|ts|mjs у каталозі пакета`)
104
+ return
105
+ }
106
+ const content = await readFile(join(rootDir, viteConfig), 'utf8')
107
+ const checks = [
108
+ { token: 'VueMacros', ok: `${viteConfig} використовує VueMacros`, err: `${viteConfig} не містить VueMacros` },
109
+ { token: 'AutoImport', ok: `${viteConfig} використовує AutoImport`, err: `${viteConfig} не містить AutoImport` }
110
+ ]
111
+ for (const { token, ok, err } of checks) {
112
+ if (content.includes(token)) {
113
+ passFn(`${prefix}${ok}`)
124
114
  } else {
125
- fail(`${prefix}${viteConfig} не містить AutoImport`)
115
+ fail(`${prefix}${err}`)
126
116
  }
127
- } else {
128
- fail(`${prefix}немає vite.config.js|ts|mjs у каталозі пакета`)
129
117
  }
118
+ }
130
119
 
131
- const absPackageRoot = join(process.cwd(), rootDir)
120
+ /**
121
+ * Сканує джерела пакета на заборонені value-імпорти з vue.
122
+ * @param {string} rootDir параметр rootDir
123
+ * @param {string} absPackageRoot параметр absPackageRoot
124
+ * @param {string} prefix параметр prefix
125
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
126
+ * @param {(msg: string) => void} fail callback при помилці
127
+ */
128
+ async function checkVueImportViolations(rootDir, absPackageRoot, prefix, passFn, fail) {
132
129
  /** @type {string[]} */
133
130
  const sourcePaths = []
134
131
  await walkDir(absPackageRoot, absPath => {
135
132
  const rel = relative(absPackageRoot, absPath).split('\\').join('/')
136
- if (shouldSkipFileForVueImportScan(rel) || !isVueImportScanSourceFile(rel)) {
137
- return
133
+ if (!shouldSkipFileForVueImportScan(rel) && isVueImportScanSourceFile(rel)) {
134
+ sourcePaths.push(absPath)
138
135
  }
139
- sourcePaths.push(absPath)
140
136
  })
141
137
 
142
138
  let importViolations = 0
143
139
  for (const absPath of sourcePaths) {
144
140
  const rel = relative(absPackageRoot, absPath).split('\\').join('/')
145
141
  const content = await readFile(absPath, 'utf8')
146
- const hits = findForbiddenVueImportsInSourceFile(content, rel)
147
- for (const v of hits) {
142
+ for (const v of findForbiddenVueImportsInSourceFile(content, rel)) {
148
143
  importViolations++
149
144
  fail(`${prefix}${rel}:${v.line} — прибери явний value-імпорт з 'vue' (unplugin-auto-import): ${v.snippet}`)
150
145
  }
@@ -156,6 +151,52 @@ async function checkVuePackage(rootDir, fail, passFn) {
156
151
  }
157
152
  }
158
153
 
154
+ /**
155
+ * Перевіряє залежності та vite.config одного Vue-пакета.
156
+ * @param {string} rootDir відносний шлях до пакета
157
+ * @param {(msg: string) => void} fail функція зворотного виклику для реєстрації помилки перевірки
158
+ * @param {(msg: string) => void} passFn успішне повідомлення (як у check-reporter)
159
+ * @returns {Promise<void>} завершується після перевірок залежностей, `vite.config` і сканування джерел на імпорти з `vue`
160
+ */
161
+ async function checkVuePackage(rootDir, fail, passFn) {
162
+ const prefix = `[${packageLabel(rootDir)}] `
163
+ const pkg = JSON.parse(await readFile(join(rootDir, 'package.json'), 'utf8'))
164
+ const deps = pkg.dependencies || {}
165
+ const devDeps = pkg.devDependencies || {}
166
+ const allDeps = { ...deps, ...devDeps }
167
+
168
+ checkRequiredDep(deps, 'vue', prefix, passFn, fail, 'vue відсутній в dependencies')
169
+ checkViteVersion(devDeps, prefix, passFn, fail)
170
+ checkRequiredDep(
171
+ devDeps,
172
+ '@vitejs/plugin-vue',
173
+ prefix,
174
+ passFn,
175
+ fail,
176
+ '@vitejs/plugin-vue відсутній в devDependencies'
177
+ )
178
+ checkRequiredDep(allDeps, 'vue-macros', prefix, passFn, fail, 'vue-macros відсутній — bun add -d vue-macros')
179
+ checkRequiredDep(
180
+ allDeps,
181
+ 'unplugin-auto-import',
182
+ prefix,
183
+ passFn,
184
+ fail,
185
+ 'unplugin-auto-import відсутній — bun add -d unplugin-auto-import'
186
+ )
187
+ checkRequiredDep(
188
+ allDeps,
189
+ 'vite-plugin-vue-layouts-next',
190
+ prefix,
191
+ passFn,
192
+ fail,
193
+ 'vite-plugin-vue-layouts-next відсутній — bun add -d vite-plugin-vue-layouts-next'
194
+ )
195
+
196
+ await checkViteConfig(rootDir, prefix, passFn, fail)
197
+ await checkVueImportViolations(rootDir, join(process.cwd(), rootDir), prefix, passFn, fail)
198
+ }
199
+
159
200
  /**
160
201
  * Перевіряє відповідність проєкту правилам vue.mdc (корінь і всі workspace-пакети з `vue` у dependencies).
161
202
  * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
@@ -59,11 +59,15 @@ export function lintDockerfileWithHadolint(root, absPath) {
59
59
  }
60
60
  }
61
61
 
62
- const docker = spawnSync(dockerPath, ['run', '--rm', '-v', `${root}:/workdir`, '-w', '/workdir', HADOLINT_IMAGE, rel], {
63
- cwd: root,
64
- encoding: 'utf8',
65
- maxBuffer: 10 * 1024 * 1024
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 Object.entries(jobs)) {
36
- if (job && typeof job === 'object') {
37
- const steps = /** @type {{ steps?: unknown }} */ (job).steps
38
- if (Array.isArray(steps)) {
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 jobs = root?.jobs
104
- if (!jobs || typeof jobs !== 'object') {
105
- return true
106
- }
107
- for (const job of Object.values(jobs)) {
108
- if (job && typeof job === 'object') {
109
- const steps = /** @type {{ steps?: unknown }} */ (job).steps
110
- if (Array.isArray(steps)) {
111
- for (let i = 0; i < steps.length; i++) {
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('actions/checkout@v6'))) {
251
+ if (!usesList.some(u => u.includes(CHECKOUT_V6_USES))) {
283
252
  failures.push('немає кроку uses: actions/checkout@v6')
284
253
  }
285
254
 
286
- let checkoutOk = false
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('./.github/actions/setup-bun-deps'))) {
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
- for (const { step } of steps) {
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,73 @@ 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 =>
328
+ step && typeof step === 'object' ? [/** @type {Record<string, unknown>} */ (step)] : []
329
+ )
330
+ }
331
+
332
+ /**
333
+ * Чи є checkout@v6 з `persist-credentials: false`.
334
+ * @param {{ step: Record<string, unknown> }[]} steps кроки flattenWorkflowSteps
335
+ * @returns {boolean} true, якщо знайдено очікуваний checkout
336
+ */
337
+ function hasCheckoutWithPersistCredentialsFalse(steps) {
338
+ for (const { step } of steps) {
339
+ const uses = getStepUses(step)
340
+ if (uses.includes(CHECKOUT_V6_USES)) {
341
+ const withObj = step.with
342
+ if (
343
+ withObj &&
344
+ typeof withObj === 'object' &&
345
+ /** @type {Record<string, unknown>} */ (withObj)['persist-credentials'] === false
346
+ ) {
347
+ return true
348
+ }
349
+ }
350
+ }
351
+ return false
352
+ }
353
+
354
+ /**
355
+ * Додає порушення для `--fix` у CI-кроках lint-js workflow.
356
+ * @param {string[]} failures акумулятор порушень
357
+ * @param {{ step: Record<string, unknown> }[]} steps кроки flattenWorkflowSteps
358
+ * @returns {void}
359
+ */
360
+ function appendCiFixFlagFailures(failures, steps) {
361
+ for (const { step } of steps) {
362
+ const run = getStepRun(step)
363
+ if (BUNX_OXLINT_FIX_RE.test(run)) {
364
+ failures.push('у run є oxlint з --fix (у CI заборонено)')
365
+ }
366
+ if (run.includes('eslint --fix')) {
367
+ failures.push('у run є eslint --fix (у CI заборонено)')
368
+ }
369
+ }
370
+ }
@@ -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
- let w = raw.replaceAll('\\', '/')
41
- while (TRAILING_SLASH_RE.test(w)) {
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) => {