chore(release): 0.1.0 #8
@@ -0,0 +1,26 @@
|
|||||||
|
name: Conventional commits
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened, edited]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
commits:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: https://github.com/actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: https://github.com/actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version-file: .nvmrc
|
||||||
|
- run: npm ci --ignore-scripts
|
||||||
|
- name: Check PR title and commits
|
||||||
|
env:
|
||||||
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
|
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"
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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
|
||||||
+19
-1
@@ -1,6 +1,24 @@
|
|||||||
# Root owns only these files; nested repositories and local data stay local.
|
# Root owns only explicitly allowed files; nested repositories and data stay local.
|
||||||
/*
|
/*
|
||||||
!/.gitignore
|
!/.gitignore
|
||||||
!/AGENTS.md
|
!/AGENTS.md
|
||||||
!/README.md
|
!/README.md
|
||||||
!/test-workspace-tracking.py
|
!/test-workspace-tracking.py
|
||||||
|
!/.nvmrc
|
||||||
|
!/package.json
|
||||||
|
!/package-lock.json
|
||||||
|
!/.release-it.json
|
||||||
|
!/CHANGELOG.md
|
||||||
|
!/commitlint.config.cjs
|
||||||
|
!/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,106 @@
|
|||||||
|
{
|
||||||
|
"git": {
|
||||||
|
"requireBranch": "release/*",
|
||||||
|
"requireCleanWorkingDir": true,
|
||||||
|
"requireUpstream": false,
|
||||||
|
"requireCommits": true,
|
||||||
|
"commitMessage": "chore(release): ${version}",
|
||||||
|
"tagName": "v${version}",
|
||||||
|
"tag": false,
|
||||||
|
"push": false
|
||||||
|
},
|
||||||
|
"npm": {
|
||||||
|
"publish": false
|
||||||
|
},
|
||||||
|
"github": {
|
||||||
|
"release": false
|
||||||
|
},
|
||||||
|
"gitlab": {
|
||||||
|
"release": false
|
||||||
|
},
|
||||||
|
"hooks": {
|
||||||
|
"after:git:init": "node scripts/check-release.cjs"
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"@release-it/conventional-changelog": {
|
||||||
|
"infile": "CHANGELOG.md",
|
||||||
|
"preset": {
|
||||||
|
"name": "conventionalcommits",
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "chore",
|
||||||
|
"scope": "release",
|
||||||
|
"effect": "hidden"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "feat",
|
||||||
|
"section": "Features",
|
||||||
|
"effect": "bump"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "fix",
|
||||||
|
"section": "Bug Fixes",
|
||||||
|
"effect": "bump"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "perf",
|
||||||
|
"section": "Performance",
|
||||||
|
"effect": "bump"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "docs",
|
||||||
|
"section": "Documentation",
|
||||||
|
"effect": "bump"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "spec",
|
||||||
|
"section": "Specifications",
|
||||||
|
"effect": "bump"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "refactor",
|
||||||
|
"section": "Refactoring",
|
||||||
|
"effect": "bump"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"section": "Build",
|
||||||
|
"effect": "bump"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "ci",
|
||||||
|
"section": "CI",
|
||||||
|
"effect": "bump"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"section": "Tests",
|
||||||
|
"effect": "bump"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "style",
|
||||||
|
"section": "Styles",
|
||||||
|
"effect": "bump"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "chore",
|
||||||
|
"section": "Maintenance",
|
||||||
|
"effect": "bump"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "revert",
|
||||||
|
"section": "Reverts",
|
||||||
|
"effect": "bump"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"host": "https://git.personal.ksv741.ru",
|
||||||
|
"owner": "WealthLedger",
|
||||||
|
"repository": "main",
|
||||||
|
"linkReferences": true,
|
||||||
|
"commit": "commit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,3 +20,10 @@ Root — отдельный Git-репозиторий `WealthLedger/main`; ко
|
|||||||
7. Проверь результат по задаче, рискованным случаям и local gates. Сохрани краткие команды/результаты; сообщи оставшиеся ограничения. Успех lint/tests не заменяет semantic review.
|
7. Проверь результат по задаче, рискованным случаям и local gates. Сохрани краткие команды/результаты; сообщи оставшиеся ограничения. Успех lint/tests не заменяет semantic review.
|
||||||
|
|
||||||
Перед Node-командой активируй `.nvmrc` affected repo через `nvm use --silent`.
|
Перед Node-командой активируй `.nvmrc` affected repo через `nvm use --silent`.
|
||||||
|
|
||||||
|
## Ветки и релизы
|
||||||
|
|
||||||
|
Новая работа — в отдельной ветке от `develop`, затем PR в `develop`.
|
||||||
|
Прямой push в `main` запрещён. Commits и заголовки PR — Conventional Commits.
|
||||||
|
Перед release/hotfix, merge между `main` и `develop`, изменением release-конфигурации
|
||||||
|
или CI refs прочитай [процесс релиза](README.md#релизы-и-ветки).
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.0 (2026-09-14)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
* document workspace and safe onboarding ([9f5baed](https://git.personal.ksv741.ru/WealthLedger/main/commit/9f5baed3a318cacd615125026612f5284e890744))
|
||||||
|
* update workspace documentation routes ([f5d3640](https://git.personal.ksv741.ru/WealthLedger/main/commit/f5d3640168b6f622d2526fdcb06c0a893b4c0987))
|
||||||
|
|
||||||
|
### Maintenance
|
||||||
|
|
||||||
|
* configure releases and develop workflow ([50589c6](https://git.personal.ksv741.ru/WealthLedger/main/commit/50589c63254bb53997e775c49e4931bbf78f7432))
|
||||||
@@ -38,8 +38,9 @@ git clone ssh://git@git.personal.ksv741.ru:2222/WealthLedger/specs.git specs
|
|||||||
|
|
||||||
### Что хранится в main
|
### Что хранится в main
|
||||||
|
|
||||||
Root отслеживает только `.gitignore`, `AGENTS.md`, `README.md` и
|
Root отслеживает входные документы, npm/release-конфигурацию, changelog,
|
||||||
`test-workspace-tracking.py`. Остальные root-пути игнорируются по умолчанию:
|
проверки tracking/release и три CI workflow. Точный allowlist — в `.gitignore`.
|
||||||
|
Остальные root-пути игнорируются по умолчанию:
|
||||||
вложенные repos, `.env`, базы и WAL, импорты выписок, PDF, `references/`,
|
вложенные repos, `.env`, базы и WAL, импорты выписок, PDF, `references/`,
|
||||||
`design/`, `todo/` и локальные настройки инструментов.
|
`design/`, `todo/` и локальные настройки инструментов.
|
||||||
Новый общий файл включается отдельной правкой allowlist после проверки содержимого.
|
Новый общий файл включается отдельной правкой allowlist после проверки содержимого.
|
||||||
@@ -53,8 +54,9 @@ Root отслеживает только `.gitignore`, `AGENTS.md`, `README.md`
|
|||||||
## Подготовка и запуск
|
## Подготовка и запуск
|
||||||
|
|
||||||
Нужны nvm и Node из `.nvmrc` каждого npm-repo (сейчас 24.21.0, npm 11.19).
|
Нужны nvm и Node из `.nvmrc` каждого npm-repo (сейчас 24.21.0, npm 11.19).
|
||||||
Root не содержит npm-проекта. В frontend, backend и docs отдельно выполните
|
Во всех пяти репозиториях отдельно выполните
|
||||||
`nvm install`, `nvm use --silent`, `npm ci`.
|
`nvm install`, `nvm use --silent`, `npm ci`. В root и specs npm нужен для release-it
|
||||||
|
и проверки сообщений коммитов.
|
||||||
После смены major Node переустановите native dependencies через `npm ci`.
|
После смены major Node переустановите native dependencies через `npm ci`.
|
||||||
Разрешения install scripts заданы в `package.json` соответствующего repo.
|
Разрешения install scripts заданы в `package.json` соответствующего repo.
|
||||||
|
|
||||||
@@ -94,7 +96,7 @@ npm run dev
|
|||||||
### OpenSpec
|
### OpenSpec
|
||||||
|
|
||||||
OpenSpec не нужен для запуска приложения и применяется по явному запросу.
|
OpenSpec не нужен для запуска приложения и применяется по явному запросу.
|
||||||
Specs использует Node из `frontend/.nvmrc`; проверенная версия CLI — 1.10.0.
|
Specs использует Node из своей `.nvmrc`; проверенная версия CLI — 1.10.0.
|
||||||
Store `wealth-ledger-specs` расположен в `specs/openspec/`.
|
Store `wealth-ledger-specs` расположен в `specs/openspec/`.
|
||||||
[Работа с OpenSpec](specs/AGENTS.md), [команды validation](specs/README.md#проверка).
|
[Работа с OpenSpec](specs/AGENTS.md), [команды validation](specs/README.md#проверка).
|
||||||
|
|
||||||
@@ -116,3 +118,91 @@ Python 3 нужен только для проверки root tracking. Backend
|
|||||||
T-Bank. Полные команды и ограничения принадлежат README
|
T-Bank. Полные команды и ограничения принадлежат README
|
||||||
[frontend](frontend/README.md#проверка) и [backend](backend/README.md#проверка).
|
[frontend](frontend/README.md#проверка) и [backend](backend/README.md#проверка).
|
||||||
Для изменения проекта начните с [AGENTS](AGENTS.md).
|
Для изменения проекта начните с [AGENTS](AGENTS.md).
|
||||||
|
|
||||||
|
## Релизы и ветки
|
||||||
|
|
||||||
|
Все пять репозиториев выпускаются независимо. Первый релиз — `0.1.0`;
|
||||||
|
версия в `package.json` и lockfile соответствует последнему принятому тегу
|
||||||
|
`vX.Y.Z`. Root-репозиторий называется `main`, его ветки — `main` и `develop`.
|
||||||
|
|
||||||
|
`main` хранит принятые релизы, `develop` — интеграцию разработки. Новая задача:
|
||||||
|
отдельная ветка от свежего `develop`, затем PR в `develop`. Для веток агента
|
||||||
|
используется `codex/<task>`; также допустимы `feature/<task>` и `fix/<task>`.
|
||||||
|
В `main` напрямую не пушить. Обычные PR можно squash-merge; релизные PR и
|
||||||
|
обратную синхронизацию `main` с `develop` сливать с сохранением истории.
|
||||||
|
|
||||||
|
Commits и заголовки PR — Conventional Commits. `feat:` повышает minor,
|
||||||
|
`fix:`/`perf:` — patch, `!` или `BREAKING CHANGE:` — major, в том числе до 1.0.
|
||||||
|
`docs:`, `spec:`, `chore:`, `build:`, `ci:`, `refactor:`, `test:`, `style:` и
|
||||||
|
`revert:` дают patch при выпуске. `chore(release):` — служебная запись,
|
||||||
|
исключённая из changelog и рекомендации версии. Release-it не запускается
|
||||||
|
автоматически после каждого commit. Номер связанной issue можно добавить в заголовок.
|
||||||
|
|
||||||
|
### Подготовка релиза
|
||||||
|
|
||||||
|
1. Обновить `main`, `develop` и теги командой `git fetch origin --tags`.
|
||||||
|
Проверить, что текущий `develop` содержит последний релиз из `main`.
|
||||||
|
Создать `release/<name>` от проверенного `develop` и выполнить `npm ci`.
|
||||||
|
2. Для backend и docs обновить `.ci/refs.json`: полные SHA совместимых соседних
|
||||||
|
репозиториев. Эти revisions должны быть доступны в Gitea. Проверить именно
|
||||||
|
их совместимость; движущиеся ветки не являются фиксацией состава релиза.
|
||||||
|
Закоммитить обновлённые pins до подготовки версии.
|
||||||
|
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. Незакоммиченные файлы,
|
||||||
|
неподходящая ветка, отсутствие изменений и уже подготовленная версия блокируют выпуск.
|
||||||
|
4. Проверить diff релизного коммита и штатные gates. Отправить только релизную
|
||||||
|
ветку: `git push -u origin HEAD`. Открыть PR в `main` с заголовком
|
||||||
|
`chore(release): X.Y.Z`. После зелёных `verify`, `security`, `commits`
|
||||||
|
выполнить **fast-forward-only merge**. Если `main` изменился, сначала
|
||||||
|
согласовать историю и повторить проверку кандидата; не обходить защиту.
|
||||||
|
5. После merge получить `main` и убедиться, что его HEAD — проверенный релизный
|
||||||
|
коммит, версия и lockfile совпадают. На этом SHA создать аннотированный тег:
|
||||||
|
`git tag -a vX.Y.Z <release-sha> -m "Release X.Y.Z"`, затем отправить
|
||||||
|
только этот тег: `git push origin refs/tags/vX.Y.Z`. Повторно release-it
|
||||||
|
на `main` не запускать: он уже подготовил версию.
|
||||||
|
6. Через PR `main → develop` вернуть релизный коммит в разработку без squash
|
||||||
|
и rebase. Убедиться, что `develop` содержит тег. После первого выпуска
|
||||||
|
`develop` создаётся непосредственно от релизного SHA.
|
||||||
|
|
||||||
|
Перед публикацией тегов сравнить local SHA с удалённым `main`; PR и проверки
|
||||||
|
должны относиться к этому кандидату. Опубликованные теги не перемещать.
|
||||||
|
При сбое после merge продолжить с создания отсутствующего тега, без нового bump.
|
||||||
|
При сбое до merge исправить релизную ветку и заново проверить кандидат;
|
||||||
|
подготовленную версию и changelog не повышать второй раз. Пять репозиториев
|
||||||
|
не имеют общей атомарной публикации: завершённость проверяется отдельно по каждому.
|
||||||
|
|
||||||
|
Первый выпуск задаётся явно: `npm run release:prepare -- 0.1.0` из
|
||||||
|
`release/0.1.0`. Начальные `0.0.0` — bootstrap-состояние до первого тега.
|
||||||
|
Исторические неконвенциональные commits не переписываются; первый changelog
|
||||||
|
требует просмотра человеком. Дальнейшие версии выбираются по commits.
|
||||||
|
|
||||||
|
### Hotfix, проверки и защита
|
||||||
|
|
||||||
|
Срочный fix начинается от `main`, проходит проверки и релизный PR из
|
||||||
|
`release/hotfix-<name>` в `main`, затем обязательно возвращается в `develop`.
|
||||||
|
Если ломается публичный контракт, major сохраняется и для hotfix.
|
||||||
|
|
||||||
|
Backend CI проверяет runtime OpenAPI с frontend; docs CI — межрепозиторные
|
||||||
|
ссылки. Для `main`, PR в `main` и `release/*` соседи берутся из `.ci/refs.json`;
|
||||||
|
для разработки — из `develop`. SHA записываются в логи. Для изменения сразу
|
||||||
|
нескольких репозиториев сначала согласовать их revisions, затем проверять
|
||||||
|
релизный набор; порядок публикации сам по себе не доказывает совместимость.
|
||||||
|
|
||||||
|
В Gitea для `main` и `develop` нужны запрет прямого push и force-push,
|
||||||
|
обязательные проверки `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`.
|
||||||
|
|
||||||
|
Npm publishing, автоматический deploy и карточки Gitea Releases не включены.
|
||||||
|
Граница выпуска — принятый коммит, changelog и Git-тег.
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ["@commitlint/config-conventional"],
|
||||||
|
rules: { "type-enum": [2, "always", ["feat", "fix", "perf", "docs", "spec", "refactor", "build", "ci", "test", "style", "chore", "revert"]] },
|
||||||
|
};
|
||||||
Generated
+3634
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "wealth-ledger-workspace",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@commitlint/cli": "21.2.2",
|
||||||
|
"@commitlint/config-conventional": "21.2.2",
|
||||||
|
"@release-it/conventional-changelog": "12.0.0",
|
||||||
|
"conventional-changelog-conventionalcommits": "10.4.0",
|
||||||
|
"release-it": "21.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:recommended",
|
||||||
|
":semanticCommitTypeAll(chore)"
|
||||||
|
],
|
||||||
|
"timezone": "Europe/Moscow",
|
||||||
|
"labels": [
|
||||||
|
"dependencies"
|
||||||
|
],
|
||||||
|
"dependencyDashboard": true,
|
||||||
|
"rangeStrategy": "pin",
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"matchUpdateTypes": [
|
||||||
|
"minor",
|
||||||
|
"patch"
|
||||||
|
],
|
||||||
|
"groupName": "non-major dependencies"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matchUpdateTypes": [
|
||||||
|
"major"
|
||||||
|
],
|
||||||
|
"dependencyDashboardApproval": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"baseBranchPatterns": [
|
||||||
|
"develop"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
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.");
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
// 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 });
|
||||||
|
}
|
||||||
@@ -15,8 +15,26 @@ def check(ignore_rules):
|
|||||||
)
|
)
|
||||||
|
|
||||||
git("init", "--quiet")
|
git("init", "--quiet")
|
||||||
allowed = {".gitignore", "AGENTS.md", "README.md", "test-workspace-tracking.py"}
|
allowed = {
|
||||||
|
'.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:
|
for name in allowed:
|
||||||
|
(root / name).parent.mkdir(parents=True, exist_ok=True)
|
||||||
(root / name).write_text("fictional fixture\n")
|
(root / name).write_text("fictional fixture\n")
|
||||||
(root / ".gitignore").write_text(ignore_rules)
|
(root / ".gitignore").write_text(ignore_rules)
|
||||||
for name in ("frontend", "backend", "docs", "specs"):
|
for name in ("frontend", "backend", "docs", "specs"):
|
||||||
@@ -29,6 +47,8 @@ def check(ignore_rules):
|
|||||||
"private.db-wal", ".env", ".env.example", "audit.pdf",
|
"private.db-wal", ".env", ".env.example", "audit.pdf",
|
||||||
"design/preview.html", "todo/notes.md", "references/source.md",
|
"design/preview.html", "todo/notes.md", "references/source.md",
|
||||||
".agents/local.md", "unknown-directory/data.txt", "unknown-file.txt",
|
".agents/local.md", "unknown-directory/data.txt", "unknown-file.txt",
|
||||||
|
"scripts/private.txt", ".gitea/private.txt", ".gitea/workflows/private.yml",
|
||||||
|
"node_modules/private.txt",
|
||||||
):
|
):
|
||||||
target = root / name
|
target = root / name
|
||||||
target.parent.mkdir(parents=True, exist_ok=True)
|
target.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user