@nitra/cursor 12.8.5 → 12.8.7
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/bin/n-cursor.js +5 -5
- package/package.json +1 -1
- package/rules/abie/js/http_route_base.mdc +25 -0
- package/rules/abie/js/ua_http_route.mdc +1 -1
- package/rules/abie/main.mdc +12 -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 +13 -95
- 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 +9 -61
- package/rules/capacitor/js/ios_spm.mdc +69 -0
- package/rules/capacitor/js/version.mdc +29 -0
- package/rules/capacitor/main.mdc +8 -22
- 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 +14 -14
- 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 +15 -196
- 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 +17 -125
- package/rules/graphql/js/tooling.mdc +13 -0
- package/rules/graphql/js/vscode_extensions.mdc +13 -0
- package/rules/graphql/main.mdc +3 -22
- 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 +8 -30
- 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 +7 -34
- 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/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 +21 -214
- 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 +15 -605
- 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 +3 -11
- 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-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 -218
- 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 +30 -843
- 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 +6 -112
- 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 -55
- package/rules/php/js/lint_php_yml.mdc +12 -0
- package/rules/php/js/tooling.mdc +66 -0
- package/rules/php/main.mdc +7 -66
- 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 +9 -33
- 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 +8 -24
- 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/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 -35
- 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 +13 -253
- 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 +8 -78
- 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 +18 -184
- 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 -237
- 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 +16 -640
- package/scripts/auto-rules.mjs +6 -6
- package/scripts/auto-skills.mjs +3 -3
- package/scripts/docs/auto-rules.md +17 -31
- package/scripts/docs/auto-skills.md +18 -163
- package/scripts/docs/index.md +16 -16
- package/scripts/lib/docs/index.md +36 -36
- package/scripts/lib/docs/mirror-parity.md +7 -7
- package/scripts/lib/docs/rule-meta.md +12 -12
- package/scripts/lib/docs/skill-meta.md +9 -9
- package/scripts/lib/docs/worktree-notice.md +10 -8
- package/scripts/lib/rule-meta.mjs +6 -6
- package/scripts/lib/skill-meta.mjs +6 -6
- package/scripts/lib/worktree-notice.mjs +2 -2
- package/scripts/utils/docs/index.md +14 -14
|
@@ -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,30 @@ 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
|
+
| Пакет | Що перевіряє |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `docker.lint_docker_yml` | `.github/workflows/lint-docker.yml` відповідає канонічному сніпету (paths, uses, run-підрядки) |
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
[docker-mirror](./js/mirror.mdc)
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
[docker-multistage](./js/multistage.mdc)
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
[docker-compile](./js/compile.mdc)
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
FROM mirror.gcr.io/oven/bun:alpine AS build-env
|
|
28
|
+
[docker-native-addon](./js/native-addon.mdc)
|
|
32
29
|
|
|
33
|
-
|
|
30
|
+
[docker-non-root](./js/non-root.mdc)
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
[docker-nginx-tag](./js/nginx-tag.mdc)
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
COPY bunfig.toml .
|
|
34
|
+
[docker-nginx-user](./js/nginx-user.mdc)
|
|
39
35
|
|
|
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`** — див. вище).
|
|
36
|
+
[docker-hadolint](./js/hadolint.mdc)
|
|
@@ -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.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
## Універсальні правила для всіх workflow-файлів
|
|
2
|
+
|
|
3
|
+
Перевіряє кожен `.github/workflows/*.yml` через Rego-пакет `ga.workflow_common`.
|
|
4
|
+
|
|
5
|
+
### Блок `concurrency` — обов'язковий
|
|
6
|
+
|
|
7
|
+
**Кожен** workflow у `.github/workflows/*.yml` **обов'язково** містить блок `concurrency` з фіксованим `group` та `cancel-in-progress: true`:
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.ref }}-${{ github.workflow }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Без винятків — у scheduled cleanup-воркфлоу, у `pull_request: types: [closed]`, у publish-воркфлоу теж. Це уникає паралельних запусків того самого workflow на тій самій ref і скасовує попередні в чергу нових.
|
|
16
|
+
|
|
17
|
+
### Локальний composite `setup-bun-deps` — обов'язковий
|
|
18
|
+
|
|
19
|
+
**ЗАБОРОНЕНО** дублювати кроки встановлення Bun та кешування безпосередньо у workflow файлах. Завжди використовуй локальний composite action.
|
|
20
|
+
|
|
21
|
+
**Локальний composite** (`uses: ./.github/actions/setup-bun-deps` або `./npm/github-actions/setup-bun-deps`): **спочатку** обов'язковий крок **`actions/checkout@v6`** (`persist-credentials: false`), інакше runner не знайде `action.yml`. Сам composite: **`actions/setup-node@v6`** (**Node 24**), **Bun**, **`actions/cache@v5`**, **`bun install --frozen-lockfile`**.
|
|
22
|
+
|
|
23
|
+
### Приклад (НЕПРАВИЛЬНО)
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v6
|
|
28
|
+
- uses: oven-sh/setup-bun@v2
|
|
29
|
+
- uses: actions/cache@v5
|
|
30
|
+
# ... багато рядків кешування ...
|
|
31
|
+
- run: bun install --frozen-lockfile
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Приклад (ПРАВИЛЬНО)
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v6
|
|
39
|
+
with:
|
|
40
|
+
persist-credentials: false
|
|
41
|
+
- uses: ./.github/actions/setup-bun-deps
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Крок `run:` — folded block `>-`
|
|
45
|
+
|
|
46
|
+
**Кроки `run`:** не розбивай команду shell-продовженням через зворотний сліш у кінці рядка (`… \` у `run: |`). Замість багаторядкового буквального блока з `\\` оформ довгу одну shell-команду як **folded block** `>-` (рядки з'єднаються в один рядок із пробілами).
|
|
47
|
+
|
|
48
|
+
**Читабельність `run: >-` з циклами/умовами:** оскільки `>-` **згортає рядки в один shell-рядок**, відступи потрібні **лише для читабельності** й не впливають на виконання. Але для конструкцій bash на кшталт `while …; do … done` та `if …; then … fi` **обов'язково**:
|
|
49
|
+
|
|
50
|
+
- став явні роздільники команд **`;`** або **`&&`** там, де при згортанні рядків інакше "злипнуться" токени (`do`/`then`/`fi`/`done` з наступною командою);
|
|
51
|
+
- тримай `do` і `then` в одному логічному рядку як `…; do` / `…; then`, щоб після згортання це гарантовано лишалось валідним bash;
|
|
52
|
+
- додавай відступи всередині `do/then/else` блоків, навіть якщо це один рядок після згортання — так workflow лишається читабельним у diff.
|
|
53
|
+
|
|
54
|
+
### Приклад (ПРАВИЛЬНО — читабельно, `run: >-`, з `while`/`if`)
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
- name: Apply changes
|
|
58
|
+
shell: bash
|
|
59
|
+
run: >-
|
|
60
|
+
echo "$FILES" |
|
|
61
|
+
while read -r FILE; do
|
|
62
|
+
[ -z "$FILE" ] && continue;
|
|
63
|
+
dirname "$FILE";
|
|
64
|
+
done |
|
|
65
|
+
sort -u |
|
|
66
|
+
while read -r DIR; do
|
|
67
|
+
(
|
|
68
|
+
if [ -f "$DIR/kustomization.yaml" ]; then
|
|
69
|
+
printf 'Applying %s\n' "$DIR" &&
|
|
70
|
+
cd "$DIR" &&
|
|
71
|
+
kubectl apply -k .;
|
|
72
|
+
else
|
|
73
|
+
echo "Skip $DIR - no kustomization.yaml";
|
|
74
|
+
fi
|
|
75
|
+
);
|
|
76
|
+
done
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Приклад run (НЕПРАВИЛЬНО — `\\` на кінцях)
|
|
80
|
+
|
|
81
|
+
```yaml
|
|
82
|
+
- run: |
|
|
83
|
+
docker build \
|
|
84
|
+
--push \
|
|
85
|
+
--build-arg BRANCH=${{ github.ref_name }}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Приклад run (ПРАВИЛЬНО — `>-`)
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
- run: >-
|
|
92
|
+
docker build
|
|
93
|
+
--push
|
|
94
|
+
--build-arg BRANCH=${{ github.ref_name }}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Мінімальні версії marketplace actions
|
|
98
|
+
|
|
99
|
+
Канон: [uses-min-versions.snippet.json](./policy/workflow_common/template/uses-min-versions.snippet.json)
|
|
100
|
+
|
|
101
|
+
- `actions/checkout` — **не нижче major `v6`** (`@v6`, `@v6.0.2` тощо дозволені; `@v5` — ні);
|
|
102
|
+
- `Infisical/secrets-action` — **не нижче `v1.0.16`** (Node 24; нижчі теги лишаються на Node 20, deprecated з червня 2026).
|
|
103
|
+
|
|
104
|
+
SHA-pin (40 hex) semver-політику не застосовує. Для checkout рекомендується **`v6.0.2+`** (Node 24 у action), але мінімум політики — лише major **6**.
|
|
105
|
+
|
|
106
|
+
### Заборона `depcheck` у workflow
|
|
107
|
+
|
|
108
|
+
**`depcheck`:** не використовувати у `.github/workflows/*.yml` — мігровано на `knip` (div. `js.mdc`). Перевірка невикористаних залежностей виконується разом з рештою лінтерів у `lint-js`, окремий крок `npx depcheck` у workflow не потрібен і блокується полісі `ga.workflow_common`.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
## Структура workflow-файлів та обов'язкові workflows
|
|
2
|
+
|
|
3
|
+
У `.github/workflows/` лише **`.yml`** (не `.yaml`). Обов'язкові файли:
|
|
4
|
+
|
|
5
|
+
- **`clean-ga-workflows.yml`**
|
|
6
|
+
- **`clean-merged-branch.yml`**
|
|
7
|
+
- **`lint-ga.yml`**
|
|
8
|
+
- **`git-ai.yml`**
|
|
9
|
+
|
|
10
|
+
Якщо є **`apply-k8s.yml`** — тригер `on.push.paths` має містити `**/k8s/**/*.yaml`.
|
|
11
|
+
Якщо є **`apply-nats-consumer.yml`** — тригер `on.push.paths` має містити `**/consumer.yaml`.
|
|
12
|
+
|
|
13
|
+
## Перевірка glob-патернів `on.push.paths`
|
|
14
|
+
|
|
15
|
+
Кожен позитивний glob у `on.push.paths` / `on.pull_request.paths` (крім `!`-негацій та `*`-extension-фільтрів) має матчитися хоча б на один tracked файл у репозиторії (`git ls-files`). Шаблони на кшталт `*.vue` чи `{*.ts,*.js}` пропускаються — вони можуть бути заготовками для майбутніх файлів. Конкретні директорії (`some-dir/**`) — перевіряються.
|
|
16
|
+
|
|
17
|
+
## Заборона MegaLinter
|
|
18
|
+
|
|
19
|
+
**MegaLinter** не використовувати. Треба видалити:
|
|
20
|
+
|
|
21
|
+
- workflow-файли з `oxsecurity/megalinter-action` або `megalinter/megalinter`
|
|
22
|
+
- конфіги: `.mega-linter.yml`, `.megalinter.yaml`, `.mega-linter.yaml`
|
|
23
|
+
- будь-які залежності та згадки в CI / pre-commit / документації
|
|
24
|
+
|
|
25
|
+
## Shellcheck у PATH
|
|
26
|
+
|
|
27
|
+
`actionlint` (через `bunx github-actionlint`) запускає shell-перевірки в кроках `run:` лише коли `shellcheck` доступний у PATH — інакше мовчки пропускає SC-правила. Локальний `bun lint-ga` лишається зеленим, а CI на `ubuntu-latest` (де shellcheck передвстановлений) падає.
|
|
28
|
+
|
|
29
|
+
Встанови `shellcheck`:
|
|
30
|
+
- macOS: `brew install shellcheck`
|
|
31
|
+
- Debian/Ubuntu: `sudo apt-get install -y shellcheck`
|
|
32
|
+
- Arch: `sudo pacman -S shellcheck`
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
## `.github/zizmor.yml` — конфігурація zizmor
|
|
2
|
+
|
|
3
|
+
Для [unpinned-uses](https://docs.zizmor.sh/audits/#unpinned-uses) — політика **`ref-pin`**, якщо в `uses:` семантичні теги. За потреби вимкни [template-injection](https://docs.zizmor.sh/audits/#template-injection).
|
|
4
|
+
|
|
5
|
+
- Канон `.github/zizmor.yml`: [zizmor.yml.snippet.yml](./policy/zizmor_yml/template/zizmor.yml.snippet.yml)
|
|
6
|
+
|
|
7
|
+
Rego-пакет `ga.zizmor_yml`: перевіряє `rules.unpinned-uses.config.policies["*"]` відповідно до значення з template.
|