@7n/rules-lang-js 0.10.0 → 0.12.0

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/package.json +1 -1
  3. package/rules/bun/package_json/package_json.mdc +2 -2
  4. package/rules/bun/package_json/package_json.rego +26 -10
  5. package/rules/npm-module/npm_package_json/npm_package_json.mdc +1 -1
  6. package/rules/npm-module/npm_package_json/npm_package_json.rego +14 -5
  7. package/rules/storybook/adopt/docs/main.md +1 -2
  8. package/rules/storybook/adopt/main.mjs +37 -5
  9. package/rules/storybook/ci/concern.json +15 -0
  10. package/rules/storybook/ci/docs/fix-ci.md +39 -0
  11. package/rules/storybook/ci/docs/index.md +10 -0
  12. package/rules/storybook/ci/docs/main.md +50 -0
  13. package/rules/storybook/ci/fix-ci.mjs +105 -0
  14. package/rules/storybook/ci/main.mjs +112 -0
  15. package/rules/storybook/ci/template/lint-storybook.yml.snippet.yml +41 -0
  16. package/rules/storybook/ci/template/setup-playwright-chromium.action.yml +34 -0
  17. package/rules/storybook/hygiene/concern.json +1 -1
  18. package/rules/storybook/main.json +1 -1
  19. package/rules/storybook/main.mdc +24 -3
  20. package/rules/storybook/mocking/mocking.mdc +24 -0
  21. package/rules/storybook/scaffold/concern.json +1 -1
  22. package/rules/storybook/scaffold/docs/fix-scaffold.md +1 -1
  23. package/rules/storybook/scaffold/docs/main.md +1 -1
  24. package/rules/storybook/scaffold/fix-scaffold.mjs +50 -0
  25. package/rules/storybook/scaffold/main.mjs +68 -6
  26. package/rules/storybook/scaffold/template/docs/empty-vite.config.md +28 -0
  27. package/rules/storybook/scaffold/template/docs/index.md +5 -4
  28. package/rules/storybook/scaffold/template/docs/main.md +1 -2
  29. package/rules/storybook/scaffold/template/docs/preview.md +1 -2
  30. package/rules/storybook/scaffold/template/empty-vite.config.js +18 -0
  31. package/rules/storybook/scaffold/template/main.js +76 -6
  32. package/rules/storybook/scaffold/template/preview.js +13 -5
  33. package/rules/storybook/vitest-config/concern.json +1 -1
  34. package/rules/storybook/vitest-config/docs/fix-vitest-config.md +1 -2
  35. package/rules/storybook/vitest-config/docs/main.md +1 -2
  36. package/rules/storybook/vitest-config/fix-vitest-config.mjs +22 -6
  37. package/rules/storybook/vitest-config/main.mjs +33 -2
  38. package/rules/storybook/vitest-config/template/docs/storybook-project-entry.md +1 -2
  39. package/rules/storybook/vitest-config/template/docs/vitest.config.baseline.md +1 -2
  40. package/rules/storybook/vitest-config/template/storybook-project-entry.js +6 -2
  41. package/rules/storybook/vitest-config/template/vitest.config.baseline.mjs +2 -1
  42. package/rules/storybook/vitest-config/vitest-config.mdc +7 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.12.0] - 2026-07-21
4
+
5
+ ### Added
6
+
7
+ - storybook: новий concern `ci` (ADR Кластер 5, CI-частина) — канонічний composite action `setup-playwright-chromium` (кеш Playwright-браузерів, лише chromium) і `.github/workflows/lint-storybook.yml` (швидкий PR-прогін `vitest --project=storybook`), гейтований `requires.capability: ci:github`
8
+
9
+ ### Fixed
10
+
11
+ - storybook: viteFinal-фільтр стійкий до VueMacros-стека (Promise/масив-резолв, сімейний фільтр vite:*/vue-macros), vitest@^4 provider-factory (@vitest/browser-playwright) замість застарілого рядка `'playwright'`, flat-root layout у detectStoriesGlob (components без src/), точковий alias-мок одного модуля в mocking.mdc, і STORIES_RE false positive на `storybookTest({ configDir })` без явного include — усе за результатами пілота adopt-діагностики на nitra/components. Заодно governance package_json.rego: allowlist доповнено `@vitest/browser-playwright`.
12
+ - canon Storybook: viteConfigPath-обхід (empty-vite.config.js), валідний iconSet-імпорт, mswLoader замість mswDecorator, повний .storybook/**-glob у CI/lint, вирівняні governance-піни (storybook ^10.5.3, root Vite build-tooling deps), knip-виключення для .storybook-артефактів
13
+
14
+ ## [0.11.0] - 2026-07-21
15
+
16
+ ### Added
17
+
18
+ - storybook: новий concern `ci` (ADR Кластер 5, CI-частина) — канонічний composite action `setup-playwright-chromium` (кеш Playwright-браузерів, лише chromium) і `.github/workflows/lint-storybook.yml` (швидкий PR-прогін `vitest --project=storybook`), гейтований `requires.capability: ci:github`
19
+
20
+ ### Fixed
21
+
22
+ - storybook: viteFinal-фільтр стійкий до VueMacros-стека (Promise/масив-резолв, сімейний фільтр vite:*/vue-macros), vitest@^4 provider-factory (@vitest/browser-playwright) замість застарілого рядка `'playwright'`, flat-root layout у detectStoriesGlob (components без src/), точковий alias-мок одного модуля в mocking.mdc, і STORIES_RE false positive на `storybookTest({ configDir })` без явного include — усе за результатами пілота adopt-діагностики на nitra/components. Заодно governance package_json.rego: allowlist доповнено `@vitest/browser-playwright`.
23
+
3
24
  ## [0.10.0] - 2026-07-21
4
25
 
