@nitra/cursor 3.22.0 → 3.23.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/.pi-template/extensions/n-cursor-adr/docs/index.md +181 -0
- package/CHANGELOG.md +31 -3
- package/bin/docs/n-cursor.md +636 -0
- package/bin/docs/rename-yaml-extensions.md +207 -0
- package/bin/n-cursor.js +30 -3
- package/package.json +1 -1
- package/rules/abie/docs/fix.md +18 -0
- package/rules/abie/js/docs/applies.md +26 -0
- package/rules/abie/js/docs/env_dns.md +32 -0
- package/rules/abie/js/docs/firebase_hosting.md +23 -0
- package/rules/abie/js/docs/hc_pairing.md +35 -0
- package/rules/abie/js/docs/ua_http_route.md +28 -0
- package/rules/abie/js/docs/ua_node_selector.md +28 -0
- package/rules/abie/lib/docs/enabled.md +29 -0
- package/rules/abie/lib/docs/env-dns.md +35 -0
- package/rules/abie/lib/docs/hc-yaml.md +33 -0
- package/rules/abie/lib/docs/http-route.md +44 -0
- package/rules/abie/lib/docs/k8s-tree.md +40 -0
- package/rules/abie/lib/docs/kustomization-patches.md +47 -0
- package/rules/abie/lib/docs/overlay-paths.md +38 -0
- package/rules/abie/lib/docs/yaml.md +29 -0
- package/rules/adr/docs/fix.md +148 -0
- package/rules/adr/js/docs/hooks.md +259 -0
- package/rules/bun/docs/fix.md +156 -0
- package/rules/bun/js/docs/layout.md +393 -0
- package/rules/capacitor/docs/fix.md +121 -0
- package/rules/capacitor/js/docs/platforms.md +295 -0
- package/rules/changelog/changelog.mdc +2 -2
- package/rules/changelog/docs/fix.md +174 -0
- package/rules/changelog/js/consistency.mjs +114 -13
- package/rules/changelog/js/docs/consistency.md +387 -0
- package/rules/changelog/lib/docs/package-manifest.md +210 -0
- package/rules/ci4/docs/fix.md +179 -0
- package/rules/ci4/js/docs/marksman_config.md +128 -0
- package/rules/docker/docker.mdc +8 -3
- package/rules/docker/docs/fix.md +171 -0
- package/rules/docker/js/docs/lint.md +258 -0
- package/rules/docker/lib/docs/docker-hadolint.md +184 -0
- package/rules/docker/lib/docs/docker-mirror.md +247 -0
- package/rules/docker/lib/docs/docker-native-addon.md +170 -0
- package/rules/docker/lib/docs/docker-nginx-user.md +219 -0
- package/rules/docker/lint/docs/lint.md +193 -0
- package/rules/efes/docs/fix.md +203 -0
- package/rules/feedback/docs/fix.md +140 -0
- package/rules/flow/docs/fix.md +152 -0
- package/rules/ga/docs/fix.md +158 -0
- package/rules/ga/js/docs/lint.md +100 -0
- package/rules/ga/js/docs/workflows.md +217 -0
- package/rules/ga/lint/docs/lint.md +209 -0
- package/rules/ga/policy/clean_merged_branch/clean_merged_branch.rego +11 -2
- package/rules/ga/policy/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +3 -4
- package/rules/graphql/docs/fix.md +126 -0
- package/rules/graphql/js/docs/tooling.md +264 -0
- package/rules/graphql/lib/docs/graphql-gql-scan.md +219 -0
- package/rules/hasura/docs/fix.md +120 -0
- package/rules/hasura/hasura.mdc +14 -0
- package/rules/hasura/js/docs/internal_urls.md +326 -0
- package/rules/image-avif/docs/fix.md +132 -0
- package/rules/image-avif/js/docs/avif_generation.md +241 -0
- package/rules/image-compress/docs/fix.md +150 -0
- package/rules/image-compress/js/docs/package_setup.md +191 -0
- package/rules/js-bun-db/docs/fix.md +148 -0
- package/rules/js-bun-db/js/docs/safety.md +231 -0
- package/rules/js-bun-db/js-bun-db.mdc +42 -13
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +347 -0
- package/rules/js-bun-redis/docs/fix.md +123 -0
- package/rules/js-bun-redis/js/docs/imports.md +176 -0
- package/rules/js-bun-redis/lib/docs/redis-imports.md +223 -0
- package/rules/js-lint/docs/fix.md +117 -0
- package/rules/js-lint/js/docs/lint.md +250 -0
- package/rules/js-lint/js/docs/tooling.md +348 -0
- package/rules/js-lint/js/docs/utils_imports.md +207 -0
- package/rules/js-lint-ci/docs/fix.md +154 -0
- package/rules/js-lint-ci/js/docs/lint.md +144 -0
- package/rules/js-mssql/docs/fix.md +128 -0
- package/rules/js-mssql/js/docs/deps.md +263 -0
- package/rules/js-mssql/lib/docs/mssql-pool-scan.md +367 -0
- package/rules/js-run/docs/fix.md +144 -0
- package/rules/js-run/js/docs/runtime.md +388 -0
- package/rules/js-run/lib/docs/bunyan-imports.md +117 -0
- package/rules/js-run/lib/docs/check-env-scan.md +433 -0
- package/rules/js-run/lib/docs/conn-file-rules.md +300 -0
- package/rules/js-run/lib/docs/conn-imports-scan.md +204 -0
- package/rules/js-run/lib/docs/promise-settimeout-scan.md +326 -0
- package/rules/k8s/docs/fix.md +129 -0
- package/rules/k8s/js/docs/manifests.md +344 -0
- package/rules/k8s/js/manifests.mjs +6 -2
- package/rules/k8s/k8s.mdc +4 -2
- package/rules/k8s/lint/docs/lint.md +411 -0
- package/rules/k8s/policy/network_policy/template/deployment.snippet.yaml +2 -0
- package/rules/k8s/policy/network_policy/template/stateful-set.snippet.yaml +2 -0
- package/rules/nginx-default-tpl/docs/fix.md +124 -0
- package/rules/nginx-default-tpl/js/docs/template.md +378 -0
- package/rules/npm-module/docs/fix.md +98 -0
- package/rules/npm-module/js/docs/package_structure.md +274 -0
- package/rules/npm-module/js/docs/rule_meta.md +137 -0
- package/rules/npm-module/js/docs/skill_meta.md +190 -0
- package/rules/php/docs/fix.md +107 -0
- package/rules/php/js/docs/tooling.md +152 -0
- package/rules/php/lint/docs/lint.md +215 -0
- package/rules/python/docs/fix.md +163 -0
- package/rules/python/js/docs/applies.md +108 -0
- package/rules/python/js/docs/tooling.md +153 -0
- package/rules/python/lint/docs/lint.md +322 -0
- package/rules/rego/docs/fix.md +121 -0
- package/rules/rego/js/docs/applies.md +174 -0
- package/rules/rego/js/docs/lint.md +118 -0
- package/rules/rego/lint/docs/lint.md +204 -0
- package/rules/release/docs/change.md +185 -0
- package/rules/release/docs/fix.md +119 -0
- package/rules/release/docs/release.md +222 -0
- package/rules/release/lib/docs/aggregate.md +246 -0
- package/rules/release/lib/docs/change-file.md +200 -0
- package/rules/release/lib/docs/fallback.md +203 -0
- package/rules/rust/docs/fix.md +129 -0
- package/rules/rust/js/docs/applies.md +140 -0
- package/rules/rust/lib/docs/has-cargo-toml.md +130 -0
- package/rules/security/docs/fix.md +86 -0
- package/rules/security/js/docs/lint.md +171 -0
- package/rules/security/js/docs/sample_secret.md +190 -0
- package/rules/security/js/docs/trufflehog.md +137 -0
- package/rules/security/js/lint.mjs +9 -1
- package/rules/style-lint/docs/fix.md +155 -0
- package/rules/style-lint/js/docs/lint.md +184 -0
- package/rules/style-lint/js/docs/tooling.md +194 -0
- package/rules/tauri/docs/fix.md +158 -0
- package/rules/tauri/js/docs/cargo_mutants_config.md +168 -0
- package/rules/tauri/js/docs/tooling.md +228 -0
- package/rules/test/coverage/coverage.mjs +15 -3
- package/rules/test/docs/fix.md +132 -0
- package/rules/test/js/data/stryker_config/docs/stryker-vue-macros-ignorer.md +138 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +134 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +160 -0
- package/rules/test/js/data/vitest_config/docs/vitest.config.baseline.md +195 -0
- package/rules/test/js/docs/cargo_mutants_config.md +173 -0
- package/rules/test/js/docs/location.md +136 -0
- package/rules/test/js/docs/no-process-chdir.md +160 -0
- package/rules/test/js/docs/no-relative-fs-path.md +271 -0
- package/rules/test/js/docs/stryker_config.md +152 -0
- package/rules/test/js/docs/vitest-config-pool-forks.md +174 -0
- package/rules/text/docs/fix.md +118 -0
- package/rules/text/js/docs/forbidden-prettier.md +143 -0
- package/rules/text/js/docs/formatting.md +256 -0
- package/rules/text/js/docs/lint.md +122 -0
- package/rules/text/lint/docs/lint.md +220 -0
- package/rules/text/lint/docs/run-dotenv-linter.md +157 -0
- package/rules/text/lint/docs/run-shellcheck.md +212 -0
- package/rules/text/lint/docs/run-v8r.md +197 -0
- package/rules/vue/docs/fix.md +127 -0
- package/rules/vue/js/docs/packages.md +335 -0
- package/rules/vue/lib/docs/vue-forbidden-imports.md +261 -0
- package/rules/worktree/docs/fix.md +161 -0
- package/schemas/rule-meta.json +5 -1
- package/scripts/auto-rules.mjs +7 -4
- package/scripts/coverage-classify/docs/apply.md +202 -0
- package/scripts/coverage-classify/docs/cache.md +203 -0
- package/scripts/coverage-classify/docs/index.md +218 -0
- package/scripts/coverage-classify/docs/prompt.md +132 -0
- package/scripts/coverage-classify/docs/verdict-schema.md +169 -0
- package/scripts/coverage-fix-extract.mjs +122 -0
- package/scripts/coverage-fix.mjs +1 -1
- package/scripts/dispatcher/docs/graph.md +346 -0
- package/scripts/dispatcher/docs/index.md +236 -0
- package/scripts/dispatcher/docs/trace.md +296 -0
- package/scripts/dispatcher/index.mjs +1 -1
- package/scripts/dispatcher/lib/active.mjs +4 -8
- package/scripts/dispatcher/lib/commands.mjs +7 -11
- package/scripts/dispatcher/lib/docs/active.md +348 -0
- package/scripts/dispatcher/lib/docs/artifact.md +232 -0
- package/scripts/dispatcher/lib/docs/budget.md +167 -0
- package/scripts/dispatcher/lib/docs/capability.md +196 -0
- package/scripts/dispatcher/lib/docs/commands.md +210 -0
- package/scripts/dispatcher/lib/docs/events.md +182 -0
- package/scripts/dispatcher/lib/docs/executor.md +190 -0
- package/scripts/dispatcher/lib/docs/flow-lock.md +161 -0
- package/scripts/dispatcher/lib/docs/flow-resolve.md +267 -0
- package/scripts/dispatcher/lib/docs/gate.md +231 -0
- package/scripts/dispatcher/lib/docs/level.md +335 -0
- package/scripts/dispatcher/lib/docs/plan-panel.md +181 -0
- package/scripts/dispatcher/lib/docs/plan.md +200 -0
- package/scripts/dispatcher/lib/docs/planner.md +269 -0
- package/scripts/dispatcher/lib/docs/review.md +255 -0
- package/scripts/dispatcher/lib/docs/reviewer.md +240 -0
- package/scripts/dispatcher/lib/docs/snapshot.md +247 -0
- package/scripts/dispatcher/lib/docs/spec.md +203 -0
- package/scripts/dispatcher/lib/docs/state-store.md +303 -0
- package/scripts/dispatcher/lib/docs/subagent-runner.md +173 -0
- package/scripts/dispatcher/lib/executor.mjs +6 -1
- package/scripts/dispatcher/lib/flow-resolve.mjs +3 -1
- package/scripts/dispatcher/lib/level.mjs +29 -3
- package/scripts/dispatcher/lib/review.mjs +1 -1
- package/scripts/dispatcher/lib/subagent-runner.mjs +5 -3
- package/scripts/docs/auto-rules.md +376 -0
- package/scripts/docs/auto-skills.md +173 -0
- package/scripts/docs/build-agents-commands.md +183 -0
- package/scripts/docs/cli-entry.md +153 -0
- package/scripts/docs/coverage-fix.md +177 -0
- package/scripts/docs/ensure-nitra-cursor-dev-dependencies.md +189 -0
- package/scripts/lib/changed-files.mjs +4 -1
- package/scripts/lib/docs/changed-files.md +149 -0
- package/scripts/lib/docs/check-mdc-template-refs.md +222 -0
- package/scripts/lib/docs/check-reporter.md +175 -0
- package/scripts/lib/docs/discover-check-rules-from-cursor.md +157 -0
- package/scripts/lib/docs/discover-checkable-rules.md +165 -0
- package/scripts/lib/docs/ensure-tool.md +254 -0
- package/scripts/lib/docs/generated-markdown.md +275 -0
- package/scripts/lib/docs/gha-workflow.md +326 -0
- package/scripts/lib/docs/inline-template-links.md +303 -0
- package/scripts/lib/docs/list-rule-ids.md +156 -0
- package/scripts/lib/docs/load-cursor-config.md +147 -0
- package/scripts/lib/docs/mirror-parity.md +167 -0
- package/scripts/lib/worktree.mjs +26 -0
- package/scripts/worktree-cli.mjs +12 -2
- package/skills/coverage-fix/SKILL.md +34 -45
- package/skills/docgen/SKILL.md +44 -23
- package/skills/docgen/bench/etalon/firebase_hosting.md +19 -0
- package/skills/docgen/bench/etalon/k8s-tree.md +24 -0
- package/skills/docgen/bench/etalon/overlay-paths.md +24 -0
- package/skills/docgen/js/docgen-ignore.mjs +54 -0
- package/skills/docgen/js/docgen-scan.mjs +37 -21
- package/skills/llm-patch/SKILL.md +23 -2
- package/skills/start-check/SKILL.md +26 -53
- package/skills/start-check/js/check.mjs +211 -0
- package/skills/taze/SKILL.md +9 -3
- package/skills/taze/js/diff.mjs +154 -0
- package/types/bin/n-cursor.d.ts +1 -1
- package/skills/fix-tests/SKILL.md +0 -119
- package/skills/fix-tests/meta.json +0 -1
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# lint.mjs — перевірка Dockerfile / Containerfile (hadolint + правила docker.mdc)
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `npm/rules/docker/js/lint.mjs` реалізує комплексну перевірку файлів `Dockerfile` / `Containerfile` у репозиторії згідно з правилом `docker.mdc`. Він:
|
|
6
|
+
|
|
7
|
+
- знаходить усі `Dockerfile`, `Dockerfile.*`, `Containerfile`, `Containerfile.*` від кореня проєкту (з повагою до cursor-ignore шляхів);
|
|
8
|
+
- запускає на кожному з них нативний `hadolint` через утиліту `lintDockerfileWithHadolint` (PATH / кеш / авто-install через `ensureTool`, без `docker run`);
|
|
9
|
+
- додатково застосовує власні семантичні перевірки:
|
|
10
|
+
- усі `oven/bun`, `alpine`, `nginx`, `node` з Docker Hub мають іти через `mirror.gcr.io` (делегується `getMirrorGcrHint`);
|
|
11
|
+
- Dockerfile має бути **multistage** (мінімум 2 `FROM`);
|
|
12
|
+
- фінальний `FROM` має бути дозволеним runtime-образом (alpine, scratch, debian:_slim_, php, python, nginx-unprivileged, openresty; для проєктів із нативним `.node`-аддоном також `mirror.gcr.io/oven/bun:*`);
|
|
13
|
+
- якщо у Dockerfile є `bun install` і фінальний stage — alpine (backend), очікується `bun build --compile` у build stage, і у фінальному stage не повинно бути викликів `bun`;
|
|
14
|
+
- для проєктів із нативним `.node`-аддоном (sharp / @img/\* / argon2) компіляція через `bun build --compile` заборонена — застосовується окрема перевірка `getNativeAddonNoCompileHint`;
|
|
15
|
+
- фінальний stage має містити `USER <non-root>` (виняток — nginx-unprivileged, який і так від uid=101);
|
|
16
|
+
- для `mirror.gcr.io/nginxinc/nginx-unprivileged` у `FROM` тег має бути саме `alpine-slim`;
|
|
17
|
+
- додаткова перевірка nginx non-root (`getNginxUnprivilegedUserHint`).
|
|
18
|
+
|
|
19
|
+
Кореневий `.hadolint.yaml` підхоплюється hadolint автоматично. Модуль є точкою входу `check(cwd)` для CLI-перевірок і повертає exit code (0 — OK, 1 — є зауваження або помилка запуску).
|
|
20
|
+
|
|
21
|
+
## Експорти / API
|
|
22
|
+
|
|
23
|
+
| Експорт | Тип | Призначення |
|
|
24
|
+
| ------------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------- |
|
|
25
|
+
| `isDockerfileName(name)` | named function | Перевіряє, чи basename відповідає Dockerfile / Containerfile (включно з суфіксами). |
|
|
26
|
+
| `findDockerfilePaths(root, ignorePaths?)` | named async function | Збирає абсолютні шляхи до Dockerfile/Containerfile у репозиторії. |
|
|
27
|
+
| `parseFromStages(fileContent)` | named function | Парсить інструкції `FROM` і повертає масив `{ line, image }`. |
|
|
28
|
+
| `splitDockerfileStages(fileContent)` | named function | Розбиває Dockerfile на масив stage-ів `{ from, stageContent }`. |
|
|
29
|
+
| `getMultistageAndRuntimeHint(fileContent, opts?)` | named function | Перевіряє multistage та дозволеність фінального runtime-образу. |
|
|
30
|
+
| `getBunCompileHint(fileContent)` | named function | Перевіряє правило компіляції bun у бінарник на backend runtime. |
|
|
31
|
+
| `getNginxAlpineSlimTagHint(fileContent)` | named function | Перевіряє тег `alpine-slim` для nginx-unprivileged. |
|
|
32
|
+
| `getNonRootRuntimeHint(fileContent)` | named function | Перевіряє наявність `USER <non-root>` у фінальному stage. |
|
|
33
|
+
| `check(cwd?)` | named async function | Точка входу: обходить репозиторій, запускає всі перевірки, повертає exit code. |
|
|
34
|
+
|
|
35
|
+
Внутрішні (не експортуються): `isAllowedFinalRuntimeImage`, `readNearestDependencies`, `checkDockerfile`, а також модульні константи (`NEWLINE_RE`, `BUN_INSTALL_RE`, `BUN_BUILD_COMPILE_RE`, `BUN_WORD_RE`, `USER_LINE_RE`, `NGINX_UNPRIVILEGED_MIRROR_PREFIX`, `RUNTIME_IMAGES`, `DEBIAN_VIA_MIRROR_RE`, `BUN_RUNTIME_IMAGE`).
|
|
36
|
+
|
|
37
|
+
## Типи
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {{
|
|
42
|
+
* line: number
|
|
43
|
+
* image: string
|
|
44
|
+
* }} FromStage
|
|
45
|
+
*/
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`FromStage` — описує одну `FROM`-інструкцію: 1-based номер рядка і строковий image-ref (як у Dockerfile, з можливим тегом та `@digest`).
|
|
49
|
+
|
|
50
|
+
## Функції
|
|
51
|
+
|
|
52
|
+
### `isDockerfileName(name)`
|
|
53
|
+
|
|
54
|
+
- **Сигнатура:** `(name: string) => boolean`
|
|
55
|
+
- **Параметри:** `name` — basename файлу.
|
|
56
|
+
- **Повертає:** `true`, якщо ім'я (case-insensitive) дорівнює `dockerfile` / `containerfile` або починається з `dockerfile.` / `containerfile.` (наприклад `Dockerfile.prod`); інакше `false`.
|
|
57
|
+
- **Side effects:** немає.
|
|
58
|
+
|
|
59
|
+
### `findDockerfilePaths(root, ignorePaths = [])`
|
|
60
|
+
|
|
61
|
+
- **Сигнатура:** `(root: string, ignorePaths?: string[]) => Promise<string[]>`
|
|
62
|
+
- **Параметри:**
|
|
63
|
+
- `root` — корінь репозиторію, від якого виконується обхід;
|
|
64
|
+
- `ignorePaths` — масив каталогів, повністю виключених з обходу (наприклад `node_modules`, `.git`); передається в `walkDir`.
|
|
65
|
+
- **Повертає:** відсортований (через `localeCompare`) масив абсолютних шляхів до знайдених Dockerfile/Containerfile.
|
|
66
|
+
- **Side effects:** I/O на читання директорій через `walkDir`.
|
|
67
|
+
|
|
68
|
+
### `parseFromStages(fileContent)`
|
|
69
|
+
|
|
70
|
+
- **Сигнатура:** `(fileContent: string) => FromStage[]`
|
|
71
|
+
- **Параметри:** `fileContent` — повний вміст Dockerfile/Containerfile.
|
|
72
|
+
- **Повертає:** масив `FromStage` для кожного рядка, де `getFromImageToken` повернув непорожній image-ref. Номер рядка — 1-based.
|
|
73
|
+
- **Side effects:** немає (чиста функція).
|
|
74
|
+
|
|
75
|
+
### `isAllowedFinalRuntimeImage(lastLower, hasNativeAddon = false)` _(внутрішня)_
|
|
76
|
+
|
|
77
|
+
- **Сигнатура:** `(lastLower: string, hasNativeAddon?: boolean) => boolean`
|
|
78
|
+
- **Параметри:**
|
|
79
|
+
- `lastLower` — image-ref останнього `FROM` без digest, у нижньому регістрі;
|
|
80
|
+
- `hasNativeAddon` — чи має проєкт нативний `.node`-аддон (sharp / @img/\* / argon2).
|
|
81
|
+
- **Повертає:** `true`, якщо образ дозволений як фінальний runtime:
|
|
82
|
+
- `scratch` або `scratch:*`;
|
|
83
|
+
- якщо `hasNativeAddon` — додатково `mirror.gcr.io/oven/bun` або `mirror.gcr.io/oven/bun:*`;
|
|
84
|
+
- `mirror.gcr.io/library/debian:<tag>` за умови, що `<tag>` містить підрядок `slim`;
|
|
85
|
+
- будь-який з `RUNTIME_IMAGES` (alpine, php, python, nginx-unprivileged, openresty) як точне співпадіння або з тегом `:*`.
|
|
86
|
+
- **Side effects:** немає.
|
|
87
|
+
|
|
88
|
+
### `splitDockerfileStages(fileContent)`
|
|
89
|
+
|
|
90
|
+
- **Сигнатура:** `(fileContent: string) => Array<{ from: FromStage, stageContent: string }>`
|
|
91
|
+
- **Параметри:** `fileContent` — вміст Dockerfile.
|
|
92
|
+
- **Повертає:**
|
|
93
|
+
- порожній масив, якщо `FROM` немає;
|
|
94
|
+
- інакше масив об'єктів `{ from, stageContent }`: рядки від `FROM` поточного stage (включно) до рядка перед наступним `FROM` (а для останнього — до кінця файлу), з'єднані через `\n`.
|
|
95
|
+
- **Side effects:** немає.
|
|
96
|
+
|
|
97
|
+
### `getMultistageAndRuntimeHint(fileContent, opts?)`
|
|
98
|
+
|
|
99
|
+
- **Сигнатура:** `(fileContent: string, opts?: { hasNativeAddon?: boolean }) => string | null`
|
|
100
|
+
- **Параметри:**
|
|
101
|
+
- `fileContent` — вміст Dockerfile;
|
|
102
|
+
- `opts.hasNativeAddon` — чи проєкт залежить від нативних `.node`-аддонів.
|
|
103
|
+
- **Повертає:**
|
|
104
|
+
- `null`, якщо `FROM` немає або всі вимоги задоволені;
|
|
105
|
+
- повідомлення про відсутність multistage (`'має бути multistage build: мінімум 2 інструкції FROM (build stage + runtime stage)'`), якщо лише один `FROM`;
|
|
106
|
+
- повідомлення `'фінальний FROM має бути дозволеним runtime-образом (див. docker.mdc: multistage), зараз: <image> (рядок N)'`, якщо фінальний образ не пройшов `isAllowedFinalRuntimeImage`.
|
|
107
|
+
- **Side effects:** немає.
|
|
108
|
+
|
|
109
|
+
### `getBunCompileHint(fileContent)`
|
|
110
|
+
|
|
111
|
+
- **Сигнатура:** `(fileContent: string) => string | null`
|
|
112
|
+
- **Параметри:** `fileContent` — вміст Dockerfile.
|
|
113
|
+
- **Тригер активації:** у файлі є `bun install` / `bun i` (за `BUN_INSTALL_RE`) **та** фінальний `FROM` починається з `mirror.gcr.io/library/alpine:` **та** він не є nginx-unprivileged / openresty (frontend).
|
|
114
|
+
- **Повертає:**
|
|
115
|
+
- `null`, якщо немає stage-ів, немає `bun install`, фінал не alpine, або фінал — frontend (nginx/openresty);
|
|
116
|
+
- повідомлення `'є `bun install`, але немає `bun build --compile` …'`, якщо у файлі немає `bun build --compile`;
|
|
117
|
+
- повідомлення `'фінальний stage не має містити Bun …'`, якщо у `stageContent` останнього stage зустрічається слово `bun` (RUN/CMD/ENTRYPOINT з `bun`).
|
|
118
|
+
- **Side effects:** немає.
|
|
119
|
+
|
|
120
|
+
### `getNginxAlpineSlimTagHint(fileContent)`
|
|
121
|
+
|
|
122
|
+
- **Сигнатура:** `(fileContent: string) => string | null`
|
|
123
|
+
- **Параметри:** `fileContent` — вміст Dockerfile.
|
|
124
|
+
- **Повертає:**
|
|
125
|
+
- `null`, якщо немає `FROM` з префіксом `mirror.gcr.io/nginxinc/nginx-unprivileged` або всі такі `FROM` мають тег `alpine-slim`;
|
|
126
|
+
- повідомлення про відсутній явний тег (`FROM <prefix>` без `:tag`);
|
|
127
|
+
- повідомлення про неправильний тег (наприклад `:latest`, `:alpine`).
|
|
128
|
+
- **Side effects:** немає.
|
|
129
|
+
|
|
130
|
+
### `getNonRootRuntimeHint(fileContent)`
|
|
131
|
+
|
|
132
|
+
- **Сигнатура:** `(fileContent: string) => string | null`
|
|
133
|
+
- **Параметри:** `fileContent` — вміст Dockerfile.
|
|
134
|
+
- **Логіка:**
|
|
135
|
+
- бере останній stage через `splitDockerfileStages`;
|
|
136
|
+
- проходить рядки і запам'ятовує останній `USER <token>` (regex `USER_LINE_RE`, з лапок видаляються `"`/`'`);
|
|
137
|
+
- якщо `USER` відсутній і фінальний образ — `mirror.gcr.io/nginxinc/nginx-unprivileged:*`, повертає `null` (виняток для nginx-unprivileged, який стартує від uid=101);
|
|
138
|
+
- якщо `USER` відсутній — повертає повідомлення про необхідність `USER <non-root>`;
|
|
139
|
+
- якщо `USER` дорівнює `root` або `0` (без врахування регістру) — повертає повідомлення про заборону root.
|
|
140
|
+
- **Повертає:** `string | null` (повідомлення помилки або `null`).
|
|
141
|
+
- **Side effects:** немає.
|
|
142
|
+
|
|
143
|
+
### `check(cwd = process.cwd())`
|
|
144
|
+
|
|
145
|
+
- **Сигнатура:** `(cwd?: string) => Promise<number>`
|
|
146
|
+
- **Параметри:** `cwd` — корінь репозиторію; за замовчуванням `process.cwd()`.
|
|
147
|
+
- **Логіка:**
|
|
148
|
+
1. Створює репортер `createCheckReporter()`.
|
|
149
|
+
2. Завантажує `ignorePaths` через `loadCursorIgnorePaths(root)`.
|
|
150
|
+
3. Знаходить усі Dockerfile/Containerfile через `findDockerfilePaths`.
|
|
151
|
+
4. Якщо файлів немає — `pass('Немає Dockerfile / Containerfile — перевірку hadolint пропущено')` і виходить.
|
|
152
|
+
5. Інакше виводить `Знайдено файлів для hadolint: N` і послідовно викликає `checkDockerfile(reporter, root, abs)` для кожного.
|
|
153
|
+
6. Повертає `reporter.getExitCode()`.
|
|
154
|
+
- **Повертає:** `0` — все OK, `1` — є зауваження або помилка запуску.
|
|
155
|
+
- **Side effects:** читання файлової системи, синхронний/асинхронний запуск hadolint через `lintDockerfileWithHadolint` (нативний бінарник через `ensureTool`).
|
|
156
|
+
|
|
157
|
+
### `readNearestDependencies(abs, root)` _(внутрішня)_
|
|
158
|
+
|
|
159
|
+
- **Сигнатура:** `(abs: string, root: string) => Promise<Record<string, unknown>>`
|
|
160
|
+
- **Параметри:**
|
|
161
|
+
- `abs` — абсолютний шлях до Dockerfile;
|
|
162
|
+
- `root` — корінь репозиторію (зупинка піднімання).
|
|
163
|
+
- **Логіка:** піднімається від `dirname(abs)` вгору, шукаючи `package.json`. Як тільки знаходить — повертає `dependencies` (або `{}`, якщо `dependencies` відсутні / не об'єкт). Якщо досягає `root` або вищого каталогу без `package.json` — повертає `{}`.
|
|
164
|
+
- **Повертає:** `dependencies` найближчого `package.json` або порожній об'єкт.
|
|
165
|
+
- **Side effects:** I/O на читання `package.json`; помилки `readFile` поглинаються (catch без оголошення).
|
|
166
|
+
|
|
167
|
+
### `checkDockerfile(reporter, root, abs)` _(внутрішня)_
|
|
168
|
+
|
|
169
|
+
- **Сигнатура:** `(reporter: ReturnType<typeof createCheckReporter>, root: string, abs: string) => Promise<void>`
|
|
170
|
+
- **Параметри:**
|
|
171
|
+
- `reporter` — інстанс репортера з `pass` / `fail`;
|
|
172
|
+
- `root` — корінь репо (для розрахунку posix-relative шляху);
|
|
173
|
+
- `abs` — абсолютний шлях до Dockerfile.
|
|
174
|
+
- **Логіка (послідовно):**
|
|
175
|
+
1. `rel = posixRel(root, abs) || basename(abs)`.
|
|
176
|
+
2. Читає вміст файлу (`readFile(abs, 'utf8')`).
|
|
177
|
+
3. Визначає `nativeAddons` через `getNativeAddonDeps(await readNearestDependencies(abs, root))`; `hasNativeAddon = nativeAddons.length > 0`.
|
|
178
|
+
4. `getMirrorGcrHint(content)` → `fail` з префіксом `mirror.gcr.io`.
|
|
179
|
+
5. `getMultistageAndRuntimeHint(content, { hasNativeAddon })` → `fail` з префіксом `multistage`.
|
|
180
|
+
6. Якщо `hasNativeAddon` — `getNativeAddonNoCompileHint(content, nativeAddons)` → `fail` з префіксом `native-addon`; інакше — `getBunCompileHint(content)` → `fail` з префіксом `compile`.
|
|
181
|
+
7. `getNonRootRuntimeHint(content)` → `fail` з префіксом `non-root`.
|
|
182
|
+
8. `getNginxAlpineSlimTagHint(content)` → `fail` з префіксом `nginx tag`.
|
|
183
|
+
9. `getNginxUnprivilegedUserHint(content)` → `fail` з префіксом `nginx non-root`.
|
|
184
|
+
10. `lintDockerfileWithHadolint(root, abs)` (синхронний виклик з результатом `{ ok, stdout, stderr, via }`): якщо `ok` — `pass(`${rel} (${via})`)`, інакше `fail` з хвостом stdout+stderr.
|
|
185
|
+
- **Повертає:** `Promise<void>`.
|
|
186
|
+
- **Side effects:** I/O на читання Dockerfile/package.json, запуск hadolint, виклики `reporter.pass`/`reporter.fail`.
|
|
187
|
+
|
|
188
|
+
## Константи / regex
|
|
189
|
+
|
|
190
|
+
- `NEWLINE_RE = /\r?\n/` — розділення на рядки (CRLF/LF).
|
|
191
|
+
- `BUN_INSTALL_RE = /\bbun\s+(?:install|i)\b/iu` — детект `bun install` / `bun i`.
|
|
192
|
+
- `BUN_BUILD_COMPILE_RE = /\bbun\s+build\b[^\n]*\s--compile\b/iu` — детект `bun build … --compile`.
|
|
193
|
+
- `BUN_WORD_RE = /\bbun\b/iu` — будь-яке слово `bun`.
|
|
194
|
+
- `USER_LINE_RE = /^\s*USER\s+([^\s#]+)/iu` — інструкція `USER`.
|
|
195
|
+
- `NGINX_UNPRIVILEGED_MIRROR_PREFIX = 'mirror.gcr.io/nginxinc/nginx-unprivileged'`.
|
|
196
|
+
- `RUNTIME_IMAGES` — `const`-кортеж дозволених фінальних runtime-образів: `mirror.gcr.io/library/alpine`, `…/library/php`, `…/library/python`, `…/nginxinc/nginx-unprivileged`, `…/openresty/openresty`.
|
|
197
|
+
- `DEBIAN_VIA_MIRROR_RE = /^mirror\.gcr\.io\/library\/debian:(.+)$/i` — debian через mirror, для перевірки `slim` у тегу.
|
|
198
|
+
- `BUN_RUNTIME_IMAGE = 'mirror.gcr.io/oven/bun'` — bun як фінальний runtime (легітимний лише для нативних `.node`-аддонів).
|
|
199
|
+
|
|
200
|
+
## Залежності
|
|
201
|
+
|
|
202
|
+
### Зовнішні (Node.js standard library)
|
|
203
|
+
|
|
204
|
+
- `node:fs/promises` → `readFile`.
|
|
205
|
+
- `node:path` → `basename`, `dirname`, `join`.
|
|
206
|
+
|
|
207
|
+
### Внутрішні модулі репозиторію
|
|
208
|
+
|
|
209
|
+
- `../lib/docker-mirror.mjs` → `getMirrorGcrHint`, `getFromImageToken`.
|
|
210
|
+
- `../lib/docker-native-addon.mjs` → `getNativeAddonDeps`, `getNativeAddonNoCompileHint`.
|
|
211
|
+
- `../lib/docker-nginx-user.mjs` → `getNginxUnprivilegedUserHint`.
|
|
212
|
+
- `../lib/docker-hadolint.mjs` → `lintDockerfileWithHadolint`, `posixRel`.
|
|
213
|
+
- `../../../scripts/lib/check-reporter.mjs` → `createCheckReporter` (з методами `pass`, `fail`, `getExitCode`).
|
|
214
|
+
- `../../../scripts/lib/load-cursor-config.mjs` → `loadCursorIgnorePaths`.
|
|
215
|
+
- `../../../scripts/utils/walkDir.mjs` → `walkDir`.
|
|
216
|
+
|
|
217
|
+
### Зовнішні бінарники
|
|
218
|
+
|
|
219
|
+
- `hadolint` — запускається як нативний бінарник через `ensureTool` (PATH / кеш / авто-install). `docker run` не використовується.
|
|
220
|
+
|
|
221
|
+
## Потік виконання / Використання
|
|
222
|
+
|
|
223
|
+
1. Викликається `check(cwd)` (наприклад, з CLI-обгортки правил `docker`).
|
|
224
|
+
2. `loadCursorIgnorePaths(root)` повертає шляхи з cursor-конфігу, які треба ігнорувати при обході.
|
|
225
|
+
3. `findDockerfilePaths(root, ignorePaths)` за допомогою `walkDir` обходить дерево і збирає всі Dockerfile/Containerfile, відсортовані.
|
|
226
|
+
4. Якщо файлів немає — репорт `pass` і exit 0.
|
|
227
|
+
5. Інакше для кожного знайденого Dockerfile послідовно виконується `checkDockerfile`, який:
|
|
228
|
+
- читає вміст,
|
|
229
|
+
- підіймається до найближчого `package.json` для визначення `hasNativeAddon`,
|
|
230
|
+
- запускає шість статичних перевірок (mirror, multistage/runtime, compile **або** native-addon, non-root, nginx tag, nginx user),
|
|
231
|
+
- запускає `hadolint` через `lintDockerfileWithHadolint` і репортує результат.
|
|
232
|
+
6. Усі помилки агрегуються в репортері; підсумковий код повертає `reporter.getExitCode()` (0/1).
|
|
233
|
+
|
|
234
|
+
### Приклад (псевдокод використання)
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
import { check } from './lint.mjs'
|
|
238
|
+
|
|
239
|
+
const exitCode = await check(process.cwd())
|
|
240
|
+
process.exit(exitCode)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Точкове використання окремих функцій
|
|
244
|
+
|
|
245
|
+
- `parseFromStages(content)` — для тестів або інтроспекції stage-ів.
|
|
246
|
+
- `splitDockerfileStages(content)` — отримати масив `{ from, stageContent }` для подальшого аналізу.
|
|
247
|
+
- `getMultistageAndRuntimeHint`, `getBunCompileHint`, `getNginxAlpineSlimTagHint`, `getNonRootRuntimeHint` — окремі правила можна викликати ізольовано (наприклад у юніт-тестах у `npm/rules/docker/js/tests/`).
|
|
248
|
+
|
|
249
|
+
## Контрактні нюанси та винятки
|
|
250
|
+
|
|
251
|
+
- Якщо `FROM` у Dockerfile взагалі немає, перевірки `getMultistageAndRuntimeHint`, `getBunCompileHint`, `getNonRootRuntimeHint` повертають `null` (нема чого перевіряти).
|
|
252
|
+
- Для проєктів із нативним `.node`-аддоном (`sharp`, `@img/argon2` тощо):
|
|
253
|
+
- `bun build --compile` заборонено — спрацьовує `getNativeAddonNoCompileHint`, а не `getBunCompileHint`;
|
|
254
|
+
- фінальний `FROM` на `mirror.gcr.io/oven/bun:*` легітимний (`isAllowedFinalRuntimeImage` з `hasNativeAddon=true`).
|
|
255
|
+
- `nginx-unprivileged` без явного `USER` не вважається порушенням non-root (uid=101 за замовчуванням), але тег має бути саме `alpine-slim`.
|
|
256
|
+
- `scratch` (як точне співпадіння або з тегом) завжди дозволено як фінальний runtime.
|
|
257
|
+
- `debian` дозволено лише через `mirror.gcr.io/library/debian:<tag>` де `<tag>` містить `slim`.
|
|
258
|
+
- `posixRel` нормалізує шлях для уніфікованого виводу в репорті; якщо повертає порожній рядок — використовується `basename(abs)`.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# docker-hadolint.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `docker-hadolint.mjs` інкапсулює спільну логіку запуску статичного аналізатора `hadolint` для перевірки `Dockerfile`-ів у межах правила `docker` (див. `.cursor/rules/docker.mdc` / `n-docker`). Він є тонким адаптером поверх системного бінарника `hadolint` та надає двом викликачам — `check.mjs` (skill `check-docker`) і `../../lint/lint.mjs` (skill `run-docker`) — єдину функцію перевірки одного `Dockerfile` плюс утиліту нормалізації шляхів.
|
|
6
|
+
|
|
7
|
+
Ключові архітектурні рішення:
|
|
8
|
+
|
|
9
|
+
- `hadolint` запускається **виключно як нативний бінарник**. Будь-який Docker-fallback (наприклад, `docker run hadolint/hadolint`) свідомо прибраний, тож модуль не залежить від запущеного Docker Engine.
|
|
10
|
+
- Розв’язання шляху до бінарника делеговано в `ensureTool('hadolint')`, який реалізує ланцюжок `PATH → локальний кеш інструментів → авто-install` через `brew` / `scoop` / GitHub Release, відповідно до платформи.
|
|
11
|
+
- Якщо `ensureTool` падає (інструмент відсутній і авто-install вимкнено через `N_CURSOR_NO_AUTO_INSTALL` або фізично не вдався), модуль **не кидає виняток назовні**, а повертає структурований результат `{ ok: false, stderr: <інструкція з установки> }`. Це дозволяє викликачам однаково обробляти і реальні порушення hadolint, і відсутність інструмента.
|
|
12
|
+
- Шляхи у виводі нормалізуються через `posixRel` (відносно `root`, з прямими слешами `/`), щоб лог і повідомлення про помилки були стабільними між macOS / Linux / Windows.
|
|
13
|
+
|
|
14
|
+
## Експорти / API
|
|
15
|
+
|
|
16
|
+
Модуль є ESM (`.mjs`) і має два іменовані експорти:
|
|
17
|
+
|
|
18
|
+
| Експорт | Тип | Призначення |
|
|
19
|
+
| ------------------------------------------- | -------- | ---------------------------------------------------------------------------------- |
|
|
20
|
+
| `posixRel(root, absPath)` | function | Перетворити абсолютний шлях на відносний від `root` з прямими слешами. |
|
|
21
|
+
| `lintDockerfileWithHadolint(root, absPath)` | function | Запустити `hadolint` для одного `Dockerfile` і повернути структурований результат. |
|
|
22
|
+
|
|
23
|
+
Default-експорту немає. Глобальних побічних ефектів на рівні модуля немає — імпорт лише підтягує `ensureTool`, не виконуючи його.
|
|
24
|
+
|
|
25
|
+
## Функції
|
|
26
|
+
|
|
27
|
+
### `posixRel(root, absPath)`
|
|
28
|
+
|
|
29
|
+
Утиліта нормалізації шляхів для стабільного логування й передачі в `hadolint`.
|
|
30
|
+
|
|
31
|
+
**Сигнатура:**
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
export function posixRel(root: string, absPath: string): string
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Параметри:**
|
|
38
|
+
|
|
39
|
+
- `root` — абсолютний шлях до кореня репозиторію (або будь-якого опорного каталогу), відносно якого треба отримати шлях.
|
|
40
|
+
- `absPath` — абсолютний шлях до файлу, який треба представити відносно `root`.
|
|
41
|
+
|
|
42
|
+
**Повертає:**
|
|
43
|
+
|
|
44
|
+
- Рядок із відносним шляхом, де всі системні розділювачі (`path.sep`) замінено на прямий слеш `/`. На POSIX-системах результат фактично збігається з `path.relative`, на Windows — конвертує `\` на `/`.
|
|
45
|
+
|
|
46
|
+
**Алгоритм:**
|
|
47
|
+
|
|
48
|
+
1. Викликає `relative(root, absPath)` з `node:path` для обчислення відносного шляху.
|
|
49
|
+
2. Розбиває результат за поточним `sep` (`path.sep`) і склеює знову через `'/'`.
|
|
50
|
+
|
|
51
|
+
**Side effects:** немає; функція чиста й детермінована.
|
|
52
|
+
|
|
53
|
+
**Примітка:** функція не валідовує, чи `absPath` справді абсолютний і чи він знаходиться під `root` — у такому випадку `relative` поверне відносний шлях із `..`, що теж буде нормалізовано до прямих слешів.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
### `lintDockerfileWithHadolint(root, absPath)`
|
|
58
|
+
|
|
59
|
+
Основна точка входу модуля: виконує `hadolint` для одного `Dockerfile` і повертає уніфікований результат.
|
|
60
|
+
|
|
61
|
+
**Сигнатура:**
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
export function lintDockerfileWithHadolint(
|
|
65
|
+
root: string,
|
|
66
|
+
absPath: string
|
|
67
|
+
): {
|
|
68
|
+
ok: boolean,
|
|
69
|
+
stdout: string,
|
|
70
|
+
stderr: string,
|
|
71
|
+
via: string
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Параметри:**
|
|
76
|
+
|
|
77
|
+
- `root` — абсолютний шлях до кореня репозиторію. Використовується одночасно як `cwd` для дочірнього процесу і як база для `posixRel`.
|
|
78
|
+
- `absPath` — абсолютний шлях до конкретного `Dockerfile` (або файлу, який треба перевірити hadolint’ом).
|
|
79
|
+
|
|
80
|
+
**Повертає об’єкт із полями:**
|
|
81
|
+
|
|
82
|
+
- `ok` — `true`, якщо `hadolint` завершився з кодом виходу `0` (порушень немає). `false` — у будь-якому іншому випадку: інструмент знайшов проблеми, не запустився, або не зміг бути встановленим/знайденим.
|
|
83
|
+
- `stdout` — stdout процесу hadolint як рядок (UTF-8). При помилці отримання інструмента — порожній рядок.
|
|
84
|
+
- `stderr` — stderr процесу hadolint як рядок. Якщо інструмент не вдалося отримати, тут міститься українська інструкція з установки (`brew install hadolint` / `scoop install hadolint` / посилання на GitHub Releases) разом із повідомленням оригінальної помилки.
|
|
85
|
+
- `via` — завжди константа `'hadolint'`. Поле залишене для зворотної сумісності з API, де раніше міг бути ще варіант на кшталт `'docker'`; зараз інших значень не повертається.
|
|
86
|
+
|
|
87
|
+
**Алгоритм:**
|
|
88
|
+
|
|
89
|
+
1. Обчислює відносний шлях `rel = posixRel(root, absPath)`.
|
|
90
|
+
2. Намагається отримати шлях до бінарника через `hadolintPath = ensureTool('hadolint')`.
|
|
91
|
+
- Якщо `ensureTool` кидає виняток, функція **перехоплює** його і повертає об’єкт з `ok: false`, порожнім `stdout`, інструктивним `stderr` (текст помилки + поради з установки) і `via: 'hadolint'`. На цьому виконання завершується.
|
|
92
|
+
3. Викликає `spawnSync(hadolintPath, [rel], { cwd: root, encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 })`:
|
|
93
|
+
- `cwd: root` — щоб hadolint бачив правильну робочу директорію і `rel` коректно резолвився.
|
|
94
|
+
- `encoding: 'utf8'` — `stdout`/`stderr` одразу приходять як рядки, а не `Buffer`.
|
|
95
|
+
- `maxBuffer: 10 МіБ` — захист від великих обсягів виводу при множинних попередженнях.
|
|
96
|
+
4. Формує результат: `ok = local.status === 0`, `stdout`/`stderr` приходять з процесу (з фолбеком на порожній рядок через `??`), `via: 'hadolint'`.
|
|
97
|
+
|
|
98
|
+
**Side effects:**
|
|
99
|
+
|
|
100
|
+
- Може ініціювати **авто-install** `hadolint` через `ensureTool` (мережа, brew/scoop/завантаження GitHub Release, запис у локальний кеш інструментів), якщо інструмент відсутній і авто-install не вимкнено `N_CURSOR_NO_AUTO_INSTALL`.
|
|
101
|
+
- Синхронно запускає дочірній процес `hadolint`, що блокує цикл подій Node.js до завершення (через `spawnSync`).
|
|
102
|
+
- Не пише в stdout/stderr поточного процесу самостійно — увесь вивід hadolint повертає викликачу.
|
|
103
|
+
- Не змінює `process.cwd()` поточного процесу (тільки `cwd` дочірнього).
|
|
104
|
+
|
|
105
|
+
**Обробка помилок:**
|
|
106
|
+
|
|
107
|
+
- Виняток від `ensureTool` ловиться і конвертується у м’який `ok: false` з підказкою.
|
|
108
|
+
- Помилки запуску `spawnSync` (наприклад, права доступу) **не обгортаються окремо**: у такому разі `spawnSync` повертає об’єкт з `status: null` і `error`-полем, відповідно `ok` буде `false`, а `stdout`/`stderr` — порожніми (через `??`). Поле `error` об’єкта `spawnSync` у результат не транслюється.
|
|
109
|
+
- Ненульовий `status` від самого `hadolint` (тобто знайдені порушення) трактується як `ok: false`, але `stderr`/`stdout` несуть звіт hadolint для подальшого парсингу/виводу користувачу.
|
|
110
|
+
|
|
111
|
+
## Залежності
|
|
112
|
+
|
|
113
|
+
### Node.js (стандартні)
|
|
114
|
+
|
|
115
|
+
- `node:child_process` — імпортується `spawnSync` для синхронного запуску `hadolint`.
|
|
116
|
+
- `node:path` — імпортуються `relative` і `sep` для побудови відносного шляху з нормалізацією роздільників.
|
|
117
|
+
|
|
118
|
+
### Внутрішні (моноpепо)
|
|
119
|
+
|
|
120
|
+
- `../../../scripts/lib/ensure-tool.mjs` — функція `ensureTool(name)`, яка:
|
|
121
|
+
- повертає абсолютний шлях до бінарника;
|
|
122
|
+
- намагається знайти його у `PATH`, у локальному кеші інструментів;
|
|
123
|
+
- за потреби виконує авто-install через `brew` (macOS), `scoop` (Windows) чи GitHub Release (Linux);
|
|
124
|
+
- кидає виняток, якщо інструмент недоступний і авто-install неможливий / вимкнено `N_CURSOR_NO_AUTO_INSTALL`.
|
|
125
|
+
|
|
126
|
+
### Зовнішні (runtime)
|
|
127
|
+
|
|
128
|
+
- Бінарник `hadolint` (нативний; брати з `brew`, `scoop`, GitHub Release). **Не** використовується `docker run hadolint/hadolint` — Docker-fallback явно прибраний.
|
|
129
|
+
|
|
130
|
+
### Імпорт-споживачі (всередині правила `docker`)
|
|
131
|
+
|
|
132
|
+
- `./check.mjs` — скіл `check-docker`: разова перевірка обраних `Dockerfile`-ів.
|
|
133
|
+
- `../../lint/lint.mjs` — скіл `run-docker`: інтеграція в загальний `lint`-конвеєр.
|
|
134
|
+
|
|
135
|
+
## Потік виконання / Використання
|
|
136
|
+
|
|
137
|
+
Типовий сценарій використання модуля викликачем:
|
|
138
|
+
|
|
139
|
+
1. Викликач (наприклад, `check-docker` або `run-docker`) визначає `root` репозиторію і список абсолютних шляхів до `Dockerfile`-ів.
|
|
140
|
+
2. Для кожного шляху викликається `lintDockerfileWithHadolint(root, absPath)`.
|
|
141
|
+
3. Модуль:
|
|
142
|
+
- нормалізує шлях у POSIX-форму (`posixRel`);
|
|
143
|
+
- резолвить `hadolint` через `ensureTool` (можливо з авто-install);
|
|
144
|
+
- синхронно запускає `hadolint <rel>` у `cwd = root`;
|
|
145
|
+
- повертає `{ ok, stdout, stderr, via }`.
|
|
146
|
+
4. Викликач інтерпретує результат:
|
|
147
|
+
- `ok === true` → файл чистий, нічого не виводити (або вивести позитивний статус);
|
|
148
|
+
- `ok === false` → показати `stdout`/`stderr` користувачу, зарахувати як порушення; якщо `stderr` містить інструкцію з установки — повідомити користувача, як отримати інструмент.
|
|
149
|
+
|
|
150
|
+
### Приклад використання
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
import { lintDockerfileWithHadolint, posixRel } from './docker-hadolint.mjs'
|
|
154
|
+
|
|
155
|
+
const root = process.cwd()
|
|
156
|
+
const absPath = '/abs/path/to/repo/services/api/Dockerfile'
|
|
157
|
+
|
|
158
|
+
const result = lintDockerfileWithHadolint(root, absPath)
|
|
159
|
+
|
|
160
|
+
if (!result.ok) {
|
|
161
|
+
console.error(`hadolint failed for ${posixRel(root, absPath)} (via=${result.via})`)
|
|
162
|
+
if (result.stdout) console.error(result.stdout)
|
|
163
|
+
if (result.stderr) console.error(result.stderr)
|
|
164
|
+
process.exitCode = 1
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Поведінка за відсутності `hadolint`
|
|
169
|
+
|
|
170
|
+
- Якщо `hadolint` відсутній і `N_CURSOR_NO_AUTO_INSTALL` **не** виставлена, `ensureTool` спробує встановити бінарник автоматично (за платформою).
|
|
171
|
+
- Якщо `N_CURSOR_NO_AUTO_INSTALL` виставлена або авто-install не вдався, `lintDockerfileWithHadolint` повертає:
|
|
172
|
+
- `ok: false`,
|
|
173
|
+
- `stdout: ''`,
|
|
174
|
+
- `stderr: 'Не вдалося отримати hadolint (<повідомлення>). Встанови: brew install hadolint (macOS) / scoop install hadolint (Windows) / https://github.com/hadolint/hadolint/releases (Linux).'`,
|
|
175
|
+
- `via: 'hadolint'`.
|
|
176
|
+
- Завдяки м’якій обробці викликач може коректно показати інструкцію користувачу замість аварійного завершення.
|
|
177
|
+
|
|
178
|
+
### Особливості та обмеження
|
|
179
|
+
|
|
180
|
+
- **Синхронність**: усі виклики блокуючі (`spawnSync`). Для пакетної перевірки сотень файлів варіант — викликати модуль із зовнішнього оркестратора, який сам керує паралелізмом (з урахуванням обмежень середовища).
|
|
181
|
+
- **Один файл за виклик**: функція передає у `hadolint` рівно один аргумент `rel`. Для масової перевірки слід викликати функцію в циклі — це свідомий вибір, щоб уніфікувати результат і прив’язку до конкретного файлу.
|
|
182
|
+
- **Розмір буфера**: `maxBuffer = 10 МіБ` достатньо для звичайних `Dockerfile`-звітів; для аномально великих виводів значення доведеться змінювати на рівні форку модуля.
|
|
183
|
+
- **`via` константно `'hadolint'`**: поле залишене для майбутньої сумісності, але наразі ніколи не приймає інших значень.
|
|
184
|
+
- **Стабільність шляхів**: завдяки `posixRel` повідомлення про порушення містять однакові шляхи з прямими слешами на всіх ОС — це важливо для дифу логів у CI.
|