@nitra/cursor 12.8.6 → 12.8.8
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 +12 -0
- package/package.json +1 -1
- package/rules/abie/main.mdc +9 -5
- package/rules/abie/policy/base_deployment_preem/base_deployment_preem.mdc +22 -0
- package/rules/abie/policy/clean_merged_ignore_branches/clean_merged_ignore_branches.mdc +19 -0
- package/rules/abie/policy/health_check_policy/health_check_policy.mdc +17 -0
- package/rules/abie/policy/http_route_base/http_route_base.mdc +9 -0
- package/rules/abie/policy/package_json_shared/package_json_shared.mdc +17 -0
- package/rules/adr/js/hooks.mdc +32 -0
- package/rules/adr/js/madr_format.mdc +96 -0
- package/rules/adr/js/settings_policy.mdc +34 -0
- package/rules/adr/main.mdc +17 -95
- package/rules/adr/policy/settings_json/settings_json.mdc +7 -0
- package/rules/adr/policy/settings_local_json/settings_local_json.mdc +7 -0
- package/rules/bun/js/bunfig.mdc +12 -0
- package/rules/bun/js/layout.mdc +60 -0
- package/rules/bun/js/lint.mdc +9 -0
- package/rules/bun/js/package_json.mdc +19 -0
- package/rules/bun/main.mdc +7 -60
- package/rules/bun/policy/bunfig/bunfig.mdc +12 -0
- package/rules/bun/policy/package_json/package_json.mdc +14 -0
- package/rules/capacitor/js/ios_spm.mdc +69 -0
- package/rules/capacitor/js/version.mdc +29 -0
- package/rules/capacitor/main.mdc +6 -22
- package/rules/capacitor/policy/package_json/package_json.mdc +9 -0
- package/rules/changelog/js/agent-workflow.mdc +15 -0
- package/rules/changelog/js/changelog-format.mdc +33 -0
- package/rules/changelog/js/comparison-models.mdc +40 -0
- package/rules/changelog/main.mdc +4 -98
- package/rules/ci4/js/marksman_config.mdc +31 -0
- package/rules/ci4/js/vscode_extensions.mdc +33 -0
- package/rules/ci4/main.mdc +16 -14
- package/rules/ci4/policy/vscode_extensions/vscode_extensions.mdc +9 -0
- package/rules/docker/js/compile.mdc +44 -0
- package/rules/docker/js/hadolint.mdc +50 -0
- package/rules/docker/js/mirror.mdc +13 -0
- package/rules/docker/js/multistage.mdc +13 -0
- package/rules/docker/js/native-addon.mdc +43 -0
- package/rules/docker/js/nginx-tag.mdc +7 -0
- package/rules/docker/js/nginx-user.mdc +37 -0
- package/rules/docker/js/non-root.mdc +39 -0
- package/rules/docker/main.mdc +13 -196
- package/rules/docker/policy/lint_docker_yml/lint_docker_yml.mdc +14 -0
- package/rules/efes/main.mdc +1 -1
- package/rules/efes/policy/package_json_shared/package_json_shared.mdc +30 -0
- package/rules/ga/js/lint_toolchain.mdc +15 -0
- package/rules/ga/js/required_workflows.mdc +35 -0
- package/rules/ga/js/vscode.mdc +17 -0
- package/rules/ga/js/workflow_common.mdc +108 -0
- package/rules/ga/js/workflows.mdc +32 -0
- package/rules/ga/js/zizmor.mdc +7 -0
- package/rules/ga/main.mdc +16 -119
- package/rules/ga/policy/clean_ga_workflows/clean_ga_workflows.mdc +18 -0
- package/rules/ga/policy/clean_merged_branch/clean_merged_branch.mdc +22 -0
- package/rules/ga/policy/git_ai/git_ai.mdc +19 -0
- package/rules/ga/policy/lint_ga/lint_ga.mdc +21 -0
- package/rules/ga/policy/vscode_extensions/vscode_extensions.mdc +9 -0
- package/rules/ga/policy/vscode_settings/vscode_settings.mdc +9 -0
- package/rules/ga/policy/workflow_common/workflow_common.mdc +18 -0
- package/rules/ga/policy/zizmor_yml/zizmor_yml.mdc +9 -0
- package/rules/graphql/js/tooling.mdc +13 -0
- package/rules/graphql/js/vscode_extensions.mdc +13 -0
- package/rules/graphql/main.mdc +4 -21
- package/rules/graphql/policy/vscode_extensions/vscode_extensions.mdc +9 -0
- package/rules/hasura/js/internal_urls.mdc +27 -0
- package/rules/hasura/js/migrations.mdc +13 -0
- package/rules/hasura/js/svc_hl.mdc +17 -0
- package/rules/hasura/main.mdc +6 -30
- package/rules/hasura/policy/svc_hl/svc_hl.mdc +15 -0
- package/rules/image-avif/js/avif_generation.mdc +26 -0
- package/rules/image-avif/js/package_json_optout.mdc +21 -0
- package/rules/image-avif/main.mdc +5 -34
- package/rules/image-avif/policy/package_json/package_json.mdc +18 -0
- package/rules/image-compress/js/package_json.mdc +7 -0
- package/rules/image-compress/js/package_setup.mdc +13 -0
- package/rules/image-compress/main.mdc +4 -12
- package/rules/image-compress/policy/package_json/package_json.mdc +13 -0
- package/rules/js/docs/index.md +3 -3
- package/rules/js/js/dep-policy.mdc +17 -0
- package/rules/js/js/eslint-config.mdc +28 -0
- package/rules/js/js/extensions.mdc +8 -0
- package/rules/js/js/file-extensions.mdc +12 -0
- package/rules/js/js/for-in.mdc +26 -0
- package/rules/js/js/jscpd.mdc +42 -0
- package/rules/js/js/knip.mdc +15 -0
- package/rules/js/js/lint-js-workflow.mdc +58 -0
- package/rules/js/js/oxlintrc.mdc +20 -0
- package/rules/js/js/package-json.mdc +31 -0
- package/rules/js/js/tests.mdc +9 -0
- package/rules/js/js/utils-lib-structure.mdc +15 -0
- package/rules/js/main.mdc +19 -211
- package/rules/js/policy/jscpd/jscpd.mdc +14 -0
- package/rules/js/policy/lint_js_yml/lint_js_yml.mdc +14 -0
- package/rules/js/policy/package_json/package_json.mdc +15 -0
- package/rules/js/policy/vscode_extensions/vscode_extensions.mdc +11 -0
- package/rules/js-bun-db/js/bun-sql-migration.mdc +15 -0
- package/rules/js-bun-db/js/connection.mdc +42 -0
- package/rules/js-bun-db/js/pg-format-identifiers.mdc +102 -0
- package/rules/js-bun-db/js/pg-format-shim.mdc +99 -0
- package/rules/js-bun-db/js/pg-leftover.mdc +27 -0
- package/rules/js-bun-db/js/pg-listen-notify.mdc +51 -0
- package/rules/js-bun-db/js/query-safety.mdc +117 -0
- package/rules/js-bun-db/js/sql-array.mdc +88 -0
- package/rules/js-bun-db/js/unsafe.mdc +65 -0
- package/rules/js-bun-db/main.mdc +12 -607
- package/rules/js-bun-db/policy/package_json/package_json.mdc +17 -0
- package/rules/js-bun-redis/js/imports.mdc +47 -0
- package/rules/js-bun-redis/js/package_json.mdc +44 -0
- package/rules/js-bun-redis/main.mdc +4 -10
- package/rules/js-bun-redis/policy/package_json/package_json.mdc +11 -0
- package/rules/js-mssql/js/mssql-in-list.mdc +38 -0
- package/rules/js-mssql/js/mssql-pool.mdc +56 -0
- package/rules/js-mssql/js/mssql-query-template.mdc +33 -0
- package/rules/js-mssql/js/mssql-tvp.mdc +75 -0
- package/rules/js-mssql/js/mssql-version.mdc +7 -0
- package/rules/js-mssql/main.mdc +10 -198
- package/rules/js-mssql/policy/package_json/package_json.mdc +9 -0
- package/rules/js-run/js/check-env.mdc +35 -0
- package/rules/js-run/js/conn-aliases.mdc +109 -0
- package/rules/js-run/js/jsconfig.mdc +20 -0
- package/rules/js-run/js/otel-configmap.mdc +6 -0
- package/rules/js-run/js/pino.mdc +6 -0
- package/rules/js-run/js/project-structure.mdc +11 -0
- package/rules/js-run/js/runtime.mdc +14 -0
- package/rules/js-run/js/scope.mdc +11 -0
- package/rules/js-run/js/settimeout.mdc +11 -0
- package/rules/js-run/js/temporal.mdc +5 -0
- package/rules/js-run/main.mdc +16 -216
- package/rules/js-run/policy/configmap/configmap.mdc +31 -0
- package/rules/js-run/policy/jsconfig/jsconfig.mdc +25 -0
- package/rules/js-run/policy/package_json/package_json.mdc +38 -0
- package/rules/k8s/js/configmap.mdc +41 -0
- package/rules/k8s/js/deployment_resources.mdc +49 -0
- package/rules/k8s/js/hasura_httproute.mdc +91 -0
- package/rules/k8s/js/hpa_apiversion.mdc +27 -0
- package/rules/k8s/js/ingress_gateway.mdc +16 -0
- package/rules/k8s/js/kustomize_structure.mdc +144 -0
- package/rules/k8s/js/lint_k8s.mdc +72 -0
- package/rules/k8s/js/multidoc_yaml.mdc +5 -0
- package/rules/k8s/js/network_policy.mdc +136 -0
- package/rules/k8s/js/schema_modeline.mdc +57 -0
- package/rules/k8s/js/service.mdc +44 -0
- package/rules/k8s/js/topology_hpa_pdb.mdc +181 -0
- package/rules/k8s/main.mdc +29 -834
- package/rules/k8s/policy/base_kustomization/base_kustomization.mdc +12 -0
- package/rules/k8s/policy/base_manifest/base_manifest.mdc +14 -0
- package/rules/k8s/policy/gateway/gateway.mdc +17 -0
- package/rules/k8s/policy/hasura_configmap/hasura_configmap.mdc +20 -0
- package/rules/k8s/policy/hasura_httproute/hasura_httproute.mdc +16 -0
- package/rules/k8s/policy/hpa_pdb/hpa_pdb.mdc +23 -0
- package/rules/k8s/policy/kustomization/kustomization.mdc +20 -0
- package/rules/k8s/policy/manifest/manifest.mdc +17 -0
- package/rules/k8s/policy/network_policy/network_policy.mdc +22 -0
- package/rules/k8s/policy/svc_hl_yaml/svc_hl_yaml.mdc +13 -0
- package/rules/k8s/policy/svc_yaml/svc_yaml.mdc +12 -0
- package/rules/nginx-default-tpl/js/dockerfile.mdc +36 -0
- package/rules/nginx-default-tpl/js/http-route.mdc +41 -0
- package/rules/nginx-default-tpl/js/ini-keys.mdc +21 -0
- package/rules/nginx-default-tpl/js/template-structure.mdc +86 -0
- package/rules/nginx-default-tpl/js/vscode.mdc +37 -0
- package/rules/nginx-default-tpl/main.mdc +8 -110
- package/rules/nginx-default-tpl/policy/vscode_extensions/vscode_extensions.mdc +11 -0
- package/rules/nginx-default-tpl/policy/vscode_settings/vscode_settings.mdc +15 -0
- package/rules/npm-module/js/docs/index.md +5 -5
- package/rules/npm-module/js/docs/rule_meta.md +6 -6
- package/rules/npm-module/js/docs/skill_meta.md +8 -8
- package/rules/npm-module/js/header_doc_pointer.mdc +18 -0
- package/rules/npm-module/js/package_structure.mdc +62 -0
- package/rules/npm-module/js/rule_meta.mdc +11 -0
- package/rules/npm-module/js/skill_meta.mdc +11 -0
- package/rules/npm-module/main.mdc +10 -52
- package/rules/npm-module/policy/emit_types_config/emit_types_config.mdc +40 -0
- package/rules/npm-module/policy/npm_package_json/npm_package_json.mdc +50 -0
- package/rules/npm-module/policy/root_package_json/root_package_json.mdc +37 -0
- package/rules/php/js/lint_php_yml.mdc +12 -0
- package/rules/php/js/tooling.mdc +66 -0
- package/rules/php/main.mdc +5 -66
- package/rules/php/policy/lint_php_yml/lint_php_yml.mdc +21 -0
- package/rules/python/js/lint_python_yml.mdc +23 -0
- package/rules/python/js/pyproject_toml.mdc +32 -0
- package/rules/python/js/tooling.mdc +23 -0
- package/rules/python/main.mdc +7 -32
- package/rules/python/policy/lint_python_yml/lint_python_yml.mdc +12 -0
- package/rules/python/policy/pyproject_toml/pyproject_toml.mdc +13 -0
- package/rules/rego/js/rego-lint.mdc +31 -0
- package/rules/rego/js/vscode_extensions.mdc +11 -0
- package/rules/rego/js/vscode_settings.mdc +13 -0
- package/rules/rego/main.mdc +10 -22
- package/rules/rego/policy/vscode_extensions/vscode_extensions.mdc +11 -0
- package/rules/rego/policy/vscode_settings/vscode_settings.mdc +19 -0
- package/rules/rust/js/coverage.mdc +28 -0
- package/rules/rust/js/lint.mdc +22 -0
- package/rules/rust/js/tauri_composition.mdc +8 -0
- package/rules/rust/js/vscode_extensions.mdc +12 -0
- package/rules/rust/main.mdc +8 -38
- package/rules/rust/policy/lint_rust_yml/lint_rust_yml.mdc +12 -0
- package/rules/rust/policy/vscode_extensions/vscode_extensions.mdc +9 -0
- package/rules/security/js/rego_policies.mdc +15 -0
- package/rules/security/js/sample_secret.mdc +19 -0
- package/rules/security/js/trufflehog.mdc +21 -0
- package/rules/security/main.mdc +7 -34
- package/rules/security/policy/lint_security_yml/lint_security_yml.mdc +7 -0
- package/rules/security/policy/package_json/package_json.mdc +7 -0
- package/rules/style/js/admin-table.mdc +88 -0
- package/rules/style/js/colors.mdc +21 -0
- package/rules/style/js/gap.mdc +22 -0
- package/rules/style/js/quasar-fixes.mdc +32 -0
- package/rules/style/js/quasar.mdc +7 -0
- package/rules/style/js/tooling.mdc +85 -0
- package/rules/style/main.mdc +12 -251
- package/rules/style/policy/lint_style_yml/lint_style_yml.mdc +13 -0
- package/rules/style/policy/package_json/package_json.mdc +18 -0
- package/rules/style/policy/vscode_extensions/vscode_extensions.mdc +13 -0
- package/rules/style/policy/vscode_settings/vscode_settings.mdc +19 -0
- package/rules/tauri/js/cargo_mutants_config.mdc +39 -0
- package/rules/tauri/js/tool_surface.mdc +21 -0
- package/rules/tauri/js/tooling.mdc +25 -0
- package/rules/tauri/main.mdc +6 -78
- package/rules/tauri/policy/vscode_extensions/vscode_extensions.mdc +21 -0
- package/rules/test/js/cargo_mutants_config.mdc +18 -0
- package/rules/test/js/docs/index.md +7 -7
- package/rules/test/js/location.mdc +52 -0
- package/rules/test/js/no-console-store-restore.mdc +11 -0
- package/rules/test/js/no-process-chdir.mdc +15 -0
- package/rules/test/js/no-relative-fs-path.mdc +22 -0
- package/rules/test/js/sandbox-aware-test.mdc +28 -0
- package/rules/test/js/stryker_config.mdc +26 -0
- package/rules/test/js/vitest-config-pool-forks.mdc +33 -0
- package/rules/test/main.mdc +16 -184
- package/rules/test/policy/package_json/package_json.mdc +16 -0
- package/rules/text/js/ci-lint-text.mdc +15 -0
- package/rules/text/js/cspell.mdc +81 -0
- package/rules/text/js/dotenv-linter.mdc +16 -0
- package/rules/text/js/forbidden-prettier.mdc +13 -0
- package/rules/text/js/markdownlint.mdc +25 -0
- package/rules/text/js/oxfmt.mdc +35 -0
- package/rules/text/js/package-json.mdc +26 -0
- package/rules/text/js/shellcheck.mdc +18 -0
- package/rules/text/js/v8r.mdc +23 -0
- package/rules/text/js/vscode.mdc +86 -0
- package/rules/text/main.mdc +20 -231
- package/rules/text/policy/cspell/cspell.mdc +34 -0
- package/rules/text/policy/lint_text/lint_text.mdc +19 -0
- package/rules/text/policy/markdownlint/markdownlint.mdc +38 -0
- package/rules/text/policy/oxfmtrc/oxfmtrc.mdc +11 -0
- package/rules/text/policy/package_json/package_json.mdc +33 -0
- package/rules/text/policy/vscode_extensions/vscode_extensions.mdc +13 -0
- package/rules/text/policy/vscode_settings/vscode_settings.mdc +13 -0
- package/rules/vue/js/composition-api.mdc +82 -0
- package/rules/vue/js/nheader-layout.mdc +171 -0
- package/rules/vue/js/node-imports.mdc +25 -0
- package/rules/vue/js/quasar-ui.mdc +32 -0
- package/rules/vue/js/structure.mdc +101 -0
- package/rules/vue/js/testing.mdc +32 -0
- package/rules/vue/js/tfm-translations.mdc +26 -0
- package/rules/vue/js/vite-config.mdc +126 -0
- package/rules/vue/js/vite-env.mdc +55 -0
- package/rules/vue/js/vue-imports.mdc +25 -0
- package/rules/vue/main.mdc +15 -641
- package/rules/vue/policy/package_json/package_json.mdc +30 -0
- package/scripts/docs/index.md +16 -16
- package/scripts/lib/docs/index.md +36 -36
- package/scripts/utils/docs/index.md +14 -14
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
## Компіляція bun-проєкту в бінарник
|
|
2
|
+
|
|
3
|
+
Якщо проект має `bun install` крок, та не є фронтенд проектом (тобто не має `bun build` крок без `--compile`), **і не має нативного `.node`-аддона з динамічним завантаженням** (див. `native-addon.mdc`), то потрібно щоб була компіляція коду, і далі у фінальному образі був тільки бінарник. Цей образ не містить компілятора, npm, Bun — тільки runtime libs.
|
|
4
|
+
|
|
5
|
+
Тригер перевірки:
|
|
6
|
+
- у Dockerfile є крок `bun install` (або `bun i`);
|
|
7
|
+
- фінальний FROM — `mirror.gcr.io/library/alpine:*` (тобто не nginx/openresty frontend).
|
|
8
|
+
|
|
9
|
+
Очікування:
|
|
10
|
+
- у build stage є `bun build --compile`;
|
|
11
|
+
- у фінальному stage немає викликів `bun` (залишків build tooling).
|
|
12
|
+
|
|
13
|
+
### Канон — компільований бінарник на alpine
|
|
14
|
+
|
|
15
|
+
```dockerfile
|
|
16
|
+
FROM mirror.gcr.io/oven/bun:alpine AS build-env
|
|
17
|
+
|
|
18
|
+
WORKDIR /app
|
|
19
|
+
|
|
20
|
+
ENV NODE_ENV=production
|
|
21
|
+
|
|
22
|
+
COPY package.json .
|
|
23
|
+
COPY bunfig.toml .
|
|
24
|
+
|
|
25
|
+
RUN bun install --production
|
|
26
|
+
|
|
27
|
+
COPY ./src ./src
|
|
28
|
+
|
|
29
|
+
# Компілюємо в бінарник
|
|
30
|
+
RUN bun build --compile --outfile app ./src/index.js
|
|
31
|
+
|
|
32
|
+
FROM mirror.gcr.io/library/alpine:latest
|
|
33
|
+
|
|
34
|
+
# (libstdc++ libgcc) для Bun runtime, (tzdata) для часового поясу
|
|
35
|
+
RUN apk add --no-cache libstdc++ libgcc tzdata
|
|
36
|
+
|
|
37
|
+
WORKDIR /app
|
|
38
|
+
|
|
39
|
+
COPY --from=build-env /app/app ./app
|
|
40
|
+
|
|
41
|
+
CMD ["./app"]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Перевіряє `getBunCompileHint` у **`npm/rules/docker/js/lint.mjs`**.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
## lint-docker: hadolint і CI-workflow
|
|
2
|
+
|
|
3
|
+
### Область lint-docker
|
|
4
|
+
|
|
5
|
+
CLI **`hadolint`** приймає лише **явні шляхи** (`[DOCKERFILE...]` у **`hadolint --help`**); обхід репозиторію робить правило **`n-cursor lint docker`** (реалізація — **`npm/rules/docker/js/lint.mjs`**).
|
|
6
|
+
|
|
7
|
+
**Область lint-docker (вужча, ніж `check docker`):** лише файли з іменем **`Dockerfile`** та **`*.Dockerfile`** (суфікс **`.dockerfile`** без урахування регістру, наприклад **`api.Dockerfile`**). Файли **`Dockerfile.prod`**, **`Containerfile`** тощо **не** входять у **`lint-docker`**; їх ловить **`check docker`** (`rules/docker/fix.mjs`).
|
|
8
|
+
|
|
9
|
+
Обхід: **`walkDir`** з тими самими пропусками каталогів, що й **`rules/docker/fix.mjs`**. Виклик **`hadolint`** як **нативного бінарника** через **`ensureTool`** (PATH → кеш → авто-install brew/scoop/GitHub Release; **без** `docker run`) — спільна логіка **`npm/rules/docker/lib/docker-hadolint.mjs`**.
|
|
10
|
+
|
|
11
|
+
### GitHub Actions workflow
|
|
12
|
+
|
|
13
|
+
Додай workflow **`.github/workflows/lint-docker.yml`** (гілки **`dev`** і **`main`**, лише **`.yml`**, узгоджено з **`ga.mdc`**):
|
|
14
|
+
|
|
15
|
+
- Канон: [lint-docker.yml.snippet.yml](./policy/lint_docker_yml/template/lint-docker.yml.snippet.yml)
|
|
16
|
+
|
|
17
|
+
Локально hadolint авто-встановлюється через **`ensureTool`** (latest, без піну версії). У CI встанови його кроком із workflow-сніпета (curl-download бінарника — без `docker run`).
|
|
18
|
+
|
|
19
|
+
### Конфігурація hadolint
|
|
20
|
+
|
|
21
|
+
Кореневий **`.hadolint.yaml`**: вимкнення правил, trusted registries — [документація](https://github.com/hadolint/hadolint#configure). Щоб не додавати **`# hadolint ignore=DL3007`** у кожному **`FROM`** з **`:latest`**, у корені репозиторію задати глобально:
|
|
22
|
+
|
|
23
|
+
```yaml title=".hadolint.yaml"
|
|
24
|
+
ignored:
|
|
25
|
+
- DL3007
|
|
26
|
+
- DL3008
|
|
27
|
+
- DL3018
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Де DL3007 — «Не використовуй тег latest у FROM»
|
|
31
|
+
Де DL3018 — «Піни версії пакетів у apk add»
|
|
32
|
+
|
|
33
|
+
### Запуск
|
|
34
|
+
|
|
35
|
+
1. **`n-cursor lint docker`** — **`js/lint.mjs`**: **`Dockerfile`** та **`*.Dockerfile`** (lint-docker); у CI використовуй **`n-cursor lint docker --read-only`** і встанови hadolint (приклад у workflow).
|
|
36
|
+
2. **`npx @nitra/cursor fix docker`** — **`rules/docker/fix.mjs`**, виклик hadolint як у **`docker-hadolint.mjs`** (нативний бінарник через **`ensureTool`**; **без** `docker run`).
|
|
37
|
+
|
|
38
|
+
Окремий `package.json`-скрипт `lint-docker` не потрібен і не перевіряється — єдина точка входу для правила: **`n-cursor lint docker`**.
|
|
39
|
+
|
|
40
|
+
Якщо немає файлів у межах відповідного набору (**`lint-docker`** або **`check docker`**) — перевірка пропускається (exit 0).
|
|
41
|
+
|
|
42
|
+
Винятки: **`# hadolint ignore=DL3008`** (або інший код) у Dockerfile або **`ignored`** у **`.hadolint.yaml`** (наприклад **DL3007** для **`:latest`** — div. вище).
|
|
43
|
+
|
|
44
|
+
### Rego-перевірка (conftest)
|
|
45
|
+
|
|
46
|
+
Пакет `docker.lint_docker_yml` перевіряє `.github/workflows/lint-docker.yml` на відповідність канонічному сніпету:
|
|
47
|
+
|
|
48
|
+
- `on.push.paths` містить обов'язкові шляхи з template;
|
|
49
|
+
- присутні всі `uses:` кроки з template;
|
|
50
|
+
- всі `run:` підрядки з template є у workflow.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
## GCR-дзеркало замість Docker Hub
|
|
2
|
+
|
|
3
|
+
Для образів з Docker Hub — **`oven/bun`**, **`alpine`**, **`nginx`**, **`nginxinc/nginx-unprivileged`**, **`node`** — у **`FROM`** треба вказувати дзеркало GCR, а не pull напряму з Hub:
|
|
4
|
+
|
|
5
|
+
| Docker Hub | GCR-дзеркало |
|
|
6
|
+
|---|---|
|
|
7
|
+
| `oven/bun` | `mirror.gcr.io/oven/bun` |
|
|
8
|
+
| `alpine` | `mirror.gcr.io/library/alpine` |
|
|
9
|
+
| `nginx` | `mirror.gcr.io/library/nginx` |
|
|
10
|
+
| `node` | `mirror.gcr.io/library/node` |
|
|
11
|
+
| `nginxinc/nginx-unprivileged` | `mirror.gcr.io/nginxinc/nginx-unprivileged` |
|
|
12
|
+
|
|
13
|
+
Перевіряє **`npm/rules/docker/lib/docker-mirror.mjs`** (через `getMirrorGcrHint`); точка входу обходу — **`npm/rules/docker/fix.mjs`** та **`npm/rules/docker/js/lint.mjs`**.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
## Multistage build і дозволені runtime-образи
|
|
2
|
+
|
|
3
|
+
Dockerfile/Containerfile **має бути multistage build**: окремий build stage (залежності/компіляція) і окремий runtime stage. У фінальному stage дозволені лише мінімальні базові образи:
|
|
4
|
+
|
|
5
|
+
- **backend (типово)**: `mirror.gcr.io/library/alpine:*`
|
|
6
|
+
- **ультра-легкі (glibc / одна статична збірка)**: `scratch` — тільки як `FROM scratch` (офіційний порожній базовий шар), коли весь **runtime** уже в `COPY --from=…`
|
|
7
|
+
- **glibc, Debian (slim)**: `mirror.gcr.io/library/debian:*` **лише** з тегом, у якому є `slim` (наприклад `bookworm-slim`, `trixie-slim`), а не `bookworm` без `slim`. Debian-slim виправданий **лише** коли потрібен саме glibc-рантайм (нативні glibc-залежності, prebuilds без musl-варіанта). **Non-root сам по собі не є підставою** переходити сюди: `mirror.gcr.io/oven/bun:alpine` уже має користувача `bun` (uid/gid 1000), тож `USER bun` дає non-root **без зміни бази**. Перехід Alpine→Debian заради лише non-root — **антипатерн** (більший образ + зайва musl→glibc міграція bun-бінарника)
|
|
8
|
+
- **виняток (інтерпретовані стеки)**: `mirror.gcr.io/library/php:*` або `mirror.gcr.io/library/python:*` — якщо сервіс має крутитися в офіційному runtime PHP чи Python, а не як один бінарник на Alpine; інакше лишай **alpine** у фінальному stage
|
|
9
|
+
- **frontend**: `mirror.gcr.io/nginxinc/nginx-unprivileged:*` або `mirror.gcr.io/openresty/openresty:*`
|
|
10
|
+
|
|
11
|
+
Це стримує зайвий build tooling (Bun, **node_modules** зі збірки) у фінальному образі; для **alpine** / **nginx** / **openresty** у **runtime** лишаються лише відповідні вимоги, для **php** / **python** (виняток) — цільовий інтерпретований **stack**; **scratch** і **debian** з тегом **`*slim*`** — коли glibc і мінімальне оточення Debian важливіші за musl в **alpine**.
|
|
12
|
+
|
|
13
|
+
Перевіряє `getMultistageAndRuntimeHint` у **`npm/rules/docker/js/lint.mjs`**.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
## Виняток: нативний `.node`-аддон (sharp / @img/* / argon2)
|
|
2
|
+
|
|
3
|
+
Якщо в `package.json#dependencies` є нативний `.node`-аддон, який вантажиться через **динамічний `require`** — передусім **`sharp`**; той самий клас — **`@img/*`**, **`argon2`** — його **не можна** пакувати через `bun build --compile`.
|
|
4
|
+
|
|
5
|
+
`bun build --compile` не трейсить `require(\`@img/sharp-${platform}/sharp.node\`)` і не вшиває нативний біндинг → компільований бінарник падає в рантаймі: `Could not load the "sharp" module using the linuxmusl-arm64 runtime`. Доведено реальними docker-збірками (bun 1.3.14, sharp 0.34.5); відтворюється і на darwin-arm64 (тобто не musl/glibc-залежне). `apk add vips` **НЕ лікує** — він дає системний libvips, а бракує саме `sharp.node`.
|
|
6
|
+
|
|
7
|
+
Канон — ship `node_modules` і запускати через `bun <entry>` на базі `mirror.gcr.io/oven/bun:alpine` (це легітимний виняток до правила «лише alpine/scratch у фінальному stage» — тут потрібен саме bun-рантайм). База `oven/bun` уже має non-root користувача `bun` (uid/gid 1000). Entry бери з наявного `--outfile`-таргета / `package.json#main` / `scripts.start`; якщо не визначити — лиши TODO-маркер, не вгадуй.
|
|
8
|
+
|
|
9
|
+
Список нативних аддонів — розширювана константа `NATIVE_ADDON_PACKAGES` / `NATIVE_ADDON_SCOPES` у **`npm/rules/docker/lib/docker-native-addon.mjs`** (підключено в **`npm/rules/docker/js/lint.mjs`**, точка входу обходу — **`npm/rules/docker/fix.mjs`**).
|
|
10
|
+
|
|
11
|
+
### Антипатерн (це правило ловить)
|
|
12
|
+
|
|
13
|
+
```dockerfile
|
|
14
|
+
RUN bun build --compile --outfile app ./src/index.js # ← з sharp у deps
|
|
15
|
+
FROM mirror.gcr.io/library/alpine:latest
|
|
16
|
+
RUN apk add --no-cache ... vips # системний vips не рятує
|
|
17
|
+
COPY --from=build-env --chown=app:app /app/app ./app
|
|
18
|
+
USER app
|
|
19
|
+
CMD ["./app"]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Канон (привести до цього)
|
|
23
|
+
|
|
24
|
+
```dockerfile
|
|
25
|
+
FROM mirror.gcr.io/oven/bun:alpine AS build-env
|
|
26
|
+
WORKDIR /app
|
|
27
|
+
ENV NODE_ENV=production
|
|
28
|
+
COPY package.json .
|
|
29
|
+
RUN bun install --production
|
|
30
|
+
COPY ./src ./src
|
|
31
|
+
|
|
32
|
+
FROM mirror.gcr.io/oven/bun:alpine
|
|
33
|
+
RUN apk add --no-cache tzdata
|
|
34
|
+
WORKDIR /app
|
|
35
|
+
# база oven/bun має non-root користувача bun (uid/gid 1000)
|
|
36
|
+
COPY --from=build-env --chown=bun:bun /app/node_modules ./node_modules
|
|
37
|
+
COPY --from=build-env --chown=bun:bun /app/src ./src
|
|
38
|
+
COPY --from=build-env --chown=bun:bun /app/package.json ./package.json
|
|
39
|
+
USER bun
|
|
40
|
+
CMD ["bun", "src/index.js"]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Для проєктів **без** нативних аддонів standalone-бінарник на alpine лишається каноном (див. `compile.mdc`).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
## Мінімальний тег для nginx-unprivileged
|
|
2
|
+
|
|
3
|
+
Якщо використовується nginx, то **використовуй** `mirror.gcr.io/nginxinc/nginx-unprivileged:alpine-slim` (і не `latest` / `alpine`).
|
|
4
|
+
|
|
5
|
+
Тег `alpine-slim` є обов'язковим для всіх `FROM` з образом `nginxinc/nginx-unprivileged` — будь-який інший тег (включаючи без тега) прапорцюється як порушення.
|
|
6
|
+
|
|
7
|
+
Перевіряє `getNginxAlpineSlimTagHint` у **`npm/rules/docker/js/lint.mjs`**.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
## nginx-unprivileged — без USER, із --chown
|
|
2
|
+
|
|
3
|
+
Окрема гілка для фронтенду на базі **`nginxinc/nginx-unprivileged`** (будь-який тег, з/без `mirror.gcr.io/`-префікса). Цей образ **уже** оголошує `USER 101` і `EXPOSE 8080`, тож у фінальному stage **не потрібні** жодні явні `USER`-інструкції:
|
|
4
|
+
|
|
5
|
+
- **жодного `USER root` / `USER 0`** для білд-кроків: він перезатирає успадкований `USER 101`, і якщо потім не повернути non-root — фінальний образ лишається root, а k8s із `runAsNonRoot: true` падає з `CreateContainerConfigError`;
|
|
6
|
+
- **жодного switch-back** `USER 101` / `USER nginx` наприкінці stage — це лише симптом зайвого `USER root` на початку (повертати треба саме **числовим** UID, бо kubelet не підтверджує non-root за іменем `nginx`). Канон — взагалі не виходити з-під дефолтного 101;
|
|
7
|
+
- **`COPY`/`ADD` лише з `--chown`** (канон — `--chown=nginx:nginx`): без нього файли копіюються власником root і дефолтний non-root користувач (uid=101) не зможе читати статику.
|
|
8
|
+
|
|
9
|
+
Build-stage не чіпаємо — там root і tooling норма. Перевіряє **`npm/rules/docker/lib/docker-nginx-user.mjs`** (підключено в **`npm/rules/docker/js/lint.mjs`**, точка входу обходу — **`npm/rules/docker/fix.mjs`**).
|
|
10
|
+
|
|
11
|
+
### Антипатерн (це правило ловить)
|
|
12
|
+
|
|
13
|
+
```dockerfile
|
|
14
|
+
FROM mirror.gcr.io/nginxinc/nginx-unprivileged:alpine-slim
|
|
15
|
+
USER root
|
|
16
|
+
COPY ./k8s/nginx.conf /etc/nginx/conf.d/default.conf
|
|
17
|
+
COPY --from=build /app/dist ./
|
|
18
|
+
RUN find ./ -type f -name "*.js" -exec gzip -k {} \;
|
|
19
|
+
USER 101 # повернення назад — симптом того, що був зайвий USER root
|
|
20
|
+
EXPOSE 8080
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Канон (привести до цього)
|
|
24
|
+
|
|
25
|
+
```dockerfile
|
|
26
|
+
FROM mirror.gcr.io/nginxinc/nginx-unprivileged:alpine-slim
|
|
27
|
+
|
|
28
|
+
COPY --chown=nginx:nginx ./k8s/nginx.conf /etc/nginx/conf.d/default.conf
|
|
29
|
+
|
|
30
|
+
WORKDIR /usr/share/nginx/html
|
|
31
|
+
|
|
32
|
+
COPY --from=build --chown=nginx:nginx /app/dist ./
|
|
33
|
+
|
|
34
|
+
RUN find ./ -type f -name "*.js" -exec gzip -k {} \;
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
(без жодного `USER`, gzip під дефолтним користувачем 101; `EXPOSE 8080` теж зайвий — база вже його оголошує)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
## Non-root принцип у фінальному stage
|
|
2
|
+
|
|
3
|
+
Для всіх образів потрібно щоб використовувся non-root принцип. **Спосіб** досягнення non-root залежить від **бази**, а не від зміни ОС — змінювати дистрибутив (Alpine→Debian) заради лише non-root **не треба**. Два шляхи:
|
|
4
|
+
|
|
5
|
+
- **standalone-бінарник на `alpine:latest`** (секція компіляції) — у `alpine` немає готового non-root користувача, тож його створюємо явно: `addgroup -g 1000 app && adduser -D -u 1000 -G app app` + `COPY --chown=app:app` + `USER app` (приклад нижче);
|
|
6
|
+
- **ship `node_modules` на `mirror.gcr.io/oven/bun:alpine`** (виняток: нативний `.node`-аддон) — користувач `bun` (uid/gid 1000) **уже в базі**, тож достатньо `COPY --chown=bun:bun …` + `USER bun`; базу на Debian-slim міняти **не треба** — це той самий антипатерн, що й у переліку фінальних образів.
|
|
7
|
+
|
|
8
|
+
### Приклад для standalone-бінарника на alpine
|
|
9
|
+
|
|
10
|
+
```dockerfile
|
|
11
|
+
# Stage 1
|
|
12
|
+
FROM oven/bun:alpine AS build-env
|
|
13
|
+
WORKDIR /app
|
|
14
|
+
COPY package.json bunfig.toml .
|
|
15
|
+
RUN bun install --production
|
|
16
|
+
COPY ./src ./src
|
|
17
|
+
RUN bun build --compile --outfile app ./src/index.js
|
|
18
|
+
|
|
19
|
+
# Stage 2
|
|
20
|
+
FROM alpine:latest
|
|
21
|
+
RUN apk add --no-cache libstdc++ libgcc tzdata
|
|
22
|
+
|
|
23
|
+
# Додати non-root user
|
|
24
|
+
RUN addgroup -g 1000 app && adduser -D -u 1000 -G app app
|
|
25
|
+
|
|
26
|
+
WORKDIR /app
|
|
27
|
+
|
|
28
|
+
# Змінити власника файлу
|
|
29
|
+
COPY --from=build-env --chown=app:app /app/app ./app
|
|
30
|
+
|
|
31
|
+
# Запускати як non-root
|
|
32
|
+
USER app
|
|
33
|
+
|
|
34
|
+
CMD ["./app"]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Перевіряє `getNonRootRuntimeHint` у **`npm/rules/docker/js/lint.mjs`**.
|
|
38
|
+
|
|
39
|
+
> **Примітка:** для `nginxinc/nginx-unprivileged` канон відрізняється — дивись `nginx-user.mdc`.
|
package/rules/docker/main.mdc
CHANGED
|
@@ -7,211 +7,28 @@ alwaysApply: false
|
|
|
7
7
|
|
|
8
8
|
# Docker — hadolint
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Правило перевіряє Dockerfile / Containerfile: GCR-дзеркало замість Docker Hub, multistage build з дозволеними runtime-образами, компіляцію bun-проєктів у бінарник, виняток для нативних `.node`-аддонів, non-root у фінальному stage, nginx-специфічні вимоги та hadolint CI-workflow.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
## Активація
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
- **ультра-легкі (glibc / одна статична збірка)**: `scratch` — тільки як `FROM scratch` (офіційний порожній базовий шар), коли весь **runtime** уже в `COPY --from=…`
|
|
16
|
-
- **glibc, Debian (slim)**: `mirror.gcr.io/library/debian:*` **лише** з тегом, у якому є `slim` (наприклад `bookworm-slim`, `trixie-slim`), а не `bookworm` без `slim`. Debian-slim виправданий **лише** коли потрібен саме glibc-рантайм (нативні glibc-залежності, prebuilds без musl-варіанта). **Non-root сам по собі не є підставою** переходити сюди: `mirror.gcr.io/oven/bun:alpine` уже має користувача `bun` (uid/gid 1000), тож `USER bun` дає non-root **без зміни бази**. Перехід Alpine→Debian заради лише non-root — **антипатерн** (більший образ + зайва musl→glibc міграція bun-бінарника)
|
|
17
|
-
- **виняток (інтерпретовані стеки)**: `mirror.gcr.io/library/php:*` або `mirror.gcr.io/library/python:*` — якщо сервіс має крутитися в офіційному runtime PHP чи Python, а не як один бінарник на Alpine; інакше лишай **alpine** у фінальному stage
|
|
18
|
-
- **frontend**: `mirror.gcr.io/nginxinc/nginx-unprivileged:*` або `mirror.gcr.io/openresty/openresty:*`
|
|
14
|
+
Спрацьовує на всі файли `Dockerfile*`; `check docker` (`fix.mjs`) обробляє також `Containerfile` та `Containerfile.*`.
|
|
19
15
|
|
|
20
|
-
|
|
16
|
+
## Швидкий gate через conftest
|
|
21
17
|
|
|
22
|
-
|
|
18
|
+
[docker-lint_docker_yml](./policy/lint_docker_yml/lint_docker_yml.mdc)
|
|
23
19
|
|
|
24
|
-
|
|
20
|
+
[docker-mirror](./js/mirror.mdc)
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
[docker-multistage](./js/multistage.mdc)
|
|
27
23
|
|
|
28
|
-
|
|
24
|
+
[docker-compile](./js/compile.mdc)
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
FROM mirror.gcr.io/oven/bun:alpine AS build-env
|
|
26
|
+
[docker-native-addon](./js/native-addon.mdc)
|
|
32
27
|
|
|
33
|
-
|
|
28
|
+
[docker-non-root](./js/non-root.mdc)
|
|
34
29
|
|
|
35
|
-
|
|
30
|
+
[docker-nginx-tag](./js/nginx-tag.mdc)
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
COPY bunfig.toml .
|
|
32
|
+
[docker-nginx-user](./js/nginx-user.mdc)
|
|
39
33
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
COPY ./src ./src
|
|
43
|
-
|
|
44
|
-
# Компілюємо в бінарник
|
|
45
|
-
RUN bun build --compile --outfile app ./src/index.js
|
|
46
|
-
|
|
47
|
-
FROM mirror.gcr.io/library/alpine:latest
|
|
48
|
-
|
|
49
|
-
# (libstdc++ libgcc) для Bun runtime, (tzdata) для часового поясу
|
|
50
|
-
RUN apk add --no-cache libstdc++ libgcc tzdata
|
|
51
|
-
|
|
52
|
-
WORKDIR /app
|
|
53
|
-
|
|
54
|
-
COPY --from=build-env /app/app ./app
|
|
55
|
-
|
|
56
|
-
CMD ["./app"]
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### виняток: нативний `.node`-аддон (sharp / @img/* / argon2)
|
|
60
|
-
|
|
61
|
-
Якщо в `package.json#dependencies` є нативний `.node`-аддон, який вантажиться через **динамічний `require`** — передусім **`sharp`**; той самий клас — **`@img/*`**, **`argon2`** — його **не можна** пакувати через `bun build --compile`.
|
|
62
|
-
|
|
63
|
-
`bun build --compile` не трейсить `require(\`@img/sharp-${platform}/sharp.node\`)` і не вшиває нативний біндинг → компільований бінарник падає в рантаймі: `Could not load the "sharp" module using the linuxmusl-arm64 runtime`. Доведено реальними docker-збірками (bun 1.3.14, sharp 0.34.5); відтворюється і на darwin-arm64 (тобто не musl/glibc-залежне). `apk add vips` **НЕ лікує** — він дає системний libvips, а бракує саме `sharp.node`.
|
|
64
|
-
|
|
65
|
-
Канон — ship `node_modules` і запускати через `bun <entry>` на базі `mirror.gcr.io/oven/bun:alpine` (це легітимний виняток до правила «лише alpine/scratch у фінальному stage» — тут потрібен саме bun-рантайм). База `oven/bun` уже має non-root користувача `bun` (uid/gid 1000). Entry бери з наявного `--outfile`-таргета / `package.json#main` / `scripts.start`; якщо не визначити — лиши TODO-маркер, не вгадуй.
|
|
66
|
-
|
|
67
|
-
Перевіряє **`npm/rules/docker/lib/docker-native-addon.mjs`** (підключено в **`npm/rules/docker/js/lint.mjs`**, точка входу обходу — **`npm/rules/docker/fix.mjs`**). Список нативних аддонів — розширювана константа `NATIVE_ADDON_PACKAGES` / `NATIVE_ADDON_SCOPES` у чек-модулі.
|
|
68
|
-
|
|
69
|
-
#### Антипатерн (це правило ловить)
|
|
70
|
-
|
|
71
|
-
```dockerfile
|
|
72
|
-
RUN bun build --compile --outfile app ./src/index.js # ← з sharp у deps
|
|
73
|
-
FROM mirror.gcr.io/library/alpine:latest
|
|
74
|
-
RUN apk add --no-cache ... vips # системний vips не рятує
|
|
75
|
-
COPY --from=build-env --chown=app:app /app/app ./app
|
|
76
|
-
USER app
|
|
77
|
-
CMD ["./app"]
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
#### Канон (привести до цього)
|
|
81
|
-
|
|
82
|
-
```dockerfile
|
|
83
|
-
FROM mirror.gcr.io/oven/bun:alpine AS build-env
|
|
84
|
-
WORKDIR /app
|
|
85
|
-
ENV NODE_ENV=production
|
|
86
|
-
COPY package.json .
|
|
87
|
-
RUN bun install --production
|
|
88
|
-
COPY ./src ./src
|
|
89
|
-
|
|
90
|
-
FROM mirror.gcr.io/oven/bun:alpine
|
|
91
|
-
RUN apk add --no-cache tzdata
|
|
92
|
-
WORKDIR /app
|
|
93
|
-
# база oven/bun має non-root користувача bun (uid/gid 1000)
|
|
94
|
-
COPY --from=build-env --chown=bun:bun /app/node_modules ./node_modules
|
|
95
|
-
COPY --from=build-env --chown=bun:bun /app/src ./src
|
|
96
|
-
COPY --from=build-env --chown=bun:bun /app/package.json ./package.json
|
|
97
|
-
USER bun
|
|
98
|
-
CMD ["bun", "src/index.js"]
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Для проєктів **без** нативних аддонів standalone-бінарник на alpine лишається каноном (секція «компіляція» вище).
|
|
102
|
-
|
|
103
|
-
## не превілейований образ
|
|
104
|
-
|
|
105
|
-
Для всіх образів потрібно щоб використовувся non-root принцип. **Спосіб** досягнення non-root залежить від **бази**, а не від зміни ОС — змінювати дистрибутив (Alpine→Debian) заради лише non-root **не треба**. Два шляхи:
|
|
106
|
-
|
|
107
|
-
- **standalone-бінарник на `alpine:latest`** (секція «компіляція») — у `alpine` немає готового non-root користувача, тож його створюємо явно: `addgroup -g 1000 app && adduser -D -u 1000 -G app app` + `COPY --chown=app:app` + `USER app` (приклад нижче);
|
|
108
|
-
- **ship `node_modules` на `mirror.gcr.io/oven/bun:alpine`** (секція «виняток: нативний `.node`-аддон») — користувач `bun` (uid/gid 1000) **уже в базі**, тож достатньо `COPY --chown=bun:bun …` + `USER bun`; базу на Debian-slim міняти **не треба** — це той самий антипатерн, що й у переліку фінальних образів вище.
|
|
109
|
-
|
|
110
|
-
Приклад для standalone-бінарника на alpine:
|
|
111
|
-
|
|
112
|
-
```dockerfile
|
|
113
|
-
# Stage 1
|
|
114
|
-
FROM oven/bun:alpine AS build-env
|
|
115
|
-
WORKDIR /app
|
|
116
|
-
COPY package.json bunfig.toml .
|
|
117
|
-
RUN bun install --production
|
|
118
|
-
COPY ./src ./src
|
|
119
|
-
RUN bun build --compile --outfile app ./src/index.js
|
|
120
|
-
|
|
121
|
-
# Stage 2
|
|
122
|
-
FROM alpine:latest
|
|
123
|
-
RUN apk add --no-cache libstdc++ libgcc tzdata
|
|
124
|
-
|
|
125
|
-
# ✅ Додати non-root user
|
|
126
|
-
RUN addgroup -g 1000 app && adduser -D -u 1000 -G app app
|
|
127
|
-
|
|
128
|
-
WORKDIR /app
|
|
129
|
-
|
|
130
|
-
# ✅ Змінити власника файлу
|
|
131
|
-
COPY --from=build-env --chown=app:app /app/app ./app
|
|
132
|
-
|
|
133
|
-
# ✅ Запускати як non-root
|
|
134
|
-
USER app
|
|
135
|
-
|
|
136
|
-
CMD ["./app"]
|
|
137
|
-
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### nginx-unprivileged — без USER, із --chown
|
|
141
|
-
|
|
142
|
-
Окрема гілка для фронтенду на базі **`nginxinc/nginx-unprivileged`** (будь-який тег, з/без `mirror.gcr.io/`-префікса). Цей образ **уже** оголошує `USER 101` і `EXPOSE 8080`, тож у фінальному stage **не потрібні** жодні явні `USER`-інструкції:
|
|
143
|
-
|
|
144
|
-
- **жодного `USER root` / `USER 0`** для білд-кроків: він перезатирає успадкований `USER 101`, і якщо потім не повернути non-root — фінальний образ лишається root, а k8s із `runAsNonRoot: true` падає з `CreateContainerConfigError`;
|
|
145
|
-
- **жодного switch-back** `USER 101` / `USER nginx` наприкінці stage — це лише симптом зайвого `USER root` на початку (повертати треба саме **числовим** UID, бо kubelet не підтверджує non-root за іменем `nginx`). Канон — взагалі не виходити з-під дефолтного 101;
|
|
146
|
-
- **`COPY`/`ADD` лише з `--chown`** (канон — `--chown=nginx:nginx`): без нього файли копіюються власником root і дефолтний non-root користувач (uid=101) не зможе читати статику.
|
|
147
|
-
|
|
148
|
-
Build-stage не чіпаємо — там root і tooling норма. Перевіряє **`npm/rules/docker/lib/docker-nginx-user.mjs`** (підключено в **`npm/rules/docker/js/lint.mjs`**, точка входу обходу — **`npm/rules/docker/fix.mjs`**).
|
|
149
|
-
|
|
150
|
-
#### Антипатерн (це правило ловить)
|
|
151
|
-
|
|
152
|
-
```dockerfile
|
|
153
|
-
FROM mirror.gcr.io/nginxinc/nginx-unprivileged:alpine-slim
|
|
154
|
-
USER root
|
|
155
|
-
COPY ./k8s/nginx.conf /etc/nginx/conf.d/default.conf
|
|
156
|
-
COPY --from=build /app/dist ./
|
|
157
|
-
RUN find ./ -type f -name "*.js" -exec gzip -k {} \;
|
|
158
|
-
USER 101 # повернення назад — симптом того, що був зайвий USER root
|
|
159
|
-
EXPOSE 8080
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
#### Канон (привести до цього)
|
|
163
|
-
|
|
164
|
-
```dockerfile
|
|
165
|
-
FROM mirror.gcr.io/nginxinc/nginx-unprivileged:alpine-slim
|
|
166
|
-
|
|
167
|
-
COPY --chown=nginx:nginx ./k8s/nginx.conf /etc/nginx/conf.d/default.conf
|
|
168
|
-
|
|
169
|
-
WORKDIR /usr/share/nginx/html
|
|
170
|
-
|
|
171
|
-
COPY --from=build --chown=nginx:nginx /app/dist ./
|
|
172
|
-
|
|
173
|
-
RUN find ./ -type f -name "*.js" -exec gzip -k {} \;
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
(без жодного `USER`, gzip під дефолтним користувачем 101; `EXPOSE 8080` теж зайвий — база вже його оголошує)
|
|
177
|
-
|
|
178
|
-
## Область
|
|
179
|
-
|
|
180
|
-
- Усі файли з іменем **`Dockerfile`** або **`Dockerfile.*`** (наприклад `Dockerfile.prod`) у репозиторії, крім ігнорованих каталогів (`node_modules`, `.git`, `dist`, …) — як у **`rules/docker/fix.mjs`**.
|
|
181
|
-
- Також скрипт перевірки обробляє **`Containerfile`** та **`Containerfile.*`** (Podman / альтернативні імена), навіть якщо glob правила спрацьовує переважно на `Dockerfile*`.
|
|
182
|
-
|
|
183
|
-
## lint-docker
|
|
184
|
-
|
|
185
|
-
CLI **`hadolint`** приймає лише **явні шляхи** (`[DOCKERFILE...]` у **`hadolint --help`**); обхід репозиторію робить правило **`n-cursor lint docker`** (реалізація — **`npm/rules/docker/js/lint.mjs`**).
|
|
186
|
-
|
|
187
|
-
**Область lint-docker (вужча, ніж `check docker`):** лише файли з іменем **`Dockerfile`** та **`*.Dockerfile`** (суфікс **`.dockerfile`** без урахування регістру, наприклад **`api.Dockerfile`**). Файли **`Dockerfile.prod`**, **`Containerfile`** тощо **не** входять у **`lint-docker`**; їх ловить **`check docker`** (`rules/docker/fix.mjs`).
|
|
188
|
-
|
|
189
|
-
Обхід: **`walkDir`** з тими самими пропусками каталогів, що й **`rules/docker/fix.mjs`**. Виклик **`hadolint`** як **нативного бінарника** через **`ensureTool`** (PATH → кеш → авто-install brew/scoop/GitHub Release; **без** `docker run`) — спільна логіка **`npm/rules/docker/lib/docker-hadolint.mjs`**.
|
|
190
|
-
|
|
191
|
-
Окремий `package.json`-скрипт `lint-docker` не потрібен і не перевіряється — єдина точка входу для правила: **`n-cursor lint docker`**.
|
|
192
|
-
|
|
193
|
-
Додай workflow **`.github/workflows/lint-docker.yml`** (гілки **`dev`** і **`main`**, лише **`.yml`**, узгоджено з **`ga.mdc`**):
|
|
194
|
-
|
|
195
|
-
- Канон: [lint-docker.yml.snippet.yml](./policy/lint_docker_yml/template/lint-docker.yml.snippet.yml)
|
|
196
|
-
|
|
197
|
-
Локально hadolint авто-встановлюється через **`ensureTool`** (latest, без піну версії). У CI встанови його кроком із workflow-сніпета (curl-download бінарника — без `docker run`).
|
|
198
|
-
|
|
199
|
-
## Запуск
|
|
200
|
-
|
|
201
|
-
1. **`n-cursor lint docker`** — **`js/lint.mjs`**: **`Dockerfile`** та **`*.Dockerfile`** (див. **`lint-docker`**); у CI використовуй **`n-cursor lint docker --read-only`** і встанови hadolint (приклад у workflow).
|
|
202
|
-
2. **`npx @nitra/cursor fix docker`** — **`rules/docker/fix.mjs`**, виклик hadolint як у **`docker-hadolint.mjs`** (нативний бінарник через **`ensureTool`**; **без** `docker run`).
|
|
203
|
-
3. Кореневий **`.hadolint.yaml`**: вимкнення правил, trusted registries — [документація](https://github.com/hadolint/hadolint#configure). Щоб не додавати **`# hadolint ignore=DL3007`** у кожному **`FROM`** з **`:latest`**, у корені репозиторію задати глобально:
|
|
204
|
-
|
|
205
|
-
```yaml title=".hadolint.yaml"
|
|
206
|
-
ignored:
|
|
207
|
-
- DL3007
|
|
208
|
-
- DL3008
|
|
209
|
-
- DL3018
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
Де DL3007 - «Не використовуй тег latest у FROM»
|
|
213
|
-
Де DL3018 - «Піни версії пакетів у apk add»
|
|
214
|
-
|
|
215
|
-
Якщо немає файлів у межах відповідного набору (**`lint-docker`** або **`check docker`**) — перевірка пропускається (exit 0).
|
|
216
|
-
|
|
217
|
-
Винятки: **`# hadolint ignore=DL3008`** (або інший код) у Dockerfile або **`ignored`** у **`.hadolint.yaml`** (наприклад **DL3007** для **`:latest`** — див. вище).
|
|
34
|
+
[docker-hadolint](./js/hadolint.mdc)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## Перевірка `.github/workflows/lint-docker.yml`
|
|
2
|
+
|
|
3
|
+
Rego-пакет: `docker.lint_docker_yml`
|
|
4
|
+
|
|
5
|
+
Цільовий файл: `.github/workflows/lint-docker.yml`
|
|
6
|
+
|
|
7
|
+
Перевіряє, що CI-workflow lint-docker відповідає канонічному сніпету:
|
|
8
|
+
|
|
9
|
+
- `on.push.paths` містить усі три glob-маски: `**/Dockerfile`, `**/*.Dockerfile`, `**/*.dockerfile`
|
|
10
|
+
- присутні кроки `uses: actions/checkout@v6` та `uses: ./.github/actions/setup-bun-deps`
|
|
11
|
+
- крок install hadolint містить рядок із версією `v2.12.0` (substring-перевірка)
|
|
12
|
+
- крок lint містить `n-cursor lint docker --read-only`
|
|
13
|
+
|
|
14
|
+
Канонічний шаблон: [lint-docker.yml.snippet.yml](./template/lint-docker.yml.snippet.yml)
|
package/rules/efes/main.mdc
CHANGED
|
@@ -20,4 +20,4 @@ bun add -d @nitra/efes-shared
|
|
|
20
20
|
|
|
21
21
|
Пакети (директорія в **`npm/rules/efes/policy/`** → namespace → що перевіряє):
|
|
22
22
|
|
|
23
|
-
-
|
|
23
|
+
[efes-package_json_shared](./policy/package_json_shared/package_json_shared.mdc)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
## Наявність `@nitra/efes-shared` у `devDependencies`
|
|
2
|
+
|
|
3
|
+
Rego-пакет: `efes.package_json_shared`
|
|
4
|
+
|
|
5
|
+
**Цільовий файл:** `package.json` (корінь проєкту, обовʼязковий).
|
|
6
|
+
|
|
7
|
+
**Що перевіряється:** у `devDependencies` кореневого `package.json` має бути ключ `@nitra/efes-shared`. Версію не фіксуємо — лише presence. Наявність лише у `dependencies` (не `devDependencies`) — теж помилка.
|
|
8
|
+
|
|
9
|
+
**Виправлення:**
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun add -d @nitra/efes-shared
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Приклади:**
|
|
16
|
+
|
|
17
|
+
✓ правильно — `devDependencies` містить пакет:
|
|
18
|
+
```json
|
|
19
|
+
{ "devDependencies": { "@nitra/efes-shared": "^1.0.0" } }
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
✗ неправильно — `devDependencies` відсутній або порожній:
|
|
23
|
+
```json
|
|
24
|
+
{ "name": "my-project" }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
✗ неправильно — пакет лише у `dependencies`:
|
|
28
|
+
```json
|
|
29
|
+
{ "dependencies": { "@nitra/efes-shared": "^1.0.0" } }
|
|
30
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Лінт-тулчейн: actionlint, zizmor, n-cursor lint ga
|
|
2
|
+
|
|
3
|
+
**Лінт:** [actionlint](https://github.com/rhysd/actionlint) через [github-actionlint](https://www.npmjs.com/package/github-actionlint); [zizmor](https://docs.zizmor.sh) — `uvx`, офлайн. Запуск — через **`n-cursor lint ga`** (CI — `--read-only`; бінарка з `node_modules/.bin/` пакету `@nitra/cursor`), який робить preflight на `shellcheck` і послідовно запускає `actionlint` та `zizmor`. Окремого `package.json`-скрипта немає.
|
|
4
|
+
|
|
5
|
+
> Виклик через bin-ім'я `n-cursor` (а **не** `npx @nitra/cursor`): `bun x`/`npx` для скоупованого пакету з одним bin-ім'ям повертає 0 без виконання, тому в CI-кроці `run:` використовуй саме `n-cursor lint <rule>`.
|
|
6
|
+
|
|
7
|
+
CLI робить preflight на `shellcheck` і `uv` (`uvx`) у `PATH`, потім запускає `bunx github-actionlint` і `uvx zizmor --offline --collect=workflows .`.
|
|
8
|
+
|
|
9
|
+
Послідовність кроків `n-cursor lint ga`:
|
|
10
|
+
|
|
11
|
+
1. `ensureTool`: `shellcheck` і `conftest` (авто-install або hard-fail)
|
|
12
|
+
2. preflight: `uv` (для `uvx zizmor`) — hint-only, без авто-install
|
|
13
|
+
3. `bunx github-actionlint`
|
|
14
|
+
4. `uvx zizmor --offline --collect=workflows .`
|
|
15
|
+
5. `rules/ga/check()` — Rego-полісі (батч conftest з `npm/policy/ga/`) + JS cross-file перевірки правил `ga.mdc`
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
## Канонічні обов'язкові workflow-файли
|
|
2
|
+
|
|
3
|
+
Кожен з цих файлів перевіряється окремим Rego-пакетом через conftest з відповідним `--namespace`.
|
|
4
|
+
|
|
5
|
+
### `clean-ga-workflows.yml`
|
|
6
|
+
|
|
7
|
+
Очищення старих GitHub Actions workflow runs за розкладом.
|
|
8
|
+
|
|
9
|
+
- Канон: [clean-ga-workflows.yml.snippet.yml](./policy/clean_ga_workflows/template/clean-ga-workflows.yml.snippet.yml)
|
|
10
|
+
|
|
11
|
+
Rego-пакет `ga.clean_ga_workflows` перевіряє: `name`, `on.schedule[].cron`, `workflow_dispatch: {}`, `jobs.cleanup_old_workflows`, `runs-on`, `permissions` (actions: write, contents: read), перший крок (`uses`, `with.token`, `with.save_period`, `with.save_min_runs_number`).
|
|
12
|
+
|
|
13
|
+
### `clean-merged-branch.yml`
|
|
14
|
+
|
|
15
|
+
Видалення злитих гілок за розкладом та вручну.
|
|
16
|
+
|
|
17
|
+
- Канон: [clean-merged-branch.yml.snippet.yml](./policy/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml)
|
|
18
|
+
|
|
19
|
+
Інші гілки в `ignore_branches` — допустимо. Rego-пакет `ga.clean_merged_branch` перевіряє: `name`, `on.schedule[].cron`, `workflow_dispatch: {}`, `jobs.cleanup_old_branches`, `permissions`, крок 0 (`id`, `uses`, `with.github_token`, `with.last_commit_age_days`, `with.ignore_branches`, `with.dry_run: no`), крок 1 (name, env.DELETED_BRANCHES, run з echo Deleted branches).
|
|
20
|
+
|
|
21
|
+
### `lint-ga.yml`
|
|
22
|
+
|
|
23
|
+
CI-лінт GitHub Actions workflows через actionlint + zizmor.
|
|
24
|
+
|
|
25
|
+
- Канон: [lint-ga.yml.snippet.yml](./policy/lint_ga/template/lint-ga.yml.snippet.yml)
|
|
26
|
+
|
|
27
|
+
Rego-пакет `ga.lint_ga` перевіряє: `name`, `on.push.branches` і `on.pull_request.branches` (мають містити dev і main), `on.push.paths` (мають містити `.github/actions/**` і `.github/workflows/**`), `jobs.lint-ga`, `runs-on`, `permissions.contents`, наявність кроку Install conftest, крок `run: n-cursor lint ga --read-only`.
|
|
28
|
+
|
|
29
|
+
### `git-ai.yml`
|
|
30
|
+
|
|
31
|
+
Автоматизація PR-коментарів через git-ai CI.
|
|
32
|
+
|
|
33
|
+
- Канон: [git-ai.yml.snippet.yml](./policy/git_ai/template/git-ai.yml.snippet.yml)
|
|
34
|
+
|
|
35
|
+
Rego-пакет `ga.git_ai` перевіряє: `name`, `on.pull_request.types` (має містити `closed`), `jobs.git-ai`, умову `if:` job (merged PR), `permissions.contents`, наявність кроку встановлення (`https://usegitai.com/install.sh`) та запуску (`git-ai ci github run`).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## VS Code — розширення та налаштування для GitHub Actions
|
|
2
|
+
|
|
3
|
+
### `.vscode/extensions.json`
|
|
4
|
+
|
|
5
|
+
Має рекомендувати `github.vscode-github-actions`:
|
|
6
|
+
|
|
7
|
+
- Канон: [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)
|
|
8
|
+
|
|
9
|
+
Rego-пакет `ga.vscode_extensions`: кожне розширення з template має бути в `recommendations`. Додаткові рекомендації від інших правил — допустимі.
|
|
10
|
+
|
|
11
|
+
### `.vscode/settings.json`
|
|
12
|
+
|
|
13
|
+
Для мови `github-actions-workflow` має `editor.defaultFormatter = "oxc.oxc-vscode"`:
|
|
14
|
+
|
|
15
|
+
- Канон: [settings.json.snippet.json](./policy/vscode_settings/template/settings.json.snippet.json)
|
|
16
|
+
|
|
17
|
+
Rego-пакет `ga.vscode_settings`: перевіряє всі ключі з template у відповідному language-block.
|