5
26
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules-lang-js",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Плагін @7n/rules: JS/npm/bun-екосистема — lint-правила (js/bun/vue/js-run/npm-module/db), taze-провайдер (package.json, bunx taze) і doc-files-екстрактори (oxc AST)",
5
5
  "keywords": [
6
6
  "javascript",
@@ -9,8 +9,8 @@ Rego-пакет: `bun.package_json`
9
9
  Gate виносить два класи deny:
10
10
 
11
11
  1. **Заборонені top-level поля** — будь-яке поле з `package.json.deny.json` присутнє у файлі (навіть із порожнім значенням `{}`).
12
- 2. **devDependencies не з білого списку** — дозволені лише `@nitra/*`/`@7n/*` та root-only тестові peer/tools (`vitest`, `@vitest/coverage-v8`, `@vitest/browser`, `@stryker-mutator/vitest-runner`, `@stryker-mutator/core`, `@playwright/test`, `playwright`, `@storybook/addon-vitest`, `@7n/test`). Будь-який інший пакет → deny. CLI-тули, які `n-rules lint` спавнить через `bunx` (oxlint, jscpd, v8r, github-actionlint тощо), у root не пінуються — вони приїжджають як `dependencies` пакета `@7n/rules` (npm-module.mdc).
12
+ 2. **devDependencies не з білого списку** — дозволені лише `@nitra/*`/`@7n/*` та root-only тестові peer/tools (`vitest`, `@vitest/coverage-v8`, `@vitest/browser`, `@vitest/browser-playwright`, `@stryker-mutator/vitest-runner`, `@stryker-mutator/core`, `@playwright/test`, `playwright`, `@storybook/addon-vitest`, `@7n/test`). Будь-який інший пакет → deny. CLI-тули, які `n-rules lint` спавнить через `bunx` (oxlint, jscpd, v8r, github-actionlint тощо), у root не пінуються — вони приїжджають як `dependencies` пакета `@7n/rules` (npm-module.mdc).
13
13
 
14
- `@vitest/browser`/`playwright`/`@storybook/addon-vitest` — додані для named vitest project "storybook" (канон Storybook, кластер 5: browser-mode, лише chromium) — той самий root vitest.config, що й `unit`-проект; `@storybook/addon-vitest` постачає `storybookTest`-плагін для цього vitest-конфіга (канонічний template правила `storybook`). **Межа з `npm-module.mdc`**: Storybook-специфічні identity-пакети (`storybook`, `@storybook/vue3-vite`, `@storybook/vue3`, `msw`, `msw-storybook-addon`) у цей allowlist **не** додаються — вони живуть у `npm/package.json` консюмер-пакета (канон Storybook, кластер 7 Governance), бо `isStorybookRoot()` у `@7n/test` детектує Storybook-скоуп саме за тим файлом, не кореневим. `@storybook/addon-vitest` — виняток із цієї межі: він test-tooling (vitest-плагін), а не identity-маркер, тож root, як і решта vitest-peer'ів.
14
+ `@vitest/browser`/`@vitest/browser-playwright`/`playwright`/`@storybook/addon-vitest` — додані для named vitest project "storybook" (канон Storybook, кластер 5: browser-mode, лише chromium) — той самий root vitest.config, що й `unit`-проект; `@storybook/addon-vitest` постачає `storybookTest`-плагін для цього vitest-конфіга (канонічний template правила `storybook`). `@vitest/browser-playwright` — окремий npm-пакет: vitest@^4 прибрав рядкове API browser-провайдера (`provider: 'playwright'`), потрібна factory `import { playwright } from '@vitest/browser-playwright'` → `provider: playwright()` (звірено з реальним `bun.lock` пілотного консюмера — усі три `@vitest/*`/`playwright`-пакети встановлені поруч). **Межа з `npm-module.mdc`**: Storybook-специфічні identity-пакети (`storybook`, `@storybook/vue3-vite`, `@storybook/vue3`, `msw`, `msw-storybook-addon`) у цей allowlist **не** додаються — вони живуть у `npm/package.json` консюмер-пакета (канон Storybook, кластер 7 Governance), бо `isStorybookRoot()` у `@7n/test` детектує Storybook-скоуп саме за тим файлом, не кореневим. `@storybook/addon-vitest` — виняток із цієї межі: він test-tooling (vitest-плагін), а не identity-маркер, тож root, як і решта vitest-peer'ів.
15
15
 
16
16
  Перевірки, що потребують FS або cross-file контексту (наприклад наявність `yarn.lock`), лишаються у JS-шарі.
@@ -8,10 +8,11 @@
8
8
  # - `devDependencies` лише `@nitra/*` + root-only тестові peer/tools для `@7n/test coverage`
9
9
  # (правило `test` enabled завжди — див. `test/auto.md`; published workspace-и не мають
10
10
  # `devDependencies` за `npm-module.mdc`)
11
- # - `@vitest/browser`/`playwright`/`@storybook/addon-vitest` (browser-mode provider +
12
- # `storybookTest`-плагін для named vitest project "storybook", лише chromium — канон
13
- # Storybook кластер 5) теж root-only test peers: той самий vitest.config, що й
14
- # `unit`-проект, живе в корені монорепо-споживача. Storybook-специфічні
11
+ # - `@vitest/browser`/`@vitest/browser-playwright`/`playwright`/`@storybook/addon-vitest`
12
+ # (browser-mode provider factory + `storybookTest`-плагін для named vitest project
13
+ # "storybook", лише chromium — канон Storybook кластер 5) теж root-only test peers:
14
+ # той самий vitest.config, що й `unit`-проект, живе в корені монорепо-споживача.
15
+ # Storybook-специфічні
15
16
  # identity-пакети (`storybook`, `@storybook/vue3*`, `msw*`) НЕ сюди — вони живуть у
16
17
  # `npm/package.json` (канон Storybook кластер 7, `npm-module.mdc`), бо
17
18
  # `isStorybookRoot()` @7n/test читає саме той файл, не кореневий package.json.
@@ -59,25 +60,40 @@ deny contains msg if {
59
60
  # @stryker-mutator/core — обов'язковий exact-pin peer vitest-runner@9+ (раніше тягнувся транзитивно)
60
61
  # @7n/test — оркестратор `coverage` (npx @7n/test coverage); devDependency, щоб npx резолвив
61
62
  # локально без мережевого fetch щоразу.
62
- # @vitest/browser + playwright — провайдер browser-mode для named vitest project
63
- # "storybook" (канон Storybook кластер 5: лише chromium, PR — швидкий
64
- # --project=storybook). `playwright` (не `@playwright/test`) сирий driver, який
65
- # @vitest/browser використовує як provider; `@playwright/test` лишається окремо для
66
- # змістовних E2E-сценаріїв (n-vue.mdc).
63
+ # @vitest/browser + @vitest/browser-playwright + playwright — провайдер browser-mode для
64
+ # named vitest project "storybook" (канон Storybook кластер 5: лише chromium, PR —
65
+ # швидкий --project=storybook). vitest@^4 прибрав рядкове API провайдера
66
+ # (`provider: 'playwright'`) `@vitest/browser-playwright` постачає factory
67
+ # `import { playwright } from '@vitest/browser-playwright'` → `provider: playwright()`,
68
+ # окремий npm-пакет, не входить у `@vitest/browser` (звірено з реальним bun.lock пілота —
69
+ # усі три пакети встановлені поруч). `playwright` (не `@playwright/test`) — сирий driver,
70
+ # який `@vitest/browser-playwright` використовує як provider; `@playwright/test` лишається
71
+ # окремо для змістовних E2E-сценаріїв (n-vue.mdc).
67
72
  # @storybook/addon-vitest — постачає `storybookTest` для vitest-плагіна в канонічному
68
73
  # vitest.config named-проекту "storybook" (той самий канон Storybook кластер 5); версія
69
- # з лінійки Storybook 9.x (узгоджена з `storybook`@9.1.10, запіненим у
74
+ # з лінійки Storybook 10.x (узгоджена з `storybook`@^10.5.3, запіненим у
70
75
  # npm_package_json.rego) — allowlist тут за іменем, точний пінінг версії root-tooling
71
76
  # не робимо (на відміну від Storybook-identity-пакетів у npm/package.json).
77
+ # @storybook/builder-vite/@vitejs/plugin-vue/@quasar/vite-plugin — root-only build-tooling
78
+ # для канонічного `.storybook/main.js` (storybook.mdc scaffold: `core.builder`,
79
+ # `viteFinal`-плагіни `vue()`/`quasar()`); @quasar/extras — постачає material-icons
80
+ # CSS/fallback-набір для `.storybook/preview.js` (iconMapFn). Усі чотири звірено з
81
+ # реальним root package.json пілотного консюмера (components) — раніше були відсутні
82
+ # в allowlist, хоча канонічний скафолд Storybook уже на них покладався (реальний gap).
72
83
  allowed_root_test_deps := {
73
84
  "vitest",
74
85
  "@vitest/coverage-v8",
75
86
  "@vitest/browser",
87
+ "@vitest/browser-playwright",
76
88
  "@stryker-mutator/vitest-runner",
77
89
  "@stryker-mutator/core",
78
90
  "@playwright/test",
79
91
  "playwright",
80
92
  "@storybook/addon-vitest",
93
+ "@storybook/builder-vite",
94
+ "@vitejs/plugin-vue",
95
+ "@quasar/vite-plugin",
96
+ "@quasar/extras",
81
97
  "@7n/test",
82
98
  }
83
99
 
@@ -62,7 +62,7 @@ FS-перевірки (наявність файлу зі шляху `types`, с
62
62
  ✓ Правильно — канонічний Storybook-devDep із зафіксованою версією (канон Storybook):
63
63
 
64
64
  ```json
65
- { "devDependencies": { "storybook": "9.1.10" } }
65
+ { "devDependencies": { "storybook": "^10.5.3" } }
66
66
  ```
67
67
 
68
68
  ✗ Неправильно — Storybook-devDep присутній, але версія не збігається з каноном:
@@ -41,12 +41,21 @@ storybook_version_template := concat(" ", [
41
41
  ])
42
42
 
43
43
  # Канонічні Storybook-devDeps (isStorybookRoot()-маркери, канон Storybook кластер 7):
44
- # зафіксована точна версія єдина дозволена версія для кожного пакета. Оновлення
45
- # ручна правка цієї map.
44
+ # зафіксований точний РЯДОК версії, як він має бути записаний у package.json (включно з
45
+ # `^`, якщо канон його використовує) — єдине дозволене значення для кожного пакета.
46
+ # Оновлення — ручна правка цієї map, після емпіричної верифікації (звірка з реальним
47
+ # консюмером, що пройшов повний прогін storybook build + тести на цих версіях).
48
+ #
49
+ # storybook/@storybook/vue3-vite піднято з 9.1.10 (exact) на ^10.5.3 — пілотний консюмер
50
+ # (components/npm/package.json) реально пройшов storybook build + 58/58 тестів саме на
51
+ # 10.5.3 з caret-діапазоном, а не на exact-пінованій 9.1.10 (яка на практиці ніде вже не
52
+ # встановлюється). @storybook/vue3 — не окремий devDep пілота (транзитивна залежність
53
+ # @storybook/vue3-vite), версія вирівняна до тієї самої лінійки про запас (allowlist за
54
+ # іменем, presence не обов'язковий).
46
55
  storybook_canon_dev_deps := {
47
- "storybook": "9.1.10",
48
- "@storybook/vue3-vite": "9.1.10",
49
- "@storybook/vue3": "9.1.10",
56
+ "storybook": "^10.5.3",
57
+ "@storybook/vue3-vite": "^10.5.3",
58
+ "@storybook/vue3": "^10.5.3",
50
59
  "msw": "2.11.3",
51
60
  "msw-storybook-addon": "2.0.5",
52
61
  }
@@ -3,9 +3,8 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: plugins/lang-js/rules/storybook/adopt/main.mjs
5
5
  docgen:
6
- crc: 640409c7
6
+ crc: 986ffd02
7
7
  model: openai-codex/gpt-5.4-mini
8
- tier: cloud-min
9
8
  score: 90
10
9
  issues: anchor-miss:(mocking.mdc),anchor-miss:(vitest-config.mdc),judge:inaccurate:0.99
11
10
  judgeModel: openai-codex/gpt-5.4-mini
@@ -2,8 +2,8 @@
2
2
  * Adopt-режим канону Storybook (ADR канон-storybook-для-vue-компонентних-бібліотек,
3
3
  * Кластер 8; main.mdc). Для пакетів, де ВЖЕ є ручний `.storybook/`, що не збігається з
4
4
  * каноном, — діагностика diff по секціях проти канонічних `template/` (main.js,
5
- * preview.js, mocks/gql-sse.js, package.json#scripts.storybook, vitest test.projects,
6
- * vitest.stryker.config), БЕЗ сліпого перезапису розбіжних файлів. Автофікс
5
+ * preview.js, empty-vite.config.js, mocks/gql-sse.js, package.json#scripts.storybook,
6
+ * vitest test.projects, vitest.stryker.config), БЕЗ сліпого перезапису розбіжних файлів. Автофікс
7
7
  * (`--fix-missing`) — лише для секцій, яких немає ВЗАГАЛІ (той самий рендер, що й
8
8
  * T0-фікс concern-ів `scaffold`/`vitest-config` — переюз, не дублювання шаблонування).
9
9
  *
@@ -21,7 +21,7 @@ import { isRunAsCli } from '@7n/rules/scripts/cli-entry.mjs'
21
21
 
22
22
  import { collectInScopeVuePackages } from '../scope/main.mjs'
23
23
  import { MAIN_JS_MARKERS, missingMarkers, PREVIEW_JS_MARKERS, STORYBOOK_SCRIPT } from '../scaffold/main.mjs'
24
- import { renderMainJs, renderMocksGqlSse, renderPreviewJs } from '../scaffold/fix-scaffold.mjs'
24
+ import { renderEmptyViteConfig, renderMainJs, renderMocksGqlSse, renderPreviewJs } from '../scaffold/fix-scaffold.mjs'
25
25
  import { buildFreshVitestConfig, buildStrykerConfig } from '../vitest-config/fix-vitest-config.mjs'
26
26
  import {
27
27
  BROWSER_KEY_RE,
@@ -29,9 +29,10 @@ import {
29
29
  classifyProjects,
30
30
  findProperty,
31
31
  findTestObject,
32
+ hasStoriesMarker,
32
33
  parseModule,
34
+ PROVIDER_FACTORY_RE,
33
35
  resolveVitestConfigPath,
34
- STORIES_RE,
35
36
  strykerConfigPathFor
36
37
  } from '../vitest-config/main.mjs'
37
38
 
@@ -42,6 +43,7 @@ export const STATUS = Object.freeze({ MATCH: 'match', DIFFER: 'differ', MISSING:
42
43
  export const SECTION = Object.freeze({
43
44
  MAIN_JS: 'main.js',
44
45
  PREVIEW_JS: 'preview.js',
46
+ EMPTY_VITE_CONFIG: 'empty-vite.config.js',
45
47
  MOCKS_GQL_SSE: 'mocks/gql-sse.js',
46
48
  PACKAGE_SCRIPT: 'package.json#scripts.storybook',
47
49
  VITEST_PROJECTS: 'vitest test.projects (unit+storybook)',
@@ -103,6 +105,30 @@ function diagnoseMocksSection(entry) {
103
105
  }
104
106
  }
105
107
 
108
+ /**
109
+ * Діагностика `.storybook/empty-vite.config.js` — verbatim-порівняння з канонічним
110
+ * стенд-ін файлом (не залежить від пакета, як і `mocks/gql-sse.js`): `main.js` посилається
111
+ * на нього напряму через `core.builder.options.viteConfigPath`, тому розбіжність тут
112
+ * ламає обхід autodiscovery `@storybook/builder-vite`, навіть якщо `main.js` канонічний.
113
+ * @param {{ absDir: string, rootDir: string }} entry запис пакета
114
+ * @returns {{ name: string, file: string, status: string, detail?: string }} секція діагностики
115
+ */
116
+ function diagnoseEmptyViteConfigSection(entry) {
117
+ const relPath = '.storybook/empty-vite.config.js'
118
+ const file = relFileFor(entry, relPath)
119
+ const abs = join(entry.absDir, relPath)
120
+ if (!existsSync(abs)) return { name: SECTION.EMPTY_VITE_CONFIG, file, status: STATUS.MISSING }
121
+ const actual = readFileSync(abs, 'utf8')
122
+ const canonical = renderEmptyViteConfig()
123
+ if (actual === canonical) return { name: SECTION.EMPTY_VITE_CONFIG, file, status: STATUS.MATCH }
124
+ return {
125
+ name: SECTION.EMPTY_VITE_CONFIG,
126
+ file,
127
+ status: STATUS.DIFFER,
128
+ detail: 'вміст відрізняється від канонічного стенд-іна (storybook.mdc) — має лишатись порожнім defineConfig({})'
129
+ }
130
+ }
131
+
106
132
  /**
107
133
  * Діагностика `package.json#scripts.storybook`.
108
134
  * @param {{ pkg: Record<string, unknown>, rootDir: string }} entry запис пакета
@@ -167,7 +193,9 @@ function diagnoseVitestProjectsSection(entry) {
167
193
  const missingHints = []
168
194
  if (!CHROMIUM_RE.test(storybookSlice)) missingHints.push('chromium-інстанс')
169
195
  if (!BROWSER_KEY_RE.test(storybookSlice)) missingHints.push('browser-mode')
170
- if (!STORIES_RE.test(storybookSlice)) missingHints.push('stories-glob')
196
+ if (!hasStoriesMarker(storybookSlice)) missingHints.push('stories-джерело (include або storybookTest({ configDir }))')
197
+ if (!PROVIDER_FACTORY_RE.test(storybookSlice))
198
+ missingHints.push('provider-factory (playwright() з @vitest/browser-playwright)')
171
199
  if (missingHints.length > 0) {
172
200
  return { name, file, status: STATUS.DIFFER, detail: `storybook-project без: ${missingHints.join(', ')}` }
173
201
  }
@@ -216,6 +244,7 @@ export async function diagnosePackage(entry) {
216
244
  const sections = [
217
245
  diagnoseMarkerFile(entry, '.storybook/main.js', SECTION.MAIN_JS, MAIN_JS_MARKERS),
218
246
  diagnoseMarkerFile(entry, '.storybook/preview.js', SECTION.PREVIEW_JS, PREVIEW_JS_MARKERS),
247
+ diagnoseEmptyViteConfigSection(entry),
219
248
  diagnoseMocksSection(entry),
220
249
  diagnoseScriptSection(entry),
221
250
  diagnoseVitestProjectsSection(entry),
@@ -252,6 +281,9 @@ export async function fixMissingSections(entry, sections) {
252
281
  if (byName.get(SECTION.PREVIEW_JS)?.status === STATUS.MISSING) {
253
282
  writeFileEnsureDir(join(entry.absDir, '.storybook/preview.js'), renderPreviewJs())
254
283
  }
284
+ if (byName.get(SECTION.EMPTY_VITE_CONFIG)?.status === STATUS.MISSING) {
285
+ writeFileEnsureDir(join(entry.absDir, '.storybook/empty-vite.config.js'), renderEmptyViteConfig())
286
+ }
255
287
  if (byName.get(SECTION.MOCKS_GQL_SSE)?.status === STATUS.MISSING) {
256
288
  writeFileEnsureDir(join(entry.absDir, '.storybook/mocks/gql-sse.js'), renderMocksGqlSse())
257
289
  }
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "fixability": "config",
4
+ "requires": { "capability": "ci:github" },
5
+ "lint": {
6
+ "scope": "full",
7
+ "glob": [
8
+ "**/package.json",
9
+ "**/*.vue",
10
+ "**/.storybook/**",
11
+ ".github/actions/setup-playwright-chromium/action.yml",
12
+ ".github/workflows/lint-storybook.yml"
13
+ ]
14
+ }
15
+ }
@@ -0,0 +1,39 @@
1
+ ---
2
+ type: JS Module
3
+ title: fix-ci.mjs
4
+ resource: plugins/lang-js/rules/storybook/ci/fix-ci.mjs
5
+ docgen:
6
+ crc: 877b9feb
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 90
10
+ judgeModel: openai-codex/gpt-5.4-mini
11
+ ---
12
+
13
+ ## Огляд
14
+
15
+ Порожній шаблон `storybook/ci`, з якого відтворюються репо-рівневі CI-артефакти: `setup-playwright-chromium` як verbatim-копія composite action для Playwright Chromium і `.github/workflows/lint-storybook.yml` із матрицею `strategy.matrix.package`, згенерованою з фактичного списку пакетів через `collectInScopeVuePackages`. Публічні anchors: `TEMPLATE_DIR`, `renderPlaywrightAction`, `renderPackageDirsYaml`, `renderStorybookWorkflow`, `patterns`.
16
+
17
+ ## Поведінка
18
+
19
+ - **TEMPLATE_DIR** — вказує на каталог `template/` цього concern-а як джерело канонічних CI-шаблонів.
20
+ - **renderPlaywrightAction** — читає канонічний composite action `setup-playwright-chromium` із шаблону без змін.
21
+ - **renderPackageDirsYaml** — перетворює список коренів пакетів у YAML-рядки для матриці Storybook CI.
22
+ - **renderStorybookWorkflow** — збирає канонічний `.github/workflows/lint-storybook.yml`, підставляючи в нього матрицю пакетів зі скоупу репозиторію.
23
+ - **patterns** — описує T0-правила автофіксу для відновлення відсутніх CI-файлів цього concern-а.
24
+
25
+ ## Публічний API
26
+
27
+ - TEMPLATE_DIR — Каталог `template/` цього concern-а.
28
+ - renderPlaywrightAction — Вміст канонічного composite action `setup-playwright-chromium` — verbatim з template.
29
+ - renderPackageDirsYaml — Рендерить YAML-фрагмент `matrix.package` списку — по одному `- <rootDir>` на рядок, з тим
30
+ самим відступом, що й токен-рядок у шаблоні (10 пробілів — рівень елемента списку під
31
+ `strategy.matrix.package:`). `rootDir === '.'` (корінь монорепо) лишається `.` — валідний
32
+ `working-directory` для GitHub Actions.
33
+ - renderStorybookWorkflow — Вміст канонічного `.github/workflows/lint-storybook.yml` — template з підставленою
34
+ матрицею пакетів у скоупі.
35
+ - patterns — повертає набір шаблонів, за якими далі шукають або зіставляють потрібні значення.
36
+
37
+ ## Гарантії поведінки
38
+
39
+ - (специфічних машинно-виведених гарантій немає)
@@ -0,0 +1,10 @@
1
+ ---
2
+ type: Directory Index
3
+ title: plugins/lang-js/rules/storybook/ci
4
+ resource: plugins/lang-js/rules/storybook/ci/
5
+ ---
6
+
7
+ | Файл | Тип |
8
+ | ----------------------- | --------- |
9
+ | [fix-ci.mjs](fix-ci.md) | JS Module |
10
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,50 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: plugins/lang-js/rules/storybook/ci/main.mjs
5
+ docgen:
6
+ crc: 65df5791
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 85
10
+ issues: internal-name:collectInScopeVuePackages,anchor-miss:(storybook.mdc),judge:inaccurate:0.96
11
+ judgeModel: openai-codex/gpt-5.4-mini
12
+ ---
13
+
14
+ ## Огляд
15
+
16
+ Файл фіксує канонічні репо-шляхи й маркери для storybook CI, щоб у Vue component library пакетах у scope швидко знаходити правильні `.github/actions/setup-playwright-chromium/action.yml` і `.github/workflows/lint-storybook.yml` та зіставляти їх із повідомленнями в `storybook.mdc`. Експортовані константи-рядки: PLAYWRIGHT_ACTION_REL=".github/actions/setup-playwright-chromium/action.yml" — канонічний шлях до Playwright action; STORYBOOK_WORKFLOW_REL=".github/workflows/lint-storybook.yml" — канонічний шлях до workflow linting. Маркери повідомлень: PLAYWRIGHT_ACTION_MARKERS і STORYBOOK_WORKFLOW_MARKERS, які прив’язують перевірки до повідомлень у `storybook.mdc`. Це потрібно, щоб підтримувати єдиний storybook-канон без зайвого обходу `.github` і `.git`, із кешуванням результату в межах одного прогону.
17
+
18
+ ## Поведінка
19
+
20
+ - PLAYWRIGHT_ACTION_REL — задає репо-рівневий шлях до канонічного composite action `.github/actions/setup-playwright-chromium/action.yml`, який перевіряється як частина storybook CI.
21
+ - STORYBOOK_WORKFLOW_REL — задає репо-рівневий шлях до канонічного workflow `.github/workflows/lint-storybook.yml`, що відповідає за швидкий storybook-лінт.
22
+ - PLAYWRIGHT_ACTION_MARKERS — описує канонічні ознаки composite action для storybook CI, щоб гарантувати кеш Playwright і встановлення лише chromium; у повідомленнях використовується прив’язка до `storybook.mdc`.
23
+ - STORYBOOK_WORKFLOW_MARKERS — описує канонічні ознаки workflow для storybook CI, щоб гарантувати запуск швидкого `vitest --project=storybook`; у повідомленнях використовується прив’язка до `storybook.mdc`.
24
+ - lint — перевіряє наявність storybook CI-канону лише для репозиторіїв із Vue component library пакетами в scope; якщо таких пакетів немає, завершується без зауважень. Свідомо не зачіпає `.github` і `.git` як окремі області обходу; кешує результат у межах одного прогону.
25
+
26
+ ## Публічний API
27
+
28
+ - PLAYWRIGHT_ACTION_REL — Repo-relative шлях канонічного composite action (не per-package — один на репозиторій).
29
+ - STORYBOOK_WORKFLOW_REL — Repo-relative шлях канонічного workflow, що запускає `vitest --project=storybook`.
30
+ - PLAYWRIGHT_ACTION_MARKERS — Маркери канону composite action `setup-playwright-chromium` (ADR Кластер 5): кеш
31
+ `ms-playwright` через `actions/cache`, ключ від версії playwright, install лише chromium.
32
+ Текстовий пошук — той самий підхід, що й `MAIN_JS_MARKERS`/`PREVIEW_JS_MARKERS` у `scaffold`.
33
+ - STORYBOOK_WORKFLOW_MARKERS — Маркери канону `.github/workflows/lint-storybook.yml`: композитний Playwright-кеш ПІСЛЯ
34
+ setup-bun-deps, і швидкий `vitest --project=storybook` (ADR Кластер 5 — nightly-only
35
+ `@7n/test coverage`/mutation-testing на PR не запускається, лише цей швидкий шлях).
36
+ - lint — Detector concern-а `storybook/ci` (ADR Кластер 5, CI-частина): для репозиторіїв з бодай
37
+ одним Vue component library пакетом у скоупі Storybook (`collectInScopeVuePackages`) —
38
+ канонічний composite action `setup-playwright-chromium` (кеш Playwright-браузерів, лише
39
+ chromium) і канонічний `.github/workflows/lint-storybook.yml`, що запускає швидкий
40
+ `vitest --project=storybook` на PR. Гейтований `requires.capability: ci:github` — спить
41
+ у репозиторіях без плагіна `@7n/rules-ci-github` (немає `.github/workflows`).
42
+
43
+ Nightly-only `@7n/test coverage` (mutation testing) — поза обсягом цього concern-а: ADR
44
+ Кластер 5 явно розділяє швидкий PR-шлях (цей concern) і nightly mutation-прогін, який
45
+ лишається окремою інфраструктурою `test/stryker_config`.
46
+
47
+ ## Гарантії поведінки
48
+
49
+ - Кешує результати в межах одного прогону.
50
+ - Свідомо пропускає шляхи: `.github`, `.git`.
@@ -0,0 +1,105 @@
1
+ /**
2
+ * T0-autofix для concern-а `storybook/ci` (ADR Кластер 5, CI-частина): відтворює канонічний
3
+ * composite action `setup-playwright-chromium` (кеш Playwright-браузерів, лише chromium) і
4
+ * канонічний `.github/workflows/lint-storybook.yml` з `template/` цього concern-а.
5
+ *
6
+ * Обидва файли — репо-рівневі (не per-package): composite action — verbatim-копія; workflow —
7
+ * матриця `strategy.matrix.package` генерується з фактичного списку пакетів у скоупі
8
+ * (`collectInScopeVuePackages`), щоб CI реально покривав усі Storybook-пакети репозиторію.
9
+ */
10
+ import { mkdirSync, writeFileSync } from 'node:fs'
11
+ import { dirname, join } from 'node:path'
12
+ import { fileURLToPath } from 'node:url'
13
+ import { readFile } from 'node:fs/promises'
14
+
15
+ import { collectInScopeVuePackages } from '../scope/main.mjs'
16
+
17
+ /** Каталог `template/` цього concern-а. */
18
+ export const TEMPLATE_DIR = join(dirname(fileURLToPath(import.meta.url)), 'template')
19
+
20
+ /** Токен у `lint-storybook.yml.snippet.yml`, що заміщається списком пакетів матриці. */
21
+ const PACKAGE_DIRS_TOKEN = '__STORYBOOK_CI_PACKAGE_DIRS__'
22
+
23
+ /**
24
+ * Вміст канонічного composite action `setup-playwright-chromium` — verbatim з template.
25
+ * @param {string} [templateDir] каталог template/ (за замовчуванням — цього concern-а)
26
+ * @returns {Promise<string>} вміст `action.yml`
27
+ */
28
+ export function renderPlaywrightAction(templateDir = TEMPLATE_DIR) {
29
+ return readFile(join(templateDir, 'setup-playwright-chromium.action.yml'), 'utf8')
30
+ }
31
+
32
+ /**
33
+ * Рендерить YAML-фрагмент `matrix.package` списку — по одному `- <rootDir>` на рядок, з тим
34
+ * самим відступом, що й токен-рядок у шаблоні (10 пробілів — рівень елемента списку під
35
+ * `strategy.matrix.package:`). `rootDir === '.'` (корінь монорепо) лишається `.` — валідний
36
+ * `working-directory` для GitHub Actions.
37
+ * @param {string[]} rootDirs відносні (posix) корені пакетів у скоупі
38
+ * @returns {string} YAML-рядки (без завершального переносу)
39
+ */
40
+ export function renderPackageDirsYaml(rootDirs) {
41
+ return rootDirs.map(dir => ` - ${dir}`).join('\n')
42
+ }
43
+
44
+ /**
45
+ * Вміст канонічного `.github/workflows/lint-storybook.yml` — template з підставленою
46
+ * матрицею пакетів у скоупі.
47
+ * @param {string[]} rootDirs відносні корені пакетів у скоупі (`collectInScopeVuePackages`)
48
+ * @param {string} [templateDir] каталог template/ (за замовчуванням — цього concern-а)
49
+ * @returns {Promise<string>} вміст `lint-storybook.yml`
50
+ */
51
+ export async function renderStorybookWorkflow(rootDirs, templateDir = TEMPLATE_DIR) {
52
+ const raw = await readFile(join(templateDir, 'lint-storybook.yml.snippet.yml'), 'utf8')
53
+ const tokenLineRe = new RegExp(`^[ \\t]*${PACKAGE_DIRS_TOKEN}[ \\t]*$`, 'mu')
54
+ // Replacer-функція (не рядок) — уникає unicorn/no-unsafe-string-replacement:
55
+ // рядок-заміна мав би трактувати `$`-послідовності як спецсимволи `String#replace`.
56
+ return raw.replace(tokenLineRe, () => renderPackageDirsYaml(rootDirs))
57
+ }
58
+
59
+ /**
60
+ * Записує файл, створюючи батьківські каталоги й реєструючи запис для rollback (той самий
61
+ * патерн, що й `writeScaffoldFile` у `scaffold/fix-scaffold.mjs`).
62
+ * @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').FixContext} ctx fix-контекст рунга
63
+ * @param {string} absPath абсолютний шлях цільового файлу
64
+ * @param {string} content вміст файлу
65
+ * @returns {void}
66
+ */
67
+ function writeCiFile(ctx, absPath, content) {
68
+ mkdirSync(dirname(absPath), { recursive: true })
69
+ ctx.recordWrite?.(absPath)
70
+ writeFileSync(absPath, content, 'utf8')
71
+ }
72
+
73
+ /** @type {import('@7n/rules/scripts/lib/lint-surface/types.mjs').T0Pattern[]} */
74
+ export const patterns = [
75
+ {
76
+ id: 'storybook-ci-playwright-action',
77
+ test: violations => violations.some(v => v.reason === 'missing-playwright-action' && v.file),
78
+ apply: async (violations, ctx) => {
79
+ const target = violations.find(v => v.reason === 'missing-playwright-action' && v.file)
80
+ if (!target || !ctx.concernDir) return { touchedFiles: [] }
81
+
82
+ const content = await renderPlaywrightAction(join(ctx.concernDir, 'template'))
83
+ const abs = join(ctx.cwd, target.file)
84
+ writeCiFile(ctx, abs, content)
85
+ return { touchedFiles: [abs], message: `${target.file}: створено composite action setup-playwright-chromium` }
86
+ }
87
+ },
88
+ {
89
+ id: 'storybook-ci-workflow',
90
+ test: violations => violations.some(v => v.reason === 'missing-storybook-workflow' && v.file),
91
+ apply: async (violations, ctx) => {
92
+ const target = violations.find(v => v.reason === 'missing-storybook-workflow' && v.file)
93
+ if (!target || !ctx.concernDir) return { touchedFiles: [] }
94
+
95
+ const pkgs = await collectInScopeVuePackages(ctx.cwd)
96
+ const rootDirs = pkgs.map(p => p.rootDir)
97
+ if (rootDirs.length === 0) return { touchedFiles: [] }
98
+
99
+ const content = await renderStorybookWorkflow(rootDirs, join(ctx.concernDir, 'template'))
100
+ const abs = join(ctx.cwd, target.file)
101
+ writeCiFile(ctx, abs, content)
102
+ return { touchedFiles: [abs], message: `${target.file}: створено (${rootDirs.length} пакет(ів) у матриці)` }
103
+ }
104
+ }
105
+ ]
@@ -0,0 +1,112 @@
1
+ /** @see ./docs/main.md */
2
+ import { existsSync } from 'node:fs'
3
+ import { readFile } from 'node:fs/promises'
4
+ import { join } from 'node:path'
5
+
6
+ import { createViolationReporter } from '@7n/rules/scripts/lib/lint-surface/violation-reporter.mjs'
7
+ import { collectInScopeVuePackages } from '../scope/main.mjs'
8
+ import { missingMarkers } from '../scaffold/main.mjs'
9
+
10
+ /** Repo-relative шлях канонічного composite action (не per-package — один на репозиторій). */
11
+ export const PLAYWRIGHT_ACTION_REL = '.github/actions/setup-playwright-chromium/action.yml'
12
+
13
+ /** Repo-relative шлях канонічного workflow, що запускає `vitest --project=storybook`. */
14
+ export const STORYBOOK_WORKFLOW_REL = '.github/workflows/lint-storybook.yml'
15
+
16
+ /**
17
+ * Маркери канону composite action `setup-playwright-chromium` (ADR Кластер 5): кеш
18
+ * `ms-playwright` через `actions/cache`, ключ від версії playwright, install лише chromium.
19
+ * Текстовий пошук — той самий підхід, що й `MAIN_JS_MARKERS`/`PREVIEW_JS_MARKERS` у `scaffold`.
20
+ */
21
+ export const PLAYWRIGHT_ACTION_MARKERS = [
22
+ { token: 'ms-playwright', hint: 'кеш каталогу ms-playwright' },
23
+ { token: 'actions/cache@', hint: 'actions/cache для Playwright-браузерів' },
24
+ { token: 'playwright install chromium', hint: 'install лише chromium (не всі браузери)' }
25
+ ]
26
+
27
+ /**
28
+ * Маркери канону `.github/workflows/lint-storybook.yml`: композитний Playwright-кеш ПІСЛЯ
29
+ * setup-bun-deps, і швидкий `vitest --project=storybook` (ADR Кластер 5 — nightly-only
30
+ * `@7n/test coverage`/mutation-testing на PR не запускається, лише цей швидкий шлях).
31
+ */
32
+ export const STORYBOOK_WORKFLOW_MARKERS = [
33
+ { token: './.github/actions/setup-bun-deps', hint: 'setup-bun-deps перед Playwright-кроком' },
34
+ { token: './.github/actions/setup-playwright-chromium', hint: 'композитний Playwright-кеш' },
35
+ { token: '--project=storybook', hint: 'швидкий прогін лише storybook-проєкту (не повний coverage)' }
36
+ ]
37
+
38
+ /**
39
+ * Перевіряє один репо-рівневий канонічний файл (composite action чи workflow): відсутність →
40
+ * `missingReason`-порушення з посиланням на `npx \@7n/rules fix storybook`; присутність без
41
+ * якогось маркера → `markerReason`-порушення на конкретний маркер. Той самий патерн, що й
42
+ * `checkCanonFile` у `scaffold/main.mjs`, але без per-package `rootDir` — файл один на репо.
43
+ * @param {string} cwd абсолютний корінь репозиторію
44
+ * @param {string} relFile posix-relative шлях файлу від кореня репозиторію
45
+ * @param {{ token: string, hint: string }[]} markers канонічні маркери файлу
46
+ * @param {string} missingReason reason для порушення "файл відсутній"
47
+ * @param {string} markerReason reason для порушення "маркер відсутній"
48
+ * @param {ReturnType<typeof createViolationReporter>} reporter reporter поточного лінту
49
+ * @returns {Promise<void>}
50
+ */
51
+ async function checkRepoCanonFile(cwd, relFile, markers, missingReason, markerReason, reporter) {
52
+ const abs = join(cwd, relFile)
53
+ if (existsSync(abs)) {
54
+ const content = await readFile(abs, 'utf8')
55
+ for (const m of missingMarkers(content, markers)) {
56
+ reporter.fail(`${relFile} не відповідає канону — бракує: ${m.hint} (storybook.mdc, ADR Кластер 5)`, {
57
+ reason: markerReason,
58
+ file: relFile
59
+ })
60
+ }
61
+ return
62
+ }
63
+ reporter.fail(
64
+ `Відсутній ${relFile} — канонічний Playwright-кеш для vitest storybook-проєкту: npx @7n/rules fix storybook (storybook.mdc, ADR Кластер 5)`,
65
+ { reason: missingReason, file: relFile }
66
+ )
67
+ }
68
+
69
+ /**
70
+ * Detector concern-а `storybook/ci` (ADR Кластер 5, CI-частина): для репозиторіїв з бодай
71
+ * одним Vue component library пакетом у скоупі Storybook (`collectInScopeVuePackages`) —
72
+ * канонічний composite action `setup-playwright-chromium` (кеш Playwright-браузерів, лише
73
+ * chromium) і канонічний `.github/workflows/lint-storybook.yml`, що запускає швидкий
74
+ * `vitest --project=storybook` на PR. Гейтований `requires.capability: ci:github` — спить
75
+ * у репозиторіях без плагіна `@7n/rules-ci-github` (немає `.github/workflows`).
76
+ *
77
+ * Nightly-only `@7n/test coverage` (mutation testing) — поза обсягом цього concern-а: ADR
78
+ * Кластер 5 явно розділяє швидкий PR-шлях (цей concern) і nightly mutation-прогін, який
79
+ * лишається окремою інфраструктурою `test/stryker_config`.
80
+ * @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту
81
+ * @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} результат лінту
82
+ */
83
+ export async function lint(ctx) {
84
+ const reporter = createViolationReporter(ctx)
85
+ const cwd = ctx.cwd
86
+
87
+ const pkgs = await collectInScopeVuePackages(cwd)
88
+ if (pkgs.length === 0) {
89
+ reporter.pass('storybook/ci: немає Vue component library пакетів у скоупі (storybook.mdc)')
90
+ return reporter.result()
91
+ }
92
+
93
+ await checkRepoCanonFile(
94
+ cwd,
95
+ PLAYWRIGHT_ACTION_REL,
96
+ PLAYWRIGHT_ACTION_MARKERS,
97
+ 'missing-playwright-action',
98
+ 'playwright-action-marker-missing',
99
+ reporter
100
+ )
101
+
102
+ await checkRepoCanonFile(
103
+ cwd,
104
+ STORYBOOK_WORKFLOW_REL,
105
+ STORYBOOK_WORKFLOW_MARKERS,
106
+ 'missing-storybook-workflow',
107
+ 'storybook-workflow-marker-missing',
108
+ reporter
109
+ )
110
+
111
+ return reporter.result()
112
+ }