Compare commits
7
Commits
4a78dd640d
...
v0.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc030f84c9 | ||
|
|
f27487b6a8 | ||
|
|
9fbc23f757 | ||
|
|
80fa254a31 | ||
|
|
21853fde6a | ||
|
|
7e382d460b | ||
|
|
95e4361599 |
@@ -9,9 +9,8 @@
|
||||
3. Расширь подходящий owner/helper/pattern; выбирай минимальное решение,
|
||||
полностью закрывающее текущую задачу. Новый файл сам по себе не улучшение.
|
||||
|
||||
Уменьшай количество способов решать одну техническую задачу. Во frontend
|
||||
сравни query keys, formatters, class composition; в backend — pagination,
|
||||
error mapping, repositories и обращения к одному provider. Сходный синтаксис
|
||||
Уменьшай количество способов решать одну техническую задачу: конфигурацию
|
||||
release-it и commitlint поддерживай средствами этих инструментов. Сходный синтаксис
|
||||
без общей ответственности не требует DRY. Критерий dependency — в
|
||||
[dependencies](dependencies.md).
|
||||
|
||||
@@ -23,10 +22,8 @@ Consistency с подходящим project pattern предпочтительн
|
||||
Сначала используй native API/установленный stack; не строй на гипотетических
|
||||
consumers и будущих requirements.
|
||||
|
||||
Backend: CQRS, Event Sourcing, Domain Events, Unit of Work, Command Bus,
|
||||
generic repositories, abstract factories, custom DI и elaborate domain layers
|
||||
не вводятся только ради «best practice». Frontend: wrapper над Mantine, global
|
||||
store и дополнительная OpenAPI hash/schema система не создаются автоматически.
|
||||
Не вводи shared tooling для других репозиториев как обязательную зависимость
|
||||
их сборки, CI или маршрута инструкций. Каждый repo владеет своими checks.
|
||||
Trust-boundary validation, authorization, atomicity, accessibility и необходимая
|
||||
regression proof остаются частью полного решения.
|
||||
|
||||
@@ -44,8 +41,8 @@ low-risk, behavior-preserving проблемы, которые легко про
|
||||
Устойчивый pattern должен находиться через код и manifest. Добавляй convention,
|
||||
только если следующей сессии всё ещё нужно явное project decision rule.
|
||||
У каждого правила один owner; AGENTS содержит trigger и ссылку.
|
||||
Mental model живёт в `.ai/architecture.md`, intent — в specs, системные знания —
|
||||
в docs. Audit/backlog не помещай в conventions.
|
||||
Устройство root tooling находится в README и конфигурации; их чтение не
|
||||
требует соседних checkout. Audit/backlog не помещай в conventions.
|
||||
|
||||
Legacy фиксируется отдельно; новый diff не добавляет нарушений. При migration
|
||||
закрывай подтверждённые baseline findings с evidence, сохраняя область проверки.
|
||||
@@ -65,4 +62,4 @@ Numeric policy называй по смыслу и единицам (`TIMEOUT_MS
|
||||
Индексы, `index + 1`, `Math.max(value, 0)` и обычные labels/log messages
|
||||
не выноси в constants без semantic benefit. Локальный lint/i18n profile всё равно
|
||||
применим. Не создавай tests существования констант или custom magic-string analyzer.
|
||||
Различия TS/генератора принадлежат local conventions frontend/backend.
|
||||
Правила приложений принадлежат их локальным conventions.
|
||||
|
||||
@@ -14,9 +14,6 @@ native API и зрелую небольшую dependency: parsing, serialization
|
||||
debounce, class composition. Три `Array.map` не требуют utility library.
|
||||
Не продолжай копировать boilerplate, если один принятый способ дешевле сопровождать.
|
||||
|
||||
`clsx` сейчас не принят как direct dependency. Разные способы соединить классы
|
||||
сначала требуют подтверждённого повторения; устанавливать его заранее не нужно.
|
||||
|
||||
## При выборе пакета
|
||||
|
||||
- Укажи конкретную потребность, альтернативу из текущего stack и существенную
|
||||
@@ -26,8 +23,7 @@ debounce, class composition. Три `Array.map` не требуют utility libr
|
||||
- Сохраняй exact versions и lockfile. Перед Node-командами активируй `.nvmrc`
|
||||
через `nvm use --silent`; воспроизводимая установка — `npm ci` в нужном repo.
|
||||
- Peer-конфликты решай совместимыми версиями, без `--force`/`--legacy-peer-deps`.
|
||||
Известная frontend граница TypeScript/openapi-typescript — в
|
||||
[README](../../frontend/README.md#проверка); версии принадлежат manifest/lockfile.
|
||||
Версии и compatibility принадлежат местному manifest/lockfile.
|
||||
- После изменения запускай relevant install/type/build/behavior checks и
|
||||
existing security workflow. Не добавляй второй analyzer, когда задача решается
|
||||
`npm audit` при изменении зависимостей. Не добавляй второй analyzer, когда задача решается
|
||||
настройкой существующего; [карта проверок](guardrails.md).
|
||||
|
||||
@@ -7,16 +7,14 @@
|
||||
- Commit содержит одну связанную задачу и её необходимые проверки/документацию.
|
||||
Межрепозиторное изменение оформляй отдельными commits/PR с взаимными ссылками.
|
||||
- Commits и заголовки PR — Conventional Commits; допустим `spec:`.
|
||||
Во frontend номер связанной issue добавляй в конец заголовка (`fix: … #12`);
|
||||
без связанной issue номер не нужен. Формат проверяет local commitlint.
|
||||
Формат проверяет local commitlint; правила других repo принадлежат их AGENTS.
|
||||
- Перед commit проверь `git diff --cached --name-status` и сам staged diff
|
||||
в каждом affected repo. Root tracking ограничен [.gitignore](../../.gitignore):
|
||||
новый общий файл получает точечный allowlist и проверку
|
||||
[tracking](../../test-workspace-tracking.py). `git add -f` не используется.
|
||||
новый общий файл получает точечный allowlist; проверь staged paths вручную. `git add -f` не используется.
|
||||
- Не выполняй `git clean -fdx`, reset чужих изменений, force-push, удаление или
|
||||
перенос опубликованных тегов как обычную очистку. Для опасной операции нужны
|
||||
явная необходимость, сохранение данных и соответствующая авторизация.
|
||||
- Обычный PR допускает squash. Release/hotfix, синхронизация main/develop,
|
||||
release-конфигурация и CI refs требуют чтения
|
||||
release-конфигурация требуют чтения
|
||||
[процесса релиза](../../README.md#релизы-и-ветки): он владеет merge policy,
|
||||
версиями и тегами. Прямой push в `main` запрещён; версии вручную не повышай.
|
||||
|
||||
@@ -1,48 +1,15 @@
|
||||
# Conventions → deterministic guardrails
|
||||
# Root проверки
|
||||
|
||||
Читай при выборе проверки или изменении tooling. Команды выполняются из repo
|
||||
владельца после `nvm use --silent`. Config/manifest задаёт фактический scope;
|
||||
таблица не заменяет semantic review и не утверждает, что все checks уже запущены.
|
||||
Root CI содержит только `commits`: штатный commitlint проверяет заголовок PR
|
||||
и коммиты между BASE и HEAD. Установка — `npm ci --ignore-scripts`, Node — из
|
||||
местной `.nvmrc`. Самописные проверки и дополнительные CI workflow не добавляются.
|
||||
|
||||
| Convention | Существующий check | Достаточность / следующий шаг |
|
||||
| --- | --- | --- |
|
||||
| Git multi-repo tracking | root `python3 test-workspace-tracking.py`, `.gitignore` | Проверяет exact allowlist и сохранность fictional data/nested Git; обновлять вместе с allowlist |
|
||||
| Conventional Commits | local commitlint + commits workflow | Syntax покрыт; атомарность и scope — review |
|
||||
| Dependencies | exact manifest/lockfile, `npm ci`, security workflows (audit/Gitleaks) | Reuse, peer compatibility, ценность dependency — review; новый analyzer не нужен |
|
||||
| Frontend types/style/literals | TypeScript strict, `erasableSyntaxOnly`; Biome `npm run lint` | Props profile, i18n и symbolic strings требуют review; literals exclusions — в Biome |
|
||||
| Frontend FSD | dependency-cruiser `npm run deps` | Upward imports, cycles, public entry points покрыты; same-layer запрет неполный, semantic ownership не проверяется |
|
||||
| Unused frontend code/deps | `npm run knip`, TS unused locals/params | Existing tool достаточно; не добавлять второй unused analyzer |
|
||||
| API generation | frontend `openapi:check`; backend `test:openapi-contract` | Первый сравнивает generated types со snapshot; второй — Nest schema со snapshot. Ни один не доказывает runtime validation errors |
|
||||
| Query/auth/error/finance behavior | Vitest + RTL/MSW; backend Jest/SQLite/HTTP | Test Intent выбирает риск и уровень, без автоматического теста для каждого файла |
|
||||
| Storybook | `typecheck`, `deps:storybook`, `build:catalog` | Imports/types/build покрыты; `play`, visual/keyboard/browser behavior проверять отдельно |
|
||||
| Backend types/style | ESLint/Prettier, app/test TypeScript | Zero-warning gate; `no-explicit-any` отключён, часть unsafe rules включена. Не обещать полного запрета `any` |
|
||||
| Backend modules/domain | TypeScript и semantic review | Отдельного architecture analyzer нет. При повторных нарушениях сначала рассмотреть existing ESLint `no-restricted-imports`, без нового custom analyzer |
|
||||
| DB ownership/atomicity | Prisma schema/constraints, `test:sqlite`, `test:e2e`, `test:durability` | Mock Prisma недостаточно. Relevant suites на disposable DB, без reset пользовательской базы |
|
||||
| Logging/security | Logging/auth/access tests, configured HTTP tests, Gitleaks | Allowlist/redaction имеют границы. Встроенный ESLint `no-console` можно включить отдельно; semantic security review обязателен |
|
||||
| Markdown/routing | docs markdownlint-cli2 + relative-links, `git diff --check` | Existing docs gate охватывает docs, а не соседние `.ai`; для них запустить тот же установленный CLI с явным списком путей и docs config |
|
||||
Локально перед Node-командами выполни `nvm use --silent`. Для изменений проверь
|
||||
JSON/config syntax, тексты, ссылки и `git diff --check`; новые файлы просмотри
|
||||
отдельно. Перед commit проверь staged paths относительно allowlist `.gitignore`.
|
||||
Зависимости устанавливай через `npm ci`; при их изменении выполни `npm audit`.
|
||||
|
||||
## Ratchet без новой инфраструктуры
|
||||
|
||||
- Existing error gates остаются обязательными; не ослабляй их ради legacy.
|
||||
- До расширения FSD rule получи candidate violations существующим depcruise.
|
||||
Его `--ignore-known` допускает native baseline; сначала проверь формат через
|
||||
`--help`. Не игнорируй целые directories. Обновление baseline принимается
|
||||
только с evidence закрытия/явным обоснованием нового исключения.
|
||||
- Semantic findings учитываются stable IDs в
|
||||
[inventory](../migrations/conventions-v1/audit.md) и закрываются vertical slices
|
||||
по [plan](../migrations/conventions-v1/plan.md). Это reviewed inventory,
|
||||
не автоматический полный счётчик нарушений.
|
||||
- Новый analyzer/custom baseline CI не создаётся. Если существующей проверки
|
||||
недостаточно, сначала сформулируй повторяющийся regression и стоимость
|
||||
поддержки предлагаемого rule.
|
||||
|
||||
## Выбор финального gate
|
||||
|
||||
Markdown/routing-only: проверить все новые/изменённые Markdown, ссылки/якоря,
|
||||
достижимость тем, смысл и `git diff --check`. Root allowlist change дополнительно
|
||||
требует tracking-тест. Не запускать application builds/coverage только ради текста.
|
||||
|
||||
Code/dependency changes: full `npm run verify` affected repo после lockfile install;
|
||||
targeted tests ускоряют итерацию, но не подменяют required gate. Wire changes
|
||||
дополнительно требуют backend contract check, HTTP/DB changes — relevant isolated
|
||||
suites по local Testing. Форматирующие `lint:fix`/`format` — mutations, не proof.
|
||||
Release-it использует встроенные проверки ветки, чистого дерева и наличия
|
||||
коммитов. Перед подготовкой релиза вручную проверь теги и соответствие версии
|
||||
последнему принятому тегу; повторно не повышай уже подготовленную версию.
|
||||
Процесс — в [README](../../README.md#релизы-и-ветки).
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Copy to a restricted, untracked file. Never commit filled values.
|
||||
# Full release image references; use published version tags, not latest.
|
||||
FRONTEND_IMAGE=nexus.ksv741.keenetic.pro/wealthledger/frontend:0.2.0
|
||||
BACKEND_IMAGE=nexus.ksv741.keenetic.pro/wealthledger/backend:0.2.0
|
||||
FRONTEND_PORT=8080
|
||||
FRONTEND_ORIGIN=https://wealthledger.example.com
|
||||
# Absolute directory, pre-created with owner UID/GID 1000:1000 and mode 0700.
|
||||
DATA_DIRECTORY=/srv/wealthledger/data
|
||||
# Generate once and keep across updates. Store an independent key backup.
|
||||
JWT_SECRET=
|
||||
TOKEN_ENCRYPTION_KEY=
|
||||
LOG_LEVEL=info
|
||||
@@ -0,0 +1,10 @@
|
||||
# Local-only fixture values. Never use this file for a server deployment.
|
||||
# Base-file interpolation happens before overrides; these references are replaced.
|
||||
FRONTEND_IMAGE=wealthledger-frontend:development
|
||||
BACKEND_IMAGE=wealthledger-backend:development
|
||||
DATA_DIRECTORY=/unused-development-volume
|
||||
FRONTEND_PORT=5173
|
||||
FRONTEND_ORIGIN=http://localhost:5173
|
||||
JWT_SECRET=local-development-only-not-a-server-secret
|
||||
TOKEN_ENCRYPTION_KEY=MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=
|
||||
LOG_LEVEL=info
|
||||
@@ -22,5 +22,5 @@ jobs:
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: |
|
||||
printf '%s\n' "$PR_TITLE" | npm run commitlint -- --strict
|
||||
npm run commitlint -- --strict --from "$BASE_SHA" --to "$HEAD_SHA"
|
||||
printf '%s\n' "$PR_TITLE" | npm exec --no -- commitlint --strict
|
||||
npm exec --no -- commitlint --strict --from "$BASE_SHA" --to "$HEAD_SHA"
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
name: Workspace security
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '17 5 * * *'
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: https://github.com/actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Record checked revision
|
||||
run: git rev-parse HEAD
|
||||
- name: Scan history with Gitleaks 8.30.1
|
||||
run: |
|
||||
curl --fail --location --silent --show-error https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz -o /tmp/gitleaks.tar.gz
|
||||
echo '551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb /tmp/gitleaks.tar.gz' | sha256sum --check
|
||||
mkdir -p /tmp/gitleaks-bin
|
||||
tar -xzf /tmp/gitleaks.tar.gz -C /tmp/gitleaks-bin gitleaks
|
||||
/tmp/gitleaks-bin/gitleaks git --log-opts=--all --redact=100 --no-banner
|
||||
- uses: https://github.com/actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- name: Audit runtime and development dependencies
|
||||
run: npm audit --include=dev --audit-level=low
|
||||
@@ -1,17 +0,0 @@
|
||||
name: Workspace verify
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: https://github.com/actions/checkout@v4
|
||||
- uses: https://github.com/actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
- run: npm ci
|
||||
- run: npm run verify
|
||||
+5
-7
@@ -3,7 +3,10 @@
|
||||
!/.gitignore
|
||||
!/AGENTS.md
|
||||
!/README.md
|
||||
!/test-workspace-tracking.py
|
||||
!/compose.yaml
|
||||
!/compose.development.yaml
|
||||
!/.env.compose.example
|
||||
!/.env.development.example
|
||||
!/.ai/
|
||||
/.ai/*
|
||||
!/.ai/conventions/
|
||||
@@ -25,15 +28,10 @@
|
||||
!/.release-it.json
|
||||
!/CHANGELOG.md
|
||||
!/commitlint.config.cjs
|
||||
!/.gitleaksignore
|
||||
!/renovate.json
|
||||
!/test-release.cjs
|
||||
!/scripts/
|
||||
/scripts/*
|
||||
!/scripts/check-release.cjs
|
||||
!/.gitea/
|
||||
/.gitea/*
|
||||
!/.gitea/workflows/
|
||||
/.gitea/workflows/*
|
||||
!/.gitea/workflows/commits.yml
|
||||
!/.gitea/workflows/verify.yml
|
||||
!/.gitea/workflows/security.yml
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Reviewed historical local-development fixture: base64 of the deterministic
|
||||
# string 0123456789abcdef repeated twice, never a runtime secret.
|
||||
7e382d460be296b6c88e17084fff17bb7728f6ea:.env.development.example:generic-api-key:9
|
||||
@@ -18,9 +18,6 @@
|
||||
"gitlab": {
|
||||
"release": false
|
||||
},
|
||||
"hooks": {
|
||||
"after:git:init": "node scripts/check-release.cjs"
|
||||
},
|
||||
"plugins": {
|
||||
"@release-it/conventional-changelog": {
|
||||
"infile": "CHANGELOG.md",
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
# WealthLedger workspace
|
||||
|
||||
`frontend`, `backend`, `docs`, `specs` и root — независимые Git-репозитории.
|
||||
Root содержит workspace-навигацию и конфигурацию стандартных инструментов.
|
||||
Самописные скрипты не добавляются; CI выполняет только commitlint.
|
||||
`frontend`, `backend`, `docs`, `specs` — самостоятельные Git-репозитории.
|
||||
Root tracking ограничен allowlist; локальные данные и nested repos в него
|
||||
не добавляются. `references/**`, dated audits и архивы — read-only.
|
||||
Структура/запуск — в [README](README.md).
|
||||
|
||||
Начни с результата задачи и affected areas. Прочитай local AGENTS затронутого
|
||||
repo; проследи поведение/инварианты и найди owner с 1–3 примерами до реализации.
|
||||
Код показывает механизм, а случайный аналог не переопределяет контракт.
|
||||
Работай напрямую; OpenSpec/SDD lifecycle нужен только по явному запросу.
|
||||
Инструкции root относятся только к root. Работа внутри другого репозитория
|
||||
начинается с его собственного AGENTS; он содержит все обязательные правила
|
||||
без наследования root conventions и без чтения соседних checkout.
|
||||
Публичные API artifacts передаются явно; CI каждого repo использует локальные
|
||||
входы. Справочные источники не становятся обязательной цепочкой инструкций.
|
||||
|
||||
| Задача / граница | Прочитать |
|
||||
Начни с результата и affected areas. До edits проследи поведение/invariants,
|
||||
найди owner и 1–3 примера. Работай напрямую; OpenSpec/SDD — по явному запросу.
|
||||
|
||||
| Root задача | Прочитать |
|
||||
| --- | --- |
|
||||
| Frontend / backend | [Frontend](frontend/AGENTS.md) / [Backend](backend/AGENTS.md), затем только активированные темы |
|
||||
| Docs / specs | [Docs AGENTS](docs/AGENTS.md) / [Specs AGENTS](specs/AGENTS.md) |
|
||||
| Новая функция, неясное ожидание, межрепозиторное поведение | [Docs entry](docs/README.md) → нужная область/spec/ADR; локальный build/lint bug начинает с repo instructions/code |
|
||||
| Новый helper/hook/wrapper/pattern, refactor, выбор scope | [Code health / change scope](.ai/conventions/code-health.md) |
|
||||
| Dependency или tooling | [Dependencies](.ai/conventions/dependencies.md), [guardrails](.ai/conventions/guardrails.md) |
|
||||
| Нетривиальные tests | [Test Intent](.ai/conventions/testing.md) и local Testing |
|
||||
| Git, ветка, commit, PR, tracking | [Git](.ai/conventions/git.md) |
|
||||
| Release/hotfix, main↔develop, release config/CI refs | [Release process](README.md#релизы-и-ветки) |
|
||||
| Migration существующих conventions findings | [Audit/plan](.ai/migrations/conventions-v1/plan.md) |
|
||||
| Helper/pattern, refactor, scope | [Code health](.ai/conventions/code-health.md) |
|
||||
| Dependency/tooling | [Dependencies](.ai/conventions/dependencies.md), [Guardrails](.ai/conventions/guardrails.md) |
|
||||
| Нетривиальные tests | [Test Intent](.ai/conventions/testing.md) |
|
||||
| Git/branch/commit/PR/tracking | [Git](.ai/conventions/git.md) |
|
||||
| Release/hotfix | [README](README.md#релизы-и-ветки) |
|
||||
|
||||
Перед Node-командой активируй `.nvmrc` affected repo через `nvm use --silent`.
|
||||
Перед Node-командой активируй местную `.nvmrc`: `nvm use --silent`.
|
||||
При новой границе и перед завершением повтори routing по staged/unstaged/new
|
||||
files; сохраняй чужие изменения. Missing required source или конфликт требований
|
||||
сообщи до зависимого решения. После code diff выполни
|
||||
[Boy Scout review](.ai/conventions/code-health.md#boy-scout-review); проверь результат
|
||||
по рискам и [relevant gates](.ai/conventions/guardrails.md#выбор-финального-gate).
|
||||
Сохрани краткие команды/результаты и ограничения; успех lint не заменяет semantic review.
|
||||
files; сохраняй чужие изменения. Missing source/конфликт требований сообщи
|
||||
до зависимого решения. После diff выполни
|
||||
[Boy Scout review](.ai/conventions/code-health.md#boy-scout-review) и
|
||||
[relevant gates](.ai/conventions/guardrails.md). Запиши краткие команды,
|
||||
результаты и ограничения; lint не заменяет semantic review.
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# Changelog
|
||||
|
||||
## [0.2.1](https://git.personal.ksv741.ru/WealthLedger/main/compare/v0.2.0...v0.2.1) (2026-10-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** refresh release security baseline ([f27487b](https://git.personal.ksv741.ru/WealthLedger/main/commit/f27487b6a8cb59243a34e392059010dab38efd51))
|
||||
|
||||
### Documentation
|
||||
|
||||
* link production deployment repository ([9fbc23f](https://git.personal.ksv741.ru/WealthLedger/main/commit/9fbc23f7570ecb6ea13f7fae5490504d1a0d9ba8))
|
||||
|
||||
### Maintenance
|
||||
|
||||
* point container images to Nexus [#15](https://git.personal.ksv741.ru/WealthLedger/main/issues/15) ([80fa254](https://git.personal.ksv741.ru/WealthLedger/main/commit/80fa254a31d525dbc98bb3fe2d949f78c8e5872b))
|
||||
|
||||
## [0.2.0](https://git.personal.ksv741.ru/WealthLedger/main/compare/v0.1.0...v0.2.0) (2026-09-16)
|
||||
|
||||
### Features
|
||||
|
||||
* add containerized application delivery [#11](https://git.personal.ksv741.ru/WealthLedger/main/issues/11) ([7e382d4](https://git.personal.ksv741.ru/WealthLedger/main/commit/7e382d460be296b6c88e17084fff17bb7728f6ea))
|
||||
|
||||
### Documentation
|
||||
|
||||
* align inventory with simplified conventions ([4a78dd6](https://git.personal.ksv741.ru/WealthLedger/main/commit/4a78dd640d4a1db1b7c9dc4036375c076d55b34d))
|
||||
* establish workspace engineering conventions ([93dd553](https://git.personal.ksv741.ru/WealthLedger/main/commit/93dd5539ad8bd10e11c59708e7b66affc19e4883))
|
||||
|
||||
### Maintenance
|
||||
|
||||
* simplify root tooling and repository boundaries ([95e4361](https://git.personal.ksv741.ru/WealthLedger/main/commit/95e4361599244598126394aa658a0a27d77c0686))
|
||||
|
||||
## 0.1.0 (2026-09-14)
|
||||
|
||||
### Documentation
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Локальное приложение для портфеля T-Bank, операций, дохода и будущих выплат,
|
||||
а также ручного учёта банковских карт и расходов.
|
||||
[Обзор продукта](docs/overview.md) и [документация системы](docs/README.md).
|
||||
[Обзор продукта](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/overview.md) и [документация системы](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/README.md).
|
||||
|
||||
## Структура workspace
|
||||
|
||||
@@ -12,10 +12,10 @@ Workspace состоит из пяти независимых Git-репозит
|
||||
| Путь от root | Репозиторий Gitea | Назначение |
|
||||
| --- | --- | --- |
|
||||
| `.` | `WealthLedger/main` | Вход в workspace, инструкции и правила root tracking |
|
||||
| `frontend/` | `WealthLedger/frontend` | [React/Vite UI](frontend/README.md) |
|
||||
| `backend/` | `WealthLedger/backend` | [NestJS API, Prisma и SQLite](backend/README.md) |
|
||||
| `docs/` | `WealthLedger/docs` | [Объяснение системы](docs/README.md) |
|
||||
| `specs/` | `WealthLedger/specs` | [Требования и OpenSpec](specs/README.md) |
|
||||
| `frontend/` | `WealthLedger/frontend` | [React/Vite UI](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/README.md) |
|
||||
| `backend/` | `WealthLedger/backend` | [NestJS API, Prisma и SQLite](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md) |
|
||||
| `docs/` | `WealthLedger/docs` | [Объяснение системы](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/README.md) |
|
||||
| `specs/` | `WealthLedger/specs` | [Требования и OpenSpec](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/README.md) |
|
||||
|
||||
### Получение checkout
|
||||
|
||||
@@ -34,13 +34,14 @@ git clone ssh://git@git.personal.ksv741.ru:2222/WealthLedger/specs.git specs
|
||||
Для существующего workspace клонируйте только отсутствующие checkout в пустые
|
||||
каталоги. Проверяйте состояние каждого repo отдельно, например
|
||||
`git -C frontend status`. Обновление main не обновляет соседние repos;
|
||||
общего lockfile revisions нет. Относительные ссылки требуют указанных имён каталогов.
|
||||
общего lockfile revisions нет. Такая раскладка удобна для совместной работы,
|
||||
но каждый checkout можно использовать отдельно.
|
||||
|
||||
### Что хранится в main
|
||||
|
||||
Root отслеживает входные документы, общие engineering conventions и их migration
|
||||
Root отслеживает входные документы, собственные engineering conventions и исторический migration
|
||||
inventory в `.ai/`, npm/release-конфигурацию, changelog,
|
||||
проверки tracking/release и три CI workflow. Точный allowlist — в `.gitignore`.
|
||||
Compose configuration, безопасные env examples и один CI workflow с commitlint. Точный allowlist — в `.gitignore`.
|
||||
Остальные root-пути игнорируются по умолчанию:
|
||||
вложенные repos, `.env`, базы и WAL, импорты выписок, PDF, `references/`,
|
||||
`design/`, `todo/` и локальные настройки инструментов.
|
||||
@@ -52,6 +53,34 @@ inventory в `.ai/`, npm/release-конфигурацию, changelog,
|
||||
Не запускайте очистку workspace через `git clean -fdx`: ignored-пути содержат
|
||||
локальные данные. Правила tracking не удаляют файлы и вложенные `.git`.
|
||||
|
||||
## Запуск через Docker Compose
|
||||
|
||||
Нужны Docker Engine и Compose >= 2.24.4, а для development — checkout
|
||||
`frontend/` и `backend/` рядом с root. Компоненты собираются независимо:
|
||||
|
||||
```bash
|
||||
docker compose --env-file .env.development.example \
|
||||
-f compose.yaml -f compose.development.yaml up -d --build --wait --wait-timeout 180
|
||||
```
|
||||
|
||||
Откройте `http://localhost:5173`: Vite HMR и `/api/` используют один origin,
|
||||
Nest работает в watch mode. Host `node_modules` и `.env` не монтируются.
|
||||
Данные development хранятся отдельно в named volume `development-data` проекта `wealthledger-dev`;
|
||||
обычные restart/down их сохраняют. Изменение dependencies/config требует rebuild.
|
||||
|
||||
Production использует отдельный [репозиторий deploy](https://git.personal.ksv741.ru/WealthLedger/deploy):
|
||||
его Git-тег закрепляет пару images, а `./deploy.sh vX.Y.Z` выполняет обновление.
|
||||
Рабочие данные, сертификат T-Bank и постоянные ключи находятся вне checkout.
|
||||
Только frontend публикует `127.0.0.1:8080`; серверный Caddy направляет туда весь
|
||||
домен. Backend port остаётся внутренним. Корневой Compose сохраняется для development.
|
||||
|
||||
Пошаговая процедура установки, обновления и восстановления — README `deploy`;
|
||||
архитектурные границы — [документация](https://git.personal.ksv741.ru/WealthLedger/docs),
|
||||
страница `architecture/deployment.md`.
|
||||
Перед backend update обязательны остановка, backup и успешная migration.
|
||||
Image rollback не восстанавливает старую базу. Публикация component images
|
||||
принадлежит их Gitea workflows; root CI остаётся только commitlint.
|
||||
|
||||
## Подготовка и запуск
|
||||
|
||||
Нужны nvm и Node из `.nvmrc` каждого npm-repo (сейчас 24.21.0, npm 11.19).
|
||||
@@ -61,7 +90,7 @@ inventory в `.ai/`, npm/release-конфигурацию, changelog,
|
||||
После смены major Node переустановите native dependencies через `npm ci`.
|
||||
Разрешения install scripts заданы в `package.json` соответствующего repo.
|
||||
|
||||
Для нового backend создайте `.env` по [backend README](backend/README.md),
|
||||
Для нового backend создайте `.env` по [backend README](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md),
|
||||
замените оба placeholder-секрета и проверьте `DATABASE_URL`.
|
||||
Существующий `.env` сохраняйте. Из backend:
|
||||
|
||||
@@ -74,13 +103,13 @@ npm run start:dev
|
||||
|
||||
Build генерирует Prisma client. Перед миграцией существующей базы сохраните
|
||||
согласованный backup и ключ шифрования; процедуру и ограничения отката смотрите
|
||||
в [описании хранения](docs/architecture/containers.md#сохранение-и-восстановление-данных).
|
||||
в [описании хранения](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/architecture/containers.md#сохранение-и-восстановление-данных).
|
||||
Известная ошибка fresh migration при `RUST_LOG=warn/error` устранена в
|
||||
`backend/prisma.config.ts`: CLI удаляет унаследованную переменную.
|
||||
Используйте штатный `prisma migrate deploy` из backend; прежний Prisma blocker
|
||||
снят. [Подробности и границы проверки](backend/README.md#реальная-sqlite-и-сохранность-данных).
|
||||
снят. [Подробности и границы проверки](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md#реальная-sqlite-и-сохранность-данных).
|
||||
|
||||
В другом терминале подготовьте `.env` по [frontend README](frontend/README.md),
|
||||
В другом терминале подготовьте `.env` по [frontend README](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/README.md),
|
||||
затем из frontend:
|
||||
|
||||
```bash
|
||||
@@ -99,7 +128,7 @@ npm run dev
|
||||
OpenSpec не нужен для запуска приложения и применяется по явному запросу.
|
||||
Specs использует Node из своей `.nvmrc`; проверенная версия CLI — 1.10.0.
|
||||
Store `wealth-ledger-specs` расположен в `specs/openspec/`.
|
||||
[Работа с OpenSpec](specs/AGENTS.md), [команды validation](specs/README.md#проверка).
|
||||
[Работа с OpenSpec](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/AGENTS.md), [команды validation](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/README.md#проверка).
|
||||
|
||||
## Проверки
|
||||
|
||||
@@ -107,17 +136,18 @@ Store `wealth-ledger-specs` расположен в `specs/openspec/`.
|
||||
|
||||
| Где | Команда | Что проверяет |
|
||||
| --- | --- | --- |
|
||||
| root | `python3 test-workspace-tracking.py` | Массовый add на вымышленных данных; сохранность файлов и вложенных Git |
|
||||
| root | `npm exec --no -- commitlint --strict --from <base> --to <head>` | Формат сообщений коммитов |
|
||||
| root | `git diff --check` | Whitespace в изменениях root |
|
||||
| frontend | `npm run verify` | Типы, lint, тесты, зависимости, OpenAPI snapshot и сборки |
|
||||
| backend | `npm run verify` | Lint, типы, unit tests и build |
|
||||
| backend | `npm run test:e2e` | HTTP/SQLite suites и контракт с соседним frontend |
|
||||
| backend | `npm run test:e2e` | HTTP/SQLite suites и локальный OpenAPI artifact |
|
||||
| docs | `npm run verify` | Markdown, относительные ссылки и diff |
|
||||
|
||||
Python 3 нужен только для проверки root tracking. Backend e2e использует
|
||||
Root CI выполняет только commitlint: проверяет сообщения коммитов и заголовок PR.
|
||||
Allowlist root проверяется вручную по staged paths. Backend e2e использует
|
||||
временные базы; verify отдельно не доказывает HTTP/DB-сценарии или доступность
|
||||
T-Bank. Полные команды и ограничения принадлежат README
|
||||
[frontend](frontend/README.md#проверка) и [backend](backend/README.md#проверка).
|
||||
[frontend](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/README.md#проверка) и [backend](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md#проверка).
|
||||
Для изменения проекта начните с [AGENTS](AGENTS.md).
|
||||
|
||||
## Релизы и ветки
|
||||
@@ -144,18 +174,19 @@ Commits и заголовки PR — Conventional Commits. `feat:` повыша
|
||||
1. Обновить `main`, `develop` и теги командой `git fetch origin --tags`.
|
||||
Проверить, что текущий `develop` содержит последний релиз из `main`.
|
||||
Создать `release/<name>` от проверенного `develop` и выполнить `npm ci`.
|
||||
2. Для backend и docs обновить `.ci/refs.json`: полные SHA совместимых соседних
|
||||
репозиториев. Эти revisions должны быть доступны в Gitea. Проверить именно
|
||||
их совместимость; движущиеся ветки не являются фиксацией состава релиза.
|
||||
Закоммитить обновлённые pins до подготовки версии.
|
||||
2. Проверить локальные входы и публичные artifacts выпускаемого репозитория.
|
||||
Совместимость развёрнутых участников согласовать отдельно через публичные
|
||||
контракты; CI не скачивает соседние checkout и не требует их SHA.
|
||||
3. Выполнить `npm run release:preview`. Проверить номер и changelog, затем
|
||||
`npm run release:prepare`. Команда создаёт `chore(release): X.Y.Z`, обновляет
|
||||
`package.json`, lockfile и `CHANGELOG.md`; теги и push не выполняет.
|
||||
Не использовать обход branch/clean/tag guards. Незакоммиченные файлы,
|
||||
неподходящая ветка, отсутствие изменений и уже подготовленная версия блокируют выпуск.
|
||||
В root встроенные проверки release-it контролируют ветку, чистое дерево и
|
||||
наличие коммитов. Теги и соответствие версии последнему принятому тегу
|
||||
проверяются вручную; уже подготовленную версию повторно не повышать.
|
||||
4. Проверить diff релизного коммита и штатные gates. Отправить только релизную
|
||||
ветку: `git push -u origin HEAD`. Открыть PR в `main` с заголовком
|
||||
`chore(release): X.Y.Z`. После зелёных `verify`, `security`, `commits`
|
||||
`chore(release): X.Y.Z`. После зелёных обязательных проверок (`commits` в root;
|
||||
`verify`, `security`, `commits` в остальных repo)
|
||||
выполнить **fast-forward-only merge**. Если `main` изменился, сначала
|
||||
согласовать историю и повторить проверку кандидата; не обходить защиту.
|
||||
5. После merge получить `main` и убедиться, что его HEAD — проверенный релизный
|
||||
@@ -185,25 +216,26 @@ Commits и заголовки PR — Conventional Commits. `feat:` повыша
|
||||
`release/hotfix-<name>` в `main`, затем обязательно возвращается в `develop`.
|
||||
Если ломается публичный контракт, major сохраняется и для hotfix.
|
||||
|
||||
Backend CI проверяет runtime OpenAPI с frontend; docs CI — межрепозиторные
|
||||
ссылки. Для `main`, PR в `main` и `release/*` соседи берутся из `.ci/refs.json`;
|
||||
для разработки — из `develop`. SHA записываются в логи. Для изменения сразу
|
||||
нескольких репозиториев сначала согласовать их revisions, затем проверять
|
||||
релизный набор; порядок публикации сам по себе не доказывает совместимость.
|
||||
Каждый CI использует только свой checkout. Backend генерирует OpenAPI без сервера,
|
||||
сверяет генератор с configured app и не хранит JSON в Git; frontend проверяет types по входному
|
||||
snapshot. Docs проверяет внутренние ссылки; внешние источники даются как
|
||||
справочные веб-ссылки. Обновление API artifact потребителя — отдельное действие.
|
||||
Зелёные локальные gates и порядок публикации не доказывают совместимость
|
||||
развёрнутых версий. Процедуры выпуска находятся также в README каждого repo;
|
||||
доступ к root для выпуска другого репозитория не требуется.
|
||||
|
||||
В Gitea для `main` и `develop` нужны запрет прямого push и force-push,
|
||||
обязательные проверки `verify`, `security`, `commits` и запрет обхода правил
|
||||
обязательные проверки: в root только `commits`, в остальных repo —
|
||||
`verify`, `security`, `commits`; также нужен запрет обхода правил
|
||||
администратором. Требование отдельного одобрения review не вводится для
|
||||
разработки одним владельцем. Ветка по умолчанию — `develop`; Renovate тоже
|
||||
отправляет PR в `develop`. Теги `v*` защищены, создание разрешается владельцу
|
||||
релиза; их удаление или перенос не входит в обычный процесс.
|
||||
|
||||
`node test-release.cjs` из root проверяет настоящий release-it во временном Git.
|
||||
Для проверки конфигурации соседа после установки его dependencies:
|
||||
`node test-release.cjs frontend` (аналогично backend, docs, specs).
|
||||
Тест не публикует refs и не использует рабочие базы. Root `npm run verify`
|
||||
включает tracking и этот тест; frontend/backend/docs сохраняют свои gates,
|
||||
backend дополнительно требует `npm run test:e2e`.
|
||||
В root нет собственного test/verify runner и release hooks. Release-it и
|
||||
commitlint используются напрямую. Остальные репозитории сохраняют свои gates.
|
||||
Если в защите веток root ранее были обязательными `verify` и `security`, их
|
||||
нужно убрать из required checks в Gitea; YAML не меняет настройки сервера.
|
||||
|
||||
Npm publishing, автоматический deploy и карточки Gitea Releases не включены.
|
||||
Граница выпуска — принятый коммит, changelog и Git-тег.
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Requires Compose >= 2.24.4 for !override. Always use a separate project name.
|
||||
name: wealthledger-dev
|
||||
|
||||
x-development-backend: &development-backend
|
||||
image: wealthledger-backend:development
|
||||
pull_policy: build
|
||||
build:
|
||||
context: ./backend
|
||||
target: development
|
||||
read_only: false
|
||||
environment:
|
||||
NODE_ENV: development
|
||||
volumes: !override
|
||||
- development-data:/data
|
||||
|
||||
services:
|
||||
migration:
|
||||
<<: *development-backend
|
||||
|
||||
backend:
|
||||
<<: *development-backend
|
||||
healthcheck:
|
||||
test: [CMD, node, -e, 'fetch("http://127.0.0.1:3000/api/v1/auth/me").then(async r=>{await r.arrayBuffer();process.exit(r.status===401?0:1)}).catch(()=>process.exit(1))']
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
start_period: 20s
|
||||
retries: 6
|
||||
volumes: !override
|
||||
- development-data:/data
|
||||
- ./backend/src:/app/src:ro
|
||||
|
||||
frontend:
|
||||
image: wealthledger-frontend:development
|
||||
pull_policy: build
|
||||
build:
|
||||
context: ./frontend
|
||||
target: development
|
||||
read_only: false
|
||||
volumes:
|
||||
- ./frontend/src:/app/src:ro
|
||||
- ./frontend/public:/app/public:ro
|
||||
environment:
|
||||
VITE_API_BASE_URL: ''
|
||||
API_PROXY_TARGET: http://backend:3000
|
||||
healthcheck:
|
||||
test: [CMD, node, -e, 'fetch("http://127.0.0.1:8080/").then(async r=>{await r.arrayBuffer();process.exit(r.ok?0:1)}).catch(()=>process.exit(1))']
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
start_period: 10s
|
||||
retries: 6
|
||||
|
||||
volumes:
|
||||
development-data:
|
||||
@@ -0,0 +1,51 @@
|
||||
name: wealthledger
|
||||
|
||||
x-backend: &backend
|
||||
image: ${BACKEND_IMAGE:?Set versioned backend image}
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: '3000'
|
||||
DATABASE_URL: file:/data/wealthledger.db
|
||||
FRONTEND_ORIGIN: ${FRONTEND_ORIGIN:?Set browser-visible origin}
|
||||
JWT_SECRET: ${JWT_SECRET:?Set persistent JWT secret}
|
||||
TOKEN_ENCRYPTION_KEY: ${TOKEN_ENCRYPTION_KEY:?Set persistent encryption key}
|
||||
LOG_LEVEL: ${LOG_LEVEL:-info}
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ${DATA_DIRECTORY:?Set persistent absolute host data directory}
|
||||
target: /data
|
||||
bind:
|
||||
create_host_path: false
|
||||
read_only: true
|
||||
init: true
|
||||
security_opt: [no-new-privileges:true]
|
||||
cap_drop: [ALL]
|
||||
|
||||
services:
|
||||
migration:
|
||||
<<: *backend
|
||||
command: [node, node_modules/prisma/build/index.js, migrate, deploy]
|
||||
restart: 'no'
|
||||
healthcheck:
|
||||
disable: true
|
||||
|
||||
backend:
|
||||
<<: *backend
|
||||
scale: 1
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
migration:
|
||||
condition: service_completed_successfully
|
||||
|
||||
frontend:
|
||||
image: ${FRONTEND_IMAGE:?Set versioned frontend image}
|
||||
ports:
|
||||
- '127.0.0.1:${FRONTEND_PORT:-8080}:8080'
|
||||
read_only: true
|
||||
tmpfs: [/tmp]
|
||||
security_opt: [no-new-privileges:true]
|
||||
cap_drop: [ALL]
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
Generated
+14
-14
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "wealth-ledger-workspace",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wealth-ledger-workspace",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.1",
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "21.2.2",
|
||||
"@commitlint/config-conventional": "21.2.2",
|
||||
@@ -1446,9 +1446,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/basic-ftp": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz",
|
||||
"integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==",
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-6.2.2.tgz",
|
||||
"integrity": "sha512-LIkJcf/F6bTTfvE3k2YcTxrJsLKKVXiEpHZYkQP6JoqTb7khng7c13ckRTDQbqRh5IkehVH3F00sSlAa2F6DwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2124,9 +2124,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
|
||||
"integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
|
||||
"version": "3.1.8",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
|
||||
"integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -2355,9 +2355,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "10.7.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz",
|
||||
"integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==",
|
||||
"version": "10.7.3",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.3.tgz",
|
||||
"integrity": "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -3439,9 +3439,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
|
||||
"integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
|
||||
"version": "7.29.1",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.29.1.tgz",
|
||||
"integrity": "sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
+7
-2
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"name": "wealth-ledger-workspace",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.1",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "24.21.0"
|
||||
},
|
||||
"scripts": {
|
||||
"verify": "python3 test-workspace-tracking.py && node test-release.cjs && git diff --check",
|
||||
"release:preview": "release-it --dry-run --ci",
|
||||
"release:prepare": "release-it --ci",
|
||||
"commitlint": "commitlint"
|
||||
@@ -17,5 +16,11 @@
|
||||
"@release-it/conventional-changelog": "12.0.0",
|
||||
"conventional-changelog-conventionalcommits": "10.4.0",
|
||||
"release-it": "21.0.2"
|
||||
},
|
||||
"overrides": {
|
||||
"basic-ftp": "6.2.2",
|
||||
"fast-uri": "3.1.8",
|
||||
"ip-address": "10.7.3",
|
||||
"undici": "7.29.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const { execFileSync } = require("node:child_process");
|
||||
const { version } = require("../package.json");
|
||||
const git = (...args) => execFileSync("git", args, { encoding: "utf8" }).trim();
|
||||
|
||||
assert.equal(git("status", "--porcelain"), "", "Commit all changes before preparing a release");
|
||||
const tags = git("tag", "--merged", "HEAD", "--list", "v[0-9]*", "--sort=-version:refname")
|
||||
.split("\n").filter(tag => /^v\d+\.\d+\.\d+$/.test(tag));
|
||||
assert.equal(version, tags[0]?.slice(1) ?? "0.0.0",
|
||||
"Version is already prepared or release tags are missing. Fetch tags; finish the existing release before preparing another.");
|
||||
@@ -1,86 +0,0 @@
|
||||
// Exercise the actual release command in disposable repositories, never the workspace Git.
|
||||
const assert = require('node:assert/strict');
|
||||
const { execFileSync, spawnSync } = require('node:child_process');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const source = path.resolve(process.argv[2] || __dirname);
|
||||
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'wealthledger-release-'));
|
||||
const env = { ...process.env, HUSKY: '0', NO_COLOR: '1' };
|
||||
const git = (...args) => execFileSync('git', args, { cwd, env, encoding: 'utf8' }).trim();
|
||||
const read = file => fs.readFileSync(path.join(cwd, file), 'utf8');
|
||||
const write = (file, text) => fs.writeFileSync(path.join(cwd, file), text);
|
||||
const prepare = (...args) => spawnSync(process.execPath,
|
||||
[path.join(source, 'node_modules/release-it/bin/release-it.js'), '--ci', ...args],
|
||||
{ cwd, env, encoding: 'utf8' });
|
||||
const mustPass = result => assert.equal(result.status, 0, result.stdout + result.stderr);
|
||||
const mustFail = result => assert.notEqual(result.status, 0, result.stdout + result.stderr);
|
||||
const commit = message => git('commit', '--allow-empty', '-qm', message);
|
||||
try {
|
||||
fs.mkdirSync(path.join(cwd, 'scripts'));
|
||||
fs.symlinkSync(path.join(source, 'node_modules'), path.join(cwd, 'node_modules'), 'dir');
|
||||
for (const file of ['.release-it.json', 'scripts/check-release.cjs', 'commitlint.config.cjs']) {
|
||||
fs.copyFileSync(path.join(source, file), path.join(cwd, file));
|
||||
}
|
||||
write('.gitignore', 'node_modules/\nremote.git/\n');
|
||||
write('CHANGELOG.md', '# Changelog\n');
|
||||
write('package.json', JSON.stringify({ name: 'release-fixture', private: true, version: '0.0.0' }));
|
||||
write('package-lock.json', JSON.stringify({ name: 'release-fixture', version: '0.0.0', lockfileVersion: 3, packages: { '': { name: 'release-fixture', version: '0.0.0' } } }));
|
||||
git('init', '-q', '-b', 'main');
|
||||
git('config', 'user.name', 'Release test');
|
||||
git('config', 'user.email', 'release@example.invalid');
|
||||
git('config', 'core.hooksPath', '/dev/null');
|
||||
git('init', '--bare', '-q', 'remote.git');
|
||||
git('remote', 'add', 'origin', path.join(cwd, 'remote.git'));
|
||||
git('add', '.');
|
||||
commit('chore: initialize fixture');
|
||||
mustFail(prepare('0.1.0')); // main cannot prepare releases.
|
||||
git('switch', '-qc', 'release/test');
|
||||
write('untracked.txt', 'must block\n');
|
||||
mustFail(prepare('0.1.0'));
|
||||
fs.unlinkSync(path.join(cwd, 'untracked.txt'));
|
||||
const before = ['package.json', 'package-lock.json', 'CHANGELOG.md'].map(read);
|
||||
const head = git('rev-parse', 'HEAD');
|
||||
mustPass(prepare('0.1.0', '--dry-run'));
|
||||
assert.deepEqual(['package.json', 'package-lock.json', 'CHANGELOG.md'].map(read), before);
|
||||
assert.equal(git('rev-parse', 'HEAD'), head);
|
||||
mustPass(prepare('0.1.0'));
|
||||
assert.equal(git('tag', '--list'), '', 'Preparation must not create tags');
|
||||
assert.equal(git('log', '-1', '--format=%s'), 'chore(release): 0.1.0');
|
||||
mustFail(prepare()); // Prepared version cannot be bumped again before tagging.
|
||||
git('tag', '-a', 'v0.1.0', '-m', 'Release 0.1.0');
|
||||
mustFail(prepare()); // No new commits.
|
||||
for (const [message, expected] of [
|
||||
['fix: correct calculation', '0.1.1'],
|
||||
['feat: add export', '0.2.0'],
|
||||
['docs: explain export', '0.2.1'],
|
||||
['spec: define export', '0.2.2'],
|
||||
['chore(deps): update dependencies', '0.2.3'],
|
||||
['fix!: remove legacy contract', '1.0.0'],
|
||||
['feat: change export\n\nBREAKING CHANGE: replace export format', '2.0.0'],
|
||||
]) {
|
||||
commit(message);
|
||||
mustPass(prepare());
|
||||
const pkg = JSON.parse(read('package.json'));
|
||||
const lock = JSON.parse(read('package-lock.json'));
|
||||
assert.equal(pkg.version, expected);
|
||||
assert.equal(lock.version, expected);
|
||||
assert.equal(lock.packages[''].version, expected);
|
||||
assert.ok(read('CHANGELOG.md').includes(expected));
|
||||
assert.equal(git('status', '--porcelain'), '');
|
||||
git('tag', '-a', `v${expected}`, '-m', `Release ${expected}`);
|
||||
}
|
||||
assert.ok(read('CHANGELOG.md').includes('/commit/'), read('CHANGELOG.md'));
|
||||
assert.ok(read('CHANGELOG.md').includes('/compare/v0.1.0...v0.1.1'));
|
||||
assert.ok(!read('CHANGELOG.md').includes('chore(release)'));
|
||||
assert.equal(read('CHANGELOG.md').split('correct calculation').length, 2, 'Prior changes must not be repeated');
|
||||
assert.equal(spawnSync('git', ['--git-dir=remote.git', 'show-ref'], { cwd }).status, 1, 'Preparation must not push refs');
|
||||
const lint = message => spawnSync(process.execPath,
|
||||
[path.join(source, 'node_modules/@commitlint/cli/cli.js'), '--strict'],
|
||||
{ cwd, env, input: message, encoding: 'utf8' });
|
||||
mustPass(lint('spec: define contract'));
|
||||
mustFail(lint('unstructured message'));
|
||||
console.log(`PASS: release preparation, guards, version rules, lockfile and changelog (${source})`);
|
||||
} finally {
|
||||
fs.rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
"""Check root tracking on fictional data; never stage the real workspace."""
|
||||
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
||||
def check(ignore_rules):
|
||||
with tempfile.TemporaryDirectory(prefix="wealthledger-tracking-") as directory:
|
||||
root = Path(directory)
|
||||
|
||||
def git(*args):
|
||||
return subprocess.check_output(
|
||||
["git", "-C", str(root), *args], text=True
|
||||
)
|
||||
|
||||
git("init", "--quiet")
|
||||
allowed = {
|
||||
'.ai/conventions/git.md',
|
||||
'.ai/conventions/dependencies.md',
|
||||
'.ai/conventions/code-health.md',
|
||||
'.ai/conventions/testing.md',
|
||||
'.ai/conventions/guardrails.md',
|
||||
'.ai/migrations/conventions-v1/audit.md',
|
||||
'.ai/migrations/conventions-v1/plan.md',
|
||||
'.gitea/workflows/commits.yml',
|
||||
'.gitea/workflows/security.yml',
|
||||
'.gitea/workflows/verify.yml',
|
||||
'.gitignore',
|
||||
'.nvmrc',
|
||||
'.release-it.json',
|
||||
'AGENTS.md',
|
||||
'CHANGELOG.md',
|
||||
'README.md',
|
||||
'commitlint.config.cjs',
|
||||
'package-lock.json',
|
||||
'package.json',
|
||||
'renovate.json',
|
||||
'scripts/check-release.cjs',
|
||||
'test-release.cjs',
|
||||
'test-workspace-tracking.py',
|
||||
}
|
||||
for name in allowed:
|
||||
(root / name).parent.mkdir(parents=True, exist_ok=True)
|
||||
(root / name).write_text("fictional fixture\n")
|
||||
(root / ".gitignore").write_text(ignore_rules)
|
||||
for name in ("frontend", "backend", "docs", "specs"):
|
||||
nested = root / name
|
||||
nested.mkdir()
|
||||
subprocess.run(["git", "init", "--quiet", str(nested)], check=True)
|
||||
for name in (
|
||||
"frontend/source.ts", "backend/prisma/dev.db", "docs/README.md",
|
||||
"specs/README.md", "statement-imports/report.csv", "private.db",
|
||||
"private.db-wal", ".env", ".env.example", "audit.pdf",
|
||||
"design/preview.html", "todo/notes.md", "references/source.md",
|
||||
".agents/local.md", "unknown-directory/data.txt", "unknown-file.txt",
|
||||
"scripts/private.txt", ".gitea/private.txt", ".gitea/workflows/private.yml",
|
||||
"node_modules/private.txt",
|
||||
".ai/private.md", ".ai/conventions/private.md",
|
||||
".ai/migrations/private.md", ".ai/migrations/conventions-v1/private.md",
|
||||
".ai/migrations/other/private.md",
|
||||
):
|
||||
target = root / name
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text("fictional private data\n")
|
||||
before = {p.relative_to(root): p.read_bytes() for p in root.rglob("*")
|
||||
if p.is_file() and ".git" not in p.relative_to(root).parts[:1]}
|
||||
git("add", ".")
|
||||
assert set(git("ls-files").splitlines()) == allowed
|
||||
assert all((root / p).read_bytes() == content for p, content in before.items())
|
||||
assert all((root / name / ".git").is_dir()
|
||||
for name in ("frontend", "backend", "docs", "specs"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
check(Path(__file__).with_name(".gitignore").read_text())
|
||||
print("PASS: only root allowlist staged; fictional data and nested Git preserved")
|
||||
Reference in New Issue
Block a user