@7n/rules 1.27.0 → 1.28.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.
- package/CHANGELOG.md +6 -0
- package/package.json +1 -5
- package/scripts/auto-rules.mjs +2 -3
- package/scripts/docs/auto-rules.md +1 -1
- package/scripts/lib/docs/glob-to-regex.md +31 -0
- package/scripts/lib/docs/index.md +2 -0
- package/scripts/lib/docs/js-source-signals.md +32 -0
- package/scripts/lib/glob-to-regex.mjs +79 -0
- package/scripts/lib/js-source-signals.mjs +50 -0
- package/scripts/utils/ast-scan-utils.mjs +2 -2
- package/scripts/utils/docs/ast-scan-utils.md +1 -1
- package/rules/bun/bunfig/bunfig.mdc +0 -17
- package/rules/bun/bunfig/bunfig.rego +0 -29
- package/rules/bun/bunfig/concern.json +0 -9
- package/rules/bun/bunfig/template/bunfig.toml.snippet.toml +0 -2
- package/rules/bun/docs/index.md +0 -11
- package/rules/bun/layout/concern.json +0 -16
- package/rules/bun/layout/docs/fix-layout.md +0 -29
- package/rules/bun/layout/docs/main.md +0 -34
- package/rules/bun/layout/fix-layout.mjs +0 -63
- package/rules/bun/layout/layout.mdc +0 -60
- package/rules/bun/layout/main.mjs +0 -53
- package/rules/bun/licensee/concern.json +0 -7
- package/rules/bun/licensee/docs/fix-licensee.md +0 -27
- package/rules/bun/licensee/docs/index.md +0 -12
- package/rules/bun/licensee/docs/main.md +0 -30
- package/rules/bun/licensee/fix-licensee.mjs +0 -31
- package/rules/bun/licensee/main.mjs +0 -68
- package/rules/bun/lint-surface/concern.json +0 -3
- package/rules/bun/lint-surface/lint-surface.mdc +0 -13
- package/rules/bun/main.json +0 -1
- package/rules/bun/main.mdc +0 -11
- package/rules/bun/package_json/concern.json +0 -9
- package/rules/bun/package_json/docs/fix-package_json.md +0 -28
- package/rules/bun/package_json/docs/index.md +0 -9
- package/rules/bun/package_json/fix-package_json.mjs +0 -311
- package/rules/bun/package_json/package_json.mdc +0 -14
- package/rules/bun/package_json/package_json.rego +0 -64
- package/rules/bun/package_json/template/package.json.deny.json +0 -4
- package/rules/js/check/check.mdc +0 -26
- package/rules/js/check/concern.json +0 -19
- package/rules/js/check/docs/eslint-config.md +0 -56
- package/rules/js/check/docs/fix-check.md +0 -48
- package/rules/js/check/docs/index.md +0 -13
- package/rules/js/check/docs/main.md +0 -48
- package/rules/js/check/eslint-config.mjs +0 -262
- package/rules/js/check/fix-check.mjs +0 -82
- package/rules/js/check/main.mjs +0 -310
- package/rules/js/dep-policy/concern.json +0 -4
- package/rules/js/dep-policy/dep-policy.mdc +0 -36
- package/rules/js/dep-policy/docs/main.md +0 -35
- package/rules/js/dep-policy/main.mjs +0 -99
- package/rules/js/docs/index.md +0 -11
- package/rules/js/eslint/concern.json +0 -8
- package/rules/js/eslint/docs/fix-eslint.md +0 -50
- package/rules/js/eslint/docs/fix-worker.md +0 -31
- package/rules/js/eslint/docs/index.md +0 -11
- package/rules/js/eslint/docs/main.md +0 -35
- package/rules/js/eslint/fix-eslint.mjs +0 -160
- package/rules/js/eslint/fix-worker.mjs +0 -139
- package/rules/js/eslint/main.mjs +0 -115
- package/rules/js/file-extensions/concern.json +0 -3
- package/rules/js/file-extensions/file-extensions.mdc +0 -12
- package/rules/js/jscpd_config/concern.json +0 -11
- package/rules/js/jscpd_config/docs/fix-jscpd_config.md +0 -26
- package/rules/js/jscpd_config/docs/index.md +0 -9
- package/rules/js/jscpd_config/fix-jscpd_config.mjs +0 -3
- package/rules/js/jscpd_config/jscpd_config.mdc +0 -42
- package/rules/js/jscpd_config/jscpd_config.rego +0 -44
- package/rules/js/jscpd_config/template/.jscpd.json.snippet.json +0 -7
- package/rules/js/jscpd_duplicates/concern.json +0 -7
- package/rules/js/jscpd_duplicates/docs/main.md +0 -29
- package/rules/js/jscpd_duplicates/main.mjs +0 -69
- package/rules/js/knip/concern.json +0 -7
- package/rules/js/knip/docs/main.md +0 -32
- package/rules/js/knip/knip.mdc +0 -15
- package/rules/js/knip/main.mjs +0 -68
- package/rules/js/lint-findings/concern.json +0 -3
- package/rules/js/lint-findings/docs/main.md +0 -41
- package/rules/js/lint-findings/main.mjs +0 -125
- package/rules/js/main.json +0 -1
- package/rules/js/main.mdc +0 -18
- package/rules/js/package_json/concern.json +0 -9
- package/rules/js/package_json/docs/fix-package_json.md +0 -26
- package/rules/js/package_json/docs/index.md +0 -9
- package/rules/js/package_json/fix-package_json.mjs +0 -3
- package/rules/js/package_json/package_json.mdc +0 -15
- package/rules/js/package_json/package_json.rego +0 -142
- package/rules/js/package_json/template/package.json.snippet.json +0 -6
- package/rules/js/tooling/concern.json +0 -3
- package/rules/js/tooling/data/tooling/knip-canonical.json +0 -30
- package/rules/js/tooling/data/tooling/oxlint-canonical.json +0 -400
- package/rules/js/tooling/docs/main.md +0 -53
- package/rules/js/tooling/main.mjs +0 -183
- package/rules/js/utils_imports/concern.json +0 -4
- package/rules/js/utils_imports/docs/main.md +0 -50
- package/rules/js/utils_imports/main.mjs +0 -185
- package/rules/js/utils_imports/utils_imports.mdc +0 -15
- package/rules/js/vscode_extensions/concern.json +0 -11
- package/rules/js/vscode_extensions/docs/fix-vscode_extensions.md +0 -25
- package/rules/js/vscode_extensions/docs/index.md +0 -11
- package/rules/js/vscode_extensions/fix-vscode_extensions.mjs +0 -1
- package/rules/js/vscode_extensions/template/extensions.json.snippet.json +0 -6
- package/rules/js/vscode_extensions/vscode_extensions.mdc +0 -11
- package/rules/js/vscode_extensions/vscode_extensions.rego +0 -12
- package/rules/js-bun-db/connection/concern.json +0 -3
- package/rules/js-bun-db/connection/connection.mdc +0 -42
- package/rules/js-bun-db/docs/index.md +0 -11
- package/rules/js-bun-db/lib/bun-sql-scan.mjs +0 -1055
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +0 -63
- package/rules/js-bun-db/lib/docs/index.md +0 -11
- package/rules/js-bun-db/main.json +0 -1
- package/rules/js-bun-db/main.mdc +0 -8
- package/rules/js-bun-db/package_json/concern.json +0 -9
- package/rules/js-bun-db/package_json/package_json.mdc +0 -31
- package/rules/js-bun-db/package_json/package_json.rego +0 -15
- package/rules/js-bun-db/package_json/template/package.json.deny.json +0 -6
- package/rules/js-bun-db/pg_format_identifiers/concern.json +0 -3
- package/rules/js-bun-db/pg_format_identifiers/pg_format_identifiers.mdc +0 -104
- package/rules/js-bun-db/safety/concern.json +0 -4
- package/rules/js-bun-db/safety/docs/main.md +0 -34
- package/rules/js-bun-db/safety/main.mjs +0 -430
- package/rules/js-bun-db/safety/safety.mdc +0 -458
- package/rules/js-bun-redis/docs/index.md +0 -11
- package/rules/js-bun-redis/imports/concern.json +0 -4
- package/rules/js-bun-redis/imports/docs/main.md +0 -36
- package/rules/js-bun-redis/imports/imports.mdc +0 -47
- package/rules/js-bun-redis/imports/main.mjs +0 -88
- package/rules/js-bun-redis/lib/docs/index.md +0 -11
- package/rules/js-bun-redis/lib/docs/redis-imports.md +0 -227
- package/rules/js-bun-redis/lib/redis-imports.mjs +0 -130
- package/rules/js-bun-redis/main.json +0 -1
- package/rules/js-bun-redis/main.mdc +0 -8
- package/rules/js-bun-redis/package_json/concern.json +0 -9
- package/rules/js-bun-redis/package_json/package_json.mdc +0 -11
- package/rules/js-bun-redis/package_json/package_json.rego +0 -15
- package/rules/js-bun-redis/package_json/template/package.json.deny.json +0 -12
- package/rules/js-mssql/deps/concern.json +0 -4
- package/rules/js-mssql/deps/docs/main.md +0 -33
- package/rules/js-mssql/deps/main.mjs +0 -297
- package/rules/js-mssql/docs/index.md +0 -11
- package/rules/js-mssql/lib/docs/index.md +0 -11
- package/rules/js-mssql/lib/docs/mssql-pool-scan.md +0 -380
- package/rules/js-mssql/lib/mssql-pool-scan.mjs +0 -610
- package/rules/js-mssql/main.json +0 -1
- package/rules/js-mssql/main.mdc +0 -144
- package/rules/js-mssql/mssql-tvp/concern.json +0 -3
- package/rules/js-mssql/mssql-tvp/mssql-tvp.mdc +0 -77
- package/rules/js-mssql/package_json/concern.json +0 -9
- package/rules/js-mssql/package_json/package_json.mdc +0 -9
- package/rules/js-mssql/package_json/package_json.rego +0 -57
- package/rules/js-run/configmap/concern.json +0 -9
- package/rules/js-run/configmap/configmap.mdc +0 -37
- package/rules/js-run/configmap/configmap.rego +0 -21
- package/rules/js-run/configmap/template/configmap.yaml.contains.yml +0 -4
- package/rules/js-run/docs/index.md +0 -11
- package/rules/js-run/jsconfig/concern.json +0 -9
- package/rules/js-run/jsconfig/docs/fix-jsconfig.md +0 -29
- package/rules/js-run/jsconfig/docs/index.md +0 -9
- package/rules/js-run/jsconfig/fix-jsconfig.mjs +0 -119
- package/rules/js-run/jsconfig/jsconfig.mdc +0 -48
- package/rules/js-run/jsconfig/jsconfig.rego +0 -59
- package/rules/js-run/jsconfig/template/jsconfig.json.snippet.json +0 -10
- package/rules/js-run/lib/bunyan-imports.mjs +0 -98
- package/rules/js-run/lib/check-env-scan.mjs +0 -338
- package/rules/js-run/lib/conn-file-rules.mjs +0 -214
- package/rules/js-run/lib/conn-imports-scan.mjs +0 -154
- package/rules/js-run/lib/docs/bunyan-imports.md +0 -121
- package/rules/js-run/lib/docs/check-env-scan.md +0 -438
- package/rules/js-run/lib/docs/conn-file-rules.md +0 -304
- package/rules/js-run/lib/docs/conn-imports-scan.md +0 -208
- package/rules/js-run/lib/docs/index.md +0 -16
- package/rules/js-run/lib/docs/promise-settimeout-scan.md +0 -334
- package/rules/js-run/lib/docs/temporal-scan.md +0 -29
- package/rules/js-run/lib/promise-settimeout-scan.mjs +0 -128
- package/rules/js-run/lib/temporal-scan.mjs +0 -52
- package/rules/js-run/main.json +0 -1
- package/rules/js-run/main.mdc +0 -16
- package/rules/js-run/package_json/concern.json +0 -9
- package/rules/js-run/package_json/package_json.mdc +0 -44
- package/rules/js-run/package_json/package_json.rego +0 -37
- package/rules/js-run/package_json/template/package.json.deny.json +0 -22
- package/rules/js-run/project-structure/concern.json +0 -3
- package/rules/js-run/project-structure/project-structure.mdc +0 -11
- package/rules/js-run/runtime/concern.json +0 -12
- package/rules/js-run/runtime/docs/fix-runtime.md +0 -27
- package/rules/js-run/runtime/docs/main.md +0 -35
- package/rules/js-run/runtime/fix-runtime.mjs +0 -46
- package/rules/js-run/runtime/main.mjs +0 -494
- package/rules/js-run/runtime/runtime.mdc +0 -184
- package/rules/js-run/scope/concern.json +0 -3
- package/rules/js-run/scope/scope.mdc +0 -11
- package/rules/npm-module/docs/index.md +0 -11
- package/rules/npm-module/emit_types_config/concern.json +0 -9
- package/rules/npm-module/emit_types_config/docs/fix-emit_types_config.md +0 -25
- package/rules/npm-module/emit_types_config/docs/index.md +0 -9
- package/rules/npm-module/emit_types_config/emit_types_config.mdc +0 -43
- package/rules/npm-module/emit_types_config/emit_types_config.rego +0 -28
- package/rules/npm-module/emit_types_config/fix-emit_types_config.mjs +0 -5
- package/rules/npm-module/emit_types_config/template/tsconfig.emit-types.json.snippet.json +0 -9
- package/rules/npm-module/header_doc_pointer/concern.json +0 -5
- package/rules/npm-module/header_doc_pointer/docs/main.md +0 -40
- package/rules/npm-module/header_doc_pointer/header_doc_pointer.mdc +0 -18
- package/rules/npm-module/header_doc_pointer/main.mjs +0 -131
- package/rules/npm-module/main.json +0 -1
- package/rules/npm-module/main.mdc +0 -36
- package/rules/npm-module/npm_package_json/concern.json +0 -9
- package/rules/npm-module/npm_package_json/docs/fix-npm_package_json.md +0 -27
- package/rules/npm-module/npm_package_json/docs/index.md +0 -9
- package/rules/npm-module/npm_package_json/fix-npm_package_json.mjs +0 -5
- package/rules/npm-module/npm_package_json/npm_package_json.mdc +0 -57
- package/rules/npm-module/npm_package_json/npm_package_json.rego +0 -73
- package/rules/npm-module/npm_package_json/template/package.json.snippet.json +0 -1
- package/rules/npm-module/package_structure/concern.json +0 -5
- package/rules/npm-module/package_structure/docs/main.md +0 -37
- package/rules/npm-module/package_structure/main.mjs +0 -515
- package/rules/npm-module/package_structure/package_structure.mdc +0 -63
- package/rules/npm-module/root_package_json/concern.json +0 -9
- package/rules/npm-module/root_package_json/docs/fix-root_package_json.md +0 -26
- package/rules/npm-module/root_package_json/docs/index.md +0 -9
- package/rules/npm-module/root_package_json/fix-root_package_json.mjs +0 -5
- package/rules/npm-module/root_package_json/root_package_json.mdc +0 -41
- package/rules/npm-module/root_package_json/root_package_json.rego +0 -28
- package/rules/npm-module/root_package_json/template/package.json.snippet.json +0 -1
- package/rules/npm-module/rule_meta/concern.json +0 -8
- package/rules/npm-module/rule_meta/docs/main.md +0 -35
- package/rules/npm-module/rule_meta/main.mjs +0 -119
- package/rules/npm-module/rule_meta/rule_meta.mdc +0 -11
- package/rules/npm-module/skill_meta/concern.json +0 -5
- package/rules/npm-module/skill_meta/docs/main.md +0 -149
- package/rules/npm-module/skill_meta/main.mjs +0 -91
- package/rules/npm-module/skill_meta/skill_meta.mdc +0 -11
- package/rules/tool-surface/docs/index.md +0 -11
- package/rules/tool-surface/main.json +0 -6
- package/rules/tool-surface/main.mdc +0 -72
- package/rules/vue/composition-api/composition-api.mdc +0 -82
- package/rules/vue/composition-api/concern.json +0 -3
- package/rules/vue/docs/index.md +0 -11
- package/rules/vue/lib/docs/index.md +0 -11
- package/rules/vue/lib/docs/vue-forbidden-imports.md +0 -265
- package/rules/vue/lib/vue-forbidden-imports.mjs +0 -264
- package/rules/vue/main.json +0 -1
- package/rules/vue/main.mdc +0 -18
- package/rules/vue/nheader-layout/concern.json +0 -3
- package/rules/vue/nheader-layout/nheader-layout.mdc +0 -171
- package/rules/vue/package_json/concern.json +0 -9
- package/rules/vue/package_json/package_json.mdc +0 -30
- package/rules/vue/package_json/package_json.rego +0 -140
- package/rules/vue/packages/concern.json +0 -6
- package/rules/vue/packages/docs/index.md +0 -11
- package/rules/vue/packages/docs/main.md +0 -35
- package/rules/vue/packages/main.mjs +0 -575
- package/rules/vue/packages/packages.mdc +0 -56
- package/rules/vue/quasar-ui/concern.json +0 -3
- package/rules/vue/quasar-ui/quasar-ui.mdc +0 -32
- package/rules/vue/structure/concern.json +0 -3
- package/rules/vue/structure/structure.mdc +0 -101
- package/rules/vue/testing/concern.json +0 -3
- package/rules/vue/testing/testing.mdc +0 -40
- package/rules/vue/tfm-translations/concern.json +0 -7
- package/rules/vue/tfm-translations/docs/main.md +0 -29
- package/rules/vue/tfm-translations/main.mjs +0 -55
- package/rules/vue/tfm-translations/tfm-translations.mdc +0 -32
- package/rules/vue/vite-config/concern.json +0 -3
- package/rules/vue/vite-config/vite-config.mdc +0 -153
- package/rules/vue/vite-env/concern.json +0 -3
- package/rules/vue/vite-env/vite-env.mdc +0 -61
|
@@ -1,515 +0,0 @@
|
|
|
1
|
-
/** @see ./docs/package_structure.md */
|
|
2
|
-
import { existsSync } from 'node:fs'
|
|
3
|
-
import { readFile, stat } from 'node:fs/promises'
|
|
4
|
-
import { join, sep } from 'node:path'
|
|
5
|
-
|
|
6
|
-
import { parseSync } from 'oxc-parser'
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
dynamicImportModule,
|
|
10
|
-
langFromPath,
|
|
11
|
-
requireCallModule,
|
|
12
|
-
walkAstWithAncestors
|
|
13
|
-
} from '../../../scripts/utils/ast-scan-utils.mjs'
|
|
14
|
-
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
15
|
-
import { loadCursorIgnorePaths } from '../../../scripts/lib/load-cursor-config.mjs'
|
|
16
|
-
import { walkDir } from '../../../scripts/utils/walkDir.mjs'
|
|
17
|
-
|
|
18
|
-
/** Файл проєкту TypeScript для emit без каталогу `src` (див. npm-module.mdc) */
|
|
19
|
-
const EMIT_TYPES_CONFIG = 'npm/tsconfig.emit-types.json'
|
|
20
|
-
|
|
21
|
-
/** Каталоги, які за конвенцією тримають тести / фікстури і не повинні публікуватися. */
|
|
22
|
-
const TEST_DIR_NAMES = new Set(['tests', '__tests__', 'fixtures', '__fixtures__', 'spec', 'test'])
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Імена файлів за патернами test/spec (тільки basename, без path). Rego
|
|
26
|
-
* (`*_test.rego`) свідомо не входить: за конвенцією conftest юніт-тест лежить
|
|
27
|
-
* поруч з полісі у тому самому `package` — і це дозволений виняток усередині
|
|
28
|
-
* опублікованого `policy/`-каталогу (npm-module.mdc).
|
|
29
|
-
* @param {string} cwd корінь репозиторію
|
|
30
|
-
*/
|
|
31
|
-
const TEST_FILE_PATTERNS = [/^.+\.(test|spec)\.[cm]?[jt]sx?$/iu]
|
|
32
|
-
|
|
33
|
-
/** Розширення, у яких ловимо імпорти test-фреймворків. */
|
|
34
|
-
const JS_LIKE_EXT_RE = /\.[cm]?[jt]sx?$/iu
|
|
35
|
-
|
|
36
|
-
/** Імпорти/require/dynamic-import, які видають test-файл. */
|
|
37
|
-
const TEST_FRAMEWORK_MODULES = new Set([
|
|
38
|
-
'bun:test',
|
|
39
|
-
'node:test',
|
|
40
|
-
'vitest',
|
|
41
|
-
'@jest/globals',
|
|
42
|
-
'jest',
|
|
43
|
-
'mocha',
|
|
44
|
-
'ava',
|
|
45
|
-
'tap',
|
|
46
|
-
'tape'
|
|
47
|
-
])
|
|
48
|
-
|
|
49
|
-
/** Символи у glob-сегменті, які треба екранувати для RegExp (без `*` / `?` — їх обробляємо окремо). */
|
|
50
|
-
const REGEX_SPECIAL_IN_GLOB = new Set(['.', '+', '^', '$', '{', '}', '(', ')', '|', '[', ']', '\\'])
|
|
51
|
-
|
|
52
|
-
/** Збіги для post-обробки glob → regex після злиття сегментів через `/` (див. `globToRegex`). */
|
|
53
|
-
const GLOBSTAR_LEADING_RE = /^__GLOBSTAR__\//u
|
|
54
|
-
const GLOBSTAR_TRAILING_RE = /\/__GLOBSTAR__$/u
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Чи є під `npm/src` хоча б один `.js` (рекурсивно).
|
|
58
|
-
* @param {string} cwd корінь репозиторію
|
|
59
|
-
* @param {string[]} [ignorePaths] абсолютні шляхи каталогів, повністю виключених з обходу
|
|
60
|
-
* @returns {Promise<boolean>} `true`, якщо знайдено хоча б один `.js`
|
|
61
|
-
*/
|
|
62
|
-
async function npmSrcTreeHasJsFile(cwd, ignorePaths = []) {
|
|
63
|
-
const root = join(cwd, 'npm/src')
|
|
64
|
-
if (!existsSync(root)) {
|
|
65
|
-
return false
|
|
66
|
-
}
|
|
67
|
-
let found = false
|
|
68
|
-
await walkDir(
|
|
69
|
-
root,
|
|
70
|
-
p => {
|
|
71
|
-
if (p.endsWith('.js')) {
|
|
72
|
-
found = true
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
ignorePaths
|
|
76
|
-
)
|
|
77
|
-
return found
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Знаходить текстовий вміст конфігурації hk для перевірки npm-module.
|
|
82
|
-
* @returns {Promise<{ path: string, text: string } | null>} знайдений файл або `null`
|
|
83
|
-
* @param {string} cwd корінь репозиторію
|
|
84
|
-
*/
|
|
85
|
-
async function readHkConfig(cwd) {
|
|
86
|
-
const candidates = ['hk.pkl', '.config/hk.pkl']
|
|
87
|
-
for (const p of candidates) {
|
|
88
|
-
const abs = join(cwd, p)
|
|
89
|
-
if (existsSync(abs)) {
|
|
90
|
-
const text = await readFile(abs, 'utf8')
|
|
91
|
-
return { path: p, text }
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return null
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Підрядки для hk при layout з каталогом `npm/src` і glob `src` + `.js` у команді (див. npm-module.mdc).
|
|
99
|
-
* @param {string} hkText текст конфігурації hk
|
|
100
|
-
* @returns {string[]} відсутні фрагменти
|
|
101
|
-
*/
|
|
102
|
-
function missingHkSrcLayoutFragments(hkText) {
|
|
103
|
-
const need = [
|
|
104
|
-
'["pre-commit"]',
|
|
105
|
-
'bunx -p typescript tsc',
|
|
106
|
-
'src/**/*.js',
|
|
107
|
-
'--declaration',
|
|
108
|
-
'--allowJs',
|
|
109
|
-
'--emitDeclarationOnly',
|
|
110
|
-
'--outDir types',
|
|
111
|
-
'--skipLibCheck'
|
|
112
|
-
]
|
|
113
|
-
return need.filter(s => !hkText.includes(s))
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Підрядки для hk при layout з `tsconfig.emit-types.json` (див. npm-module.mdc).
|
|
118
|
-
* @param {string} hkText текст конфігурації hk
|
|
119
|
-
* @returns {string[]} відсутні фрагменти
|
|
120
|
-
*/
|
|
121
|
-
function missingHkEmitTypesConfigFragments(hkText) {
|
|
122
|
-
const need = ['["pre-commit"]', 'bunx -p typescript tsc', 'tsconfig.emit-types.json']
|
|
123
|
-
return need.filter(s => !hkText.includes(s))
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Застаріла інвокація команди `check`, прибраної в v14 (уніфікована поверхня `lint`).
|
|
128
|
-
* Матч на підрядок "check changelog" незалежно від того, яким бінарем/шляхом викликано.
|
|
129
|
-
*/
|
|
130
|
-
const DEPRECATED_CHECK_CHANGELOG_RE = /\bcheck\s+changelog\b/u
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Підрядки канонічного кроку `npm-changelog` у hk.pkl (fix-only, autofix через env
|
|
134
|
-
* `N_RULES_CHANGELOG_AUTOFIX=1`; сам крок ставить change-файл у git-індекс, `stage`
|
|
135
|
-
* не потрібен). Див. npm-module.mdc.
|
|
136
|
-
* @param {string} hkText текст конфігурації hk
|
|
137
|
-
* @returns {string[]} відсутні фрагменти
|
|
138
|
-
*/
|
|
139
|
-
function missingHkNpmChangelogFragments(hkText) {
|
|
140
|
-
const need = ['["npm-changelog"]', 'N_RULES_CHANGELOG_AUTOFIX=1', 'lint changelog']
|
|
141
|
-
return need.filter(s => !hkText.includes(s))
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Шлях на дискі до файлу з поля `types` у `npm/package.json` (значення на кшталт `./types/bin/x.d.ts`).
|
|
146
|
-
* @param {string} typesField значення поля `types` з `package.json`
|
|
147
|
-
* @returns {string | null} абсолютний шлях або `null`
|
|
148
|
-
*/
|
|
149
|
-
function npmTypesFileFromPackageField(typesField) {
|
|
150
|
-
if (typeof typesField !== 'string' || !typesField.startsWith('./types/')) {
|
|
151
|
-
return null
|
|
152
|
-
}
|
|
153
|
-
const rel = typesField.slice(2)
|
|
154
|
-
return join('npm', rel)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Перевіряє наявність на диску файлу зі значення `types` у `npm/package.json`
|
|
159
|
-
* (cross-file: JSON-поле + FS). Структуру самого поля валідує
|
|
160
|
-
* `npm/policy/npm_module/npm_package_json/`; тут — лише чи файл реально існує.
|
|
161
|
-
* @param {boolean} useSrcJsLayout чи використовується layout з npm/src
|
|
162
|
-
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
163
|
-
* @param {(msg: string) => void} failFn callback при помилці
|
|
164
|
-
* @param {string} cwd корінь репозиторію
|
|
165
|
-
*/
|
|
166
|
-
async function checkNpmPackageJson(useSrcJsLayout, passFn, failFn, cwd) {
|
|
167
|
-
const npmPkgPath = join(cwd, 'npm/package.json')
|
|
168
|
-
if (!existsSync(npmPkgPath)) return
|
|
169
|
-
const npmPkg = JSON.parse(await readFile(npmPkgPath, 'utf8'))
|
|
170
|
-
const typesField = npmPkg.types
|
|
171
|
-
|
|
172
|
-
const typesRel = useSrcJsLayout ? join('npm', 'types', 'index.d.ts') : npmTypesFileFromPackageField(typesField)
|
|
173
|
-
const missingTypesMsg = useSrcJsLayout
|
|
174
|
-
? `Відсутній ${join('npm', 'types', 'index.d.ts')} (згенеруй tsc з npm-module.mdc)`
|
|
175
|
-
: `Файл для поля types не знайдено або шлях не під ./types/ — ${String(typesField)}`
|
|
176
|
-
if (typesRel && existsSync(join(cwd, typesRel))) {
|
|
177
|
-
passFn(`${typesRel} існує`)
|
|
178
|
-
} else {
|
|
179
|
-
failFn(missingTypesMsg)
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* FS-existence для `npm/tsconfig.emit-types.json` (структуру `compilerOptions`
|
|
185
|
-
* валідує `npm/policy/npm_module/emit_types_config/`).
|
|
186
|
-
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
187
|
-
* @param {(msg: string) => void} failFn callback при помилці
|
|
188
|
-
* @param {string} cwd корінь репозиторію
|
|
189
|
-
*/
|
|
190
|
-
function checkEmitTypesConfig(passFn, failFn, cwd) {
|
|
191
|
-
if (!existsSync(join(cwd, EMIT_TYPES_CONFIG))) {
|
|
192
|
-
failFn(
|
|
193
|
-
`Без .js під npm/src потрібен ${EMIT_TYPES_CONFIG} (див. npm-module.mdc: emit через tsconfig, без штучного src/index.js)`
|
|
194
|
-
)
|
|
195
|
-
return
|
|
196
|
-
}
|
|
197
|
-
passFn(`${EMIT_TYPES_CONFIG} є (структуру перевіряє npx @7n/rules fix → npm_module.emit_types_config)`)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* FS-existence для `npm-publish.yml` workflow. Поля workflow (`on.push.paths`,
|
|
202
|
-
* `branches`, `id-token: write`, JS-DevTools/npm-publish step) валідує
|
|
203
|
-
* `npm/policy/npm_module/npm_publish_yml/`.
|
|
204
|
-
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
205
|
-
* @param {(msg: string) => void} failFn callback при виявленому порушенні
|
|
206
|
-
* @param {string} cwd корінь репозиторію
|
|
207
|
-
*/
|
|
208
|
-
function checkPublishWorkflow(passFn, failFn, cwd) {
|
|
209
|
-
const publishWf = '.github/workflows/npm-publish.yml'
|
|
210
|
-
if (existsSync(join(cwd, publishWf))) {
|
|
211
|
-
passFn(`${publishWf} є (структуру перевіряє npx @7n/rules fix → npm_module.npm_publish_yml)`)
|
|
212
|
-
} else {
|
|
213
|
-
failFn(`Відсутній ${publishWf} (npm-module.mdc: npm publish)`)
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Перетворює glob-патерн (як у npm `files` чи `meta.json:auto.glob`) у `RegExp`
|
|
219
|
-
* з якорями `^` / `$`. Підтримує globstar (нуль або більше сегментів), `*`
|
|
220
|
-
* (символи без `/`), `?` (один символ без `/`) і brace-альтернативи `{a,b,c}`
|
|
221
|
-
* (наприклад `*.{png,jpg,svg}` → `(?:png|jpg|svg)`). Клас `[…]` не
|
|
222
|
-
* підтримується — у негативних патернах `files` цього достатньо.
|
|
223
|
-
* @param {string} glob posix-шлях у glob-нотації
|
|
224
|
-
* @returns {RegExp} `RegExp` з якорями `^` / `$`
|
|
225
|
-
*/
|
|
226
|
-
export function globToRegex(glob) {
|
|
227
|
-
const parts = glob.split('/')
|
|
228
|
-
const tokens = parts.map(p => {
|
|
229
|
-
if (p === '**') return '__GLOBSTAR__'
|
|
230
|
-
let out = ''
|
|
231
|
-
let braceDepth = 0
|
|
232
|
-
for (const c of p) {
|
|
233
|
-
switch (c) {
|
|
234
|
-
case '*': {
|
|
235
|
-
out += '[^/]*'
|
|
236
|
-
continue
|
|
237
|
-
}
|
|
238
|
-
case '?': {
|
|
239
|
-
out += '[^/]'
|
|
240
|
-
continue
|
|
241
|
-
}
|
|
242
|
-
case '{': {
|
|
243
|
-
out += '(?:'
|
|
244
|
-
braceDepth++
|
|
245
|
-
continue
|
|
246
|
-
}
|
|
247
|
-
case '}': {
|
|
248
|
-
if (braceDepth > 0) {
|
|
249
|
-
out += ')'
|
|
250
|
-
braceDepth--
|
|
251
|
-
continue
|
|
252
|
-
}
|
|
253
|
-
break
|
|
254
|
-
}
|
|
255
|
-
case ',': {
|
|
256
|
-
if (braceDepth > 0) {
|
|
257
|
-
out += '|'
|
|
258
|
-
continue
|
|
259
|
-
}
|
|
260
|
-
break
|
|
261
|
-
}
|
|
262
|
-
default: {
|
|
263
|
-
break
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
out += REGEX_SPECIAL_IN_GLOB.has(c) ? `\\${c}` : c
|
|
267
|
-
}
|
|
268
|
-
return out
|
|
269
|
-
})
|
|
270
|
-
let re = tokens.join('/')
|
|
271
|
-
re = re.replaceAll('/__GLOBSTAR__/', '(?:/.*/|/)')
|
|
272
|
-
re = re.replace(GLOBSTAR_LEADING_RE, '(?:.*/)?')
|
|
273
|
-
re = re.replace(GLOBSTAR_TRAILING_RE, '(?:/.*)?')
|
|
274
|
-
re = re.replaceAll('__GLOBSTAR__', '.*')
|
|
275
|
-
// Дозволено: уся функція існує саме для конструкції RegExp з glob-pattern
|
|
276
|
-
// у `files` (значення з npm/package.json, не від кінцевого користувача), і
|
|
277
|
-
// спецсимволи вже екрановано через `REGEX_SPECIAL_IN_GLOB` вище.
|
|
278
|
-
return new RegExp(`^${re}$`, 'u')
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Збирає список файлів, що потраплять у tarball, виходячи з `files` у
|
|
283
|
-
* `npm/package.json`. Підтримує позитивні patterns (директорії або файли) і
|
|
284
|
-
* негативні (`!…`). Шляхи повертаються у posix-формі, відносно `npm/`.
|
|
285
|
-
* Не пробує дублювати всю логіку `npm pack` (license/readme/тощо) — тут лише
|
|
286
|
-
* простір імен `files`, бо саме його сканує check.
|
|
287
|
-
* @param {string[]} filesField значення поля `files`
|
|
288
|
-
* @returns {Promise<string[]>} відсортовані posix-шляхи без `npm/` префікса
|
|
289
|
-
* @param {string} cwd корінь репозиторію
|
|
290
|
-
*/
|
|
291
|
-
async function collectPublishedFiles(filesField, cwd) {
|
|
292
|
-
const positives = filesField.filter(p => typeof p === 'string' && !p.startsWith('!'))
|
|
293
|
-
const negatives = filesField.filter(p => typeof p === 'string' && p.startsWith('!')).map(p => globToRegex(p.slice(1)))
|
|
294
|
-
/** @type {Set<string>} */
|
|
295
|
-
const collected = new Set()
|
|
296
|
-
const npmRoot = join(cwd, 'npm')
|
|
297
|
-
for (const entry of positives) {
|
|
298
|
-
const fullPath = join(npmRoot, entry)
|
|
299
|
-
if (!existsSync(fullPath)) continue
|
|
300
|
-
const s = await stat(fullPath)
|
|
301
|
-
if (s.isFile()) {
|
|
302
|
-
collected.add(entry)
|
|
303
|
-
continue
|
|
304
|
-
}
|
|
305
|
-
if (!s.isDirectory()) continue
|
|
306
|
-
await walkDir(fullPath, p => {
|
|
307
|
-
const rel = p
|
|
308
|
-
.slice(npmRoot.length + 1)
|
|
309
|
-
.split(sep)
|
|
310
|
-
.join('/')
|
|
311
|
-
collected.add(rel)
|
|
312
|
-
})
|
|
313
|
-
}
|
|
314
|
-
const filtered = [...collected].filter(rel => negatives.every(re => !re.test(rel)))
|
|
315
|
-
filtered.sort()
|
|
316
|
-
return filtered
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Чи є у файлі імпорт/require/dynamic-import з модуля тест-фреймворку.
|
|
321
|
-
* Парсимо через oxc-parser (як `bunyan-imports`/`redis-imports`). При помилці
|
|
322
|
-
* парсингу повертаємо `null` — це не наш checker для синтаксису.
|
|
323
|
-
* @param {string} content повний текст файлу
|
|
324
|
-
* @param {string} virtualPath шлях для вибору `lang`
|
|
325
|
-
* @returns {string | null} модуль, через який видно тест, або `null`
|
|
326
|
-
*/
|
|
327
|
-
export function findTestFrameworkImport(content, virtualPath) {
|
|
328
|
-
const lang = langFromPath(virtualPath)
|
|
329
|
-
let result
|
|
330
|
-
try {
|
|
331
|
-
result = parseSync(virtualPath, content, { lang, sourceType: 'module' })
|
|
332
|
-
} catch {
|
|
333
|
-
return null
|
|
334
|
-
}
|
|
335
|
-
if (result.errors?.length) return null
|
|
336
|
-
for (const imp of result.module?.staticImports ?? []) {
|
|
337
|
-
const mod = imp.moduleRequest?.value
|
|
338
|
-
if (typeof mod === 'string' && TEST_FRAMEWORK_MODULES.has(mod)) return mod
|
|
339
|
-
}
|
|
340
|
-
/** @type {string | null} */
|
|
341
|
-
let found = null
|
|
342
|
-
walkAstWithAncestors(result.program, [], node => {
|
|
343
|
-
if (found) return
|
|
344
|
-
const reqMod = requireCallModule(node)
|
|
345
|
-
if (reqMod && TEST_FRAMEWORK_MODULES.has(reqMod)) {
|
|
346
|
-
found = reqMod
|
|
347
|
-
return
|
|
348
|
-
}
|
|
349
|
-
const dynMod = dynamicImportModule(node)
|
|
350
|
-
if (dynMod && TEST_FRAMEWORK_MODULES.has(dynMod)) {
|
|
351
|
-
found = dynMod
|
|
352
|
-
}
|
|
353
|
-
})
|
|
354
|
-
return found
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Класифікує опублікований файл як test/fixture, якщо хоча б одна з ознак:
|
|
359
|
-
* (1) у шляху є каталог із `TEST_DIR_NAMES`; (2) basename відповідає
|
|
360
|
-
* `TEST_FILE_PATTERNS`; (3) для JS/TS-розширень — імпорт test-фреймворку.
|
|
361
|
-
*
|
|
362
|
-
* Carve-out: для шляху `rules/<rule-name>/...` сегмент `<rule-name>` (індекс 1)
|
|
363
|
-
* — це ім'я правила, а не каталог. Зокрема, правило з id `test` (або `tests`)
|
|
364
|
-
* описує конвенцію розміщення тестів і саме по собі не є test-fixture'ом.
|
|
365
|
-
* Подальші сегменти (наприклад, `rules/<r>/js/<c>/tests/`) продовжують перевірятись.
|
|
366
|
-
* @param {string} relPath posix-шлях відносно `npm/`
|
|
367
|
-
* @returns {Promise<string | null>} причина порушення або `null`
|
|
368
|
-
* @param {string} [cwd] корінь репозиторію
|
|
369
|
-
*/
|
|
370
|
-
export async function classifyPublishedFileAsTest(relPath, cwd = process.cwd()) {
|
|
371
|
-
const segments = relPath.split('/')
|
|
372
|
-
const base = segments.at(-1)
|
|
373
|
-
const dirs = segments.slice(0, -1)
|
|
374
|
-
const testDir = dirs.find((seg, idx) => {
|
|
375
|
-
if (idx === 1 && dirs[0] === 'rules') {
|
|
376
|
-
return false
|
|
377
|
-
}
|
|
378
|
-
return TEST_DIR_NAMES.has(seg.toLowerCase())
|
|
379
|
-
})
|
|
380
|
-
if (testDir) return `test-style каталог "${testDir}/"`
|
|
381
|
-
if (TEST_FILE_PATTERNS.some(re => re.test(base))) return `test-style ім'я файлу`
|
|
382
|
-
if (JS_LIKE_EXT_RE.test(base)) {
|
|
383
|
-
const content = await readFile(join(cwd, 'npm', relPath), 'utf8')
|
|
384
|
-
const mod = findTestFrameworkImport(content, relPath)
|
|
385
|
-
if (mod) return `імпорт test-фреймворку "${mod}"`
|
|
386
|
-
}
|
|
387
|
-
return null
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Для всіх файлів, що потрапили б у tarball (positive `files` мінус negative
|
|
392
|
-
* patterns), забороняє test-style каталоги/імена/імпорти. Так пакет лишається
|
|
393
|
-
* компактним і не везе користувачам тести й фікстури.
|
|
394
|
-
* @param {(msg: string) => void} pass callback при успіху
|
|
395
|
-
* @param {(msg: string) => void} fail callback при порушенні
|
|
396
|
-
* @returns {Promise<void>}
|
|
397
|
-
* @param {string} cwd корінь репозиторію
|
|
398
|
-
*/
|
|
399
|
-
async function checkNoTestsInPublishedFiles(pass, fail, cwd) {
|
|
400
|
-
if (!existsSync(join(cwd, 'npm/package.json'))) return
|
|
401
|
-
const pkg = JSON.parse(await readFile(join(cwd, 'npm/package.json'), 'utf8'))
|
|
402
|
-
if (!Array.isArray(pkg.files)) return
|
|
403
|
-
const files = await collectPublishedFiles(pkg.files, cwd)
|
|
404
|
-
/** @type {{ file: string, reason: string }[]} */
|
|
405
|
-
const violations = []
|
|
406
|
-
for (const rel of files) {
|
|
407
|
-
const reason = await classifyPublishedFileAsTest(rel, cwd)
|
|
408
|
-
if (reason) violations.push({ file: rel, reason })
|
|
409
|
-
}
|
|
410
|
-
if (violations.length === 0) {
|
|
411
|
-
pass(`npm/: усі ${files.length} опублікованих файли без тестів і fixtures`)
|
|
412
|
-
return
|
|
413
|
-
}
|
|
414
|
-
for (const v of violations) {
|
|
415
|
-
fail(
|
|
416
|
-
`npm/${v.file}: ${v.reason} — додай у "files" у npm/package.json негативний glob, ` +
|
|
417
|
-
'що виключає цей файл з tarball (наприклад "!**/*.test.mjs", "!**/fixtures/**", "!**/*_test.rego") (npm-module.mdc)'
|
|
418
|
-
)
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* Перевіряє базову структуру монорепо: наявність каталогу `npm/` і
|
|
424
|
-
* `npm/package.json`. Поле `workspaces ∋ "npm"` у кореневому `package.json`
|
|
425
|
-
* валідує `npm/policy/npm_module/root_package_json/`.
|
|
426
|
-
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
427
|
-
* @param {(msg: string) => void} fail callback при помилці
|
|
428
|
-
* @param {string} cwd корінь репозиторію
|
|
429
|
-
*/
|
|
430
|
-
async function checkNpmModuleBasicStructure(pass, fail, cwd) {
|
|
431
|
-
if (existsSync(join(cwd, 'package.json'))) {
|
|
432
|
-
pass('package.json існує')
|
|
433
|
-
} else {
|
|
434
|
-
fail('package.json не існує')
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const npmDir = join(cwd, 'npm')
|
|
438
|
-
if (existsSync(npmDir)) {
|
|
439
|
-
const s = await stat(npmDir)
|
|
440
|
-
if (s.isDirectory()) {
|
|
441
|
-
pass('npm/ директорія існує')
|
|
442
|
-
} else {
|
|
443
|
-
fail('npm має бути директорією')
|
|
444
|
-
}
|
|
445
|
-
} else {
|
|
446
|
-
fail('npm/ директорія не існує')
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
if (existsSync(join(cwd, 'npm/package.json'))) {
|
|
450
|
-
pass('npm/package.json існує')
|
|
451
|
-
} else {
|
|
452
|
-
fail('npm/package.json не існує — створи package.json для npm модуля')
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* Перевіряє відповідність проєкту правилам npm-module.mdc
|
|
458
|
-
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту
|
|
459
|
-
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат лінту
|
|
460
|
-
*/
|
|
461
|
-
export async function lint(ctx) {
|
|
462
|
-
const cwd = ctx.cwd
|
|
463
|
-
const reporter = createViolationReporter(ctx)
|
|
464
|
-
const { pass, fail } = reporter
|
|
465
|
-
|
|
466
|
-
await checkNpmModuleBasicStructure(pass, fail, cwd)
|
|
467
|
-
await checkNoTestsInPublishedFiles(pass, fail, cwd)
|
|
468
|
-
|
|
469
|
-
const ignorePaths = await loadCursorIgnorePaths(cwd)
|
|
470
|
-
const useSrcJsLayout = await npmSrcTreeHasJsFile(cwd, ignorePaths)
|
|
471
|
-
|
|
472
|
-
await checkNpmPackageJson(useSrcJsLayout, pass, fail, cwd)
|
|
473
|
-
|
|
474
|
-
if (!useSrcJsLayout) {
|
|
475
|
-
await checkEmitTypesConfig(pass, fail, cwd)
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
const layoutLabel = useSrcJsLayout ? 'layout src' : 'tsconfig emit-types'
|
|
479
|
-
const hk = await readHkConfig(cwd)
|
|
480
|
-
if (hk) {
|
|
481
|
-
pass(`${hk.path} існує`)
|
|
482
|
-
const missing = useSrcJsLayout ? missingHkSrcLayoutFragments(hk.text) : missingHkEmitTypesConfigFragments(hk.text)
|
|
483
|
-
if (missing.length === 0) {
|
|
484
|
-
pass(`${hk.path}: pre-commit містить очікуваний виклик tsc (${layoutLabel})`)
|
|
485
|
-
} else {
|
|
486
|
-
fail(`${hk.path}: онови pre-commit крок (npm-module.mdc); не знайдено: ${missing.join(', ')}`)
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
if (DEPRECATED_CHECK_CHANGELOG_RE.test(hk.text)) {
|
|
490
|
-
fail(
|
|
491
|
-
`${hk.path}: крок містить застарілий виклик "check changelog" — команду \`check\` прибрано в v14 ` +
|
|
492
|
-
'(уніфікована поверхня `lint`). Заміни на "npx @7n/rules lint changelog" (npm-module.mdc)'
|
|
493
|
-
)
|
|
494
|
-
} else {
|
|
495
|
-
const missingChangelogStep = missingHkNpmChangelogFragments(hk.text)
|
|
496
|
-
if (missingChangelogStep.length === 0) {
|
|
497
|
-
pass(`${hk.path}: pre-commit містить крок npm-changelog (npm-module.mdc)`)
|
|
498
|
-
} else {
|
|
499
|
-
fail(`${hk.path}: онови крок npm-changelog (npm-module.mdc); не знайдено: ${missingChangelogStep.join(', ')}`)
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
} else {
|
|
503
|
-
fail('Очікується hk.pkl або .config/hk.pkl з pre-commit і tsc (npm-module.mdc)')
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
if (existsSync(join(cwd, '.github/workflows'))) {
|
|
507
|
-
pass('.github/workflows/ існує')
|
|
508
|
-
} else {
|
|
509
|
-
fail('.github/workflows/ не існує')
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
await checkPublishWorkflow(pass, fail, cwd)
|
|
513
|
-
|
|
514
|
-
return reporter.result()
|
|
515
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
## Структура монорепо та компактний пакет
|
|
2
|
-
|
|
3
|
-
Bun monorepo: workspace **`npm/`**, кореневий **`package.json`**, **`.github/workflows/`**; опційно **`demo/`**.
|
|
4
|
-
|
|
5
|
-
Мета — **максимально компактний** опублікований пакет: у npm потрапляє тільки те, що потрібно під час `require`/`import` користувачем.
|
|
6
|
-
|
|
7
|
-
- **`"files"` обовʼязковий** у `npm/package.json` як **whitelist** того, що публікується (без `"files"` npm пакує майже все — це антипатерн для цього правила).
|
|
8
|
-
- **Тести й фікстури не публікуються — через негативні glob-патерни у `"files"`.** Тести можна (і зазвичай зручніше) тримати **поруч з кодом** усередині шляхів, перелічених у `"files"` (наприклад `*.test.mjs` поруч з модулем чи `fixtures/` під `rules/<id>/js/`). Щоб вони не потрапляли у tarball, `"files"` **обовʼязково має містити негативні glob-патерни**, що їх виключають. Покрий усі форми тестового: фреймворк-тести (`bun:test`, `node:test`, `vitest`, `@jest/globals`, `mocha`, …), test-style каталоги (`tests/`, `__tests__/`, `fixtures/`, `__fixtures__/`, `spec/`, `test/`), файли за патернами `*.test.*` / `*.spec.*`. Орієнтовний набір: `"!**/*.test.*"`, `"!**/*.spec.*"`, `"!**/test-helpers.*"`, `"!**/fixtures/**"`, `"!**/__tests__/**"`. **Rego (`*_test.rego`):** за конвенцією conftest юніт-тест лежить поруч з полісі у тому самому `package` — `*_test.rego` усередині опублікованого `policy/` дозволені; якщо потрібна максимальна компактність, додай `"!**/*_test.rego"` явно (як у самому `@7n/rules`).
|
|
9
|
-
- **Лише runtime-залежності у `npm/package.json`.** `devDependencies` тримай у **кореневому** `package.json` монорепо — тоді `npm install @nitra/<pkg>` не тягне інструментарій, потрібний лише для розробки самого пакета.
|
|
10
|
-
|
|
11
|
-
Перевірки JS (`package_structure.mjs`):
|
|
12
|
-
|
|
13
|
-
- Наявність `package.json`, `npm/`, `npm/package.json`.
|
|
14
|
-
- Walk шляхів з `"files"` з застосуванням негативних patterns і скан залишку на тест-патерни (walking + AST). Якщо після застосування негативних patterns у tarball лишається test-style файл — `check` падає з вказівкою, який саме негативний glob треба додати у `"files"`.
|
|
15
|
-
|
|
16
|
-
## TypeScript declaration (`npm/types`)
|
|
17
|
-
|
|
18
|
-
Файл **`npm/package.json`** має містити **`"types"`** (шлях до головного `.d.ts` або `.d.mts` під **`./types/…`**) і запис **`"types"`** у **`files`**, щоб npm публікував декларації.
|
|
19
|
-
|
|
20
|
-
Генерація — через **`tsc`** і **`bunx -p typescript`** (окремий пакет **`typescript`** у `devDependencies` не потрібен).
|
|
21
|
-
|
|
22
|
-
### Варіант A: є вихідний **`.js`** під **`npm/src/`**
|
|
23
|
-
|
|
24
|
-
Якщо під **`npm/src`** (рекурсивно) є хоча б один файл **`.js`**:
|
|
25
|
-
|
|
26
|
-
- **`"types": "./types/index.d.ts"`**;
|
|
27
|
-
- у **hk** на **`pre-commit`** з каталогу **`npm/`** викликай:
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
bunx -p typescript tsc src/**/*.js --declaration --allowJs --emitDeclarationOnly --outDir types --skipLibCheck
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Якщо glob не розгортається — **`bash -O globstar`** або **`include`** у **`tsconfig`** з тими самими **`compilerOptions`**.
|
|
34
|
-
|
|
35
|
-
### Варіант B: немає **`npm/src/**/*.js`** (наприклад лише **`bin/`**, **`scripts/**/*.mjs`**)
|
|
36
|
-
|
|
37
|
-
Не створюй штучний **`src/index.js`**. Замість цього:
|
|
38
|
-
|
|
39
|
-
1. Додай **`npm/tsconfig.emit-types.json`** з **`include`** на реальні шляхи (`.js` / `.mjs`), **`compilerOptions`**: **`allowJs`**, **`declaration`**, **`emitDeclarationOnly`**, **`outDir`: `"types"`**, **`skipLibCheck`**: **`true`** (за потреби **`rootDir`**, **`module`**, **`moduleResolution`**).
|
|
40
|
-
2. Після першого **`tsc`** подивись, який **`.d.ts`** / **`.d.mts`** відповідає публічному API, і вкажи його в **`types`** (наприклад **`./types/bin/cli.d.ts`**).
|
|
41
|
-
3. У **hk** на **`pre-commit`** з **`npm/`** викликай **`tsc -p tsconfig.emit-types.json`**. Якщо через імпорти **TypeScript** все одно згенерує зайві **`.d.mts`** у **`types/`**, після **`tsc`** обрізай дерево до потрібного entrypoint (наприклад залиш лише **`types/bin/n-rules.d.ts`** через **`find`**), щоб у пакеті не збирались декларації внутрішніх модулів.
|
|
42
|
-
|
|
43
|
-
Файл **`tsconfig.emit-types.json`** тримай у репозиторії для **hk** / локальної генерації; у **`files`** його не додавай, якщо не хочеш публікувати його на **npm**.
|
|
44
|
-
|
|
45
|
-
## Git hooks: hk + pre-commit
|
|
46
|
-
|
|
47
|
-
У корені репозиторію — **`hk.pkl`** (або **`.config/hk.pkl`**) з **`["pre-commit"]`** і командою з відповідного варіанту (A або B) вище.
|
|
48
|
-
|
|
49
|
-
Після додавання **`hk.pkl`**: **`hk install`**.
|
|
50
|
-
|
|
51
|
-
## npm publish
|
|
52
|
-
|
|
53
|
-
**`npm-publish.yml`:** push у **`main`**, **`on.push.paths`** з **`npm/**`**, **`JS-DevTools/npm-publish@v4.1.5`**, **`with.package: npm/package.json`**, **`permissions.id-token: write`** (OIDC на npm).
|
|
54
|
-
|
|
55
|
-
Workflow робить **release + publish** одним job (`release-publish`): крок **`Release (bump + CHANGELOG + tag)`** (`bunx n-rules release` — агрегує change-файли, bump `version`, генерує секцію `CHANGELOG.md`, ставить git-тег) виконується **перед** публікацією. Тому потрібні **`permissions.contents: write`** і **`persist-credentials: true`** з **`fetch-depth: 0`** на `checkout` (release пушить commit-back версії та тег), а також локальний composite **`./.github/actions/setup-bun-deps`** і крок `Configure git identity`. Це узгоджено з **`n-changelog`**: `version`/`CHANGELOG.md` змінює лише `n-rules release` у CI на `main`. Програмна перевірка (`npm_module.npm_publish_yml`) звіряє **весь канонічний сніпет** напряму (`target.json:"check":"template"`, generic deep-subset): усі поля й кроки сніпета (`on.push.paths`/`branches`, `concurrency`, `permissions.contents/id-token`, `checkout` з `persist-credentials/fetch-depth`, `setup-bun-deps`, `Configure git identity`, `Release`, publish-крок) **обовʼязкові**; зайві кроки/поля дозволені (subset-of), масиви матчаться за наявністю (порядок кроків не важить). Сніпет — єдине джерело істини: його редагування одразу змінює enforce, без правок rego й без міграторів.
|
|
56
|
-
|
|
57
|
-
- Канон: [npm-publish.yml.snippet.yml](./policy/npm_publish_yml/template/npm-publish.yml.snippet.yml)
|
|
58
|
-
|
|
59
|
-
## Канонічні конфіги
|
|
60
|
-
|
|
61
|
-
- Кореневий `package.json` (workspaces): [package.json.snippet.json](./policy/root_package_json/template/package.json.snippet.json)
|
|
62
|
-
- `npm/package.json` (whitelist `files` обовʼязково має містити `types`): [package.json.snippet.json](./policy/npm_package_json/template/package.json.snippet.json)
|
|
63
|
-
- `npm/tsconfig.emit-types.json` (canonical `compilerOptions` для emit-types): [tsconfig.emit-types.json.snippet.json](./policy/emit_types_config/template/tsconfig.emit-types.json.snippet.json)
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: JS Module
|
|
3
|
-
title: fix-root_package_json.mjs
|
|
4
|
-
resource: npm/rules/npm-module/root_package_json/fix-root_package_json.mjs
|
|
5
|
-
docgen:
|
|
6
|
-
crc: 8181dffe
|
|
7
|
-
model: openai-codex/gpt-5.4-mini
|
|
8
|
-
tier: cloud-min
|
|
9
|
-
score: 100
|
|
10
|
-
issues: judge:inaccurate:0.97
|
|
11
|
-
judgeModel: openai-codex/gpt-5.4-mini
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## Огляд
|
|
15
|
-
|
|
16
|
-
`patterns` перевіряє кореневий `package.json` на відповідність еталонному шаблону npm-module і використовується для контролю очікуваної структури цього конфіга. Публічна функція: `patterns`. Конфіг, на який спирається код: `package.json`.
|
|
17
|
-
|
|
18
|
-
## Поведінка
|
|
19
|
-
|
|
20
|
-
1. `patterns` визначає набір правил, які звіряють кореневий `package.json` з еталонним шаблоном для npm-module.
|
|
21
|
-
2. Кожне правило в цьому наборі націлене на `package.json` і використовується для вирівнювання кореневого конфіга з очікуваною структурою проєкту.
|
|
22
|
-
3. `patterns` потрібен, щоб централізовано підтримувати однаковий формат root `package.json` у межах цього типу пакета.
|
|
23
|
-
|
|
24
|
-
## Гарантії поведінки
|
|
25
|
-
|
|
26
|
-
- Read-only: не виконує операцій запису (ФС/БД).
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: Directory Index
|
|
3
|
-
title: npm/rules/npm-module/root_package_json
|
|
4
|
-
resource: npm/rules/npm-module/root_package_json/
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
| Файл | Тип |
|
|
8
|
-
| ----------------------------------------------------- | --------- |
|
|
9
|
-
| [fix-root_package_json.mjs](fix-root_package_json.md) | JS Module |
|