diff --git a/.ci/release/release-manifest.json b/.ci/release/release-manifest.json index 7907dbc..69b6780 100644 --- a/.ci/release/release-manifest.json +++ b/.ci/release/release-manifest.json @@ -1,9 +1,37 @@ { - "version": "1.1.0", - "tag": "v1.1.0", - "publishedAt": "2026-05-12T10:30:14.398Z", - "releaseUrl": "https://git.shts.su/denozord/MikrotikManager-3/releases/tag/v1.1.0", + "version": "1.3.1", + "tag": "v1.3.1", + "publishedAt": "2026-05-12T18:06:29+07:00", + "releaseUrl": "https://git.shts.su/denozord/MikrotikManager-3/releases/tag/v1.3.1", "commits": [ + { + "sha": "0435608ef9b0aa792afb7d678856c60058a40659", + "shortSha": "0435608", + "subject": "chore: обновить правила коммитов и улучшить форматирование релизных заметок", + "author": "Denozordec", + "type": "chore" + }, + { + "sha": "c2a8a08618db0ba7a98635637d88be9d3f1d8ce1", + "shortSha": "c2a8a08", + "subject": "feat: обновить название проекта и улучшить документацию", + "author": "Denozordec", + "type": "feat" + }, + { + "sha": "35876fcd6fd0d13e0419c30c9d112b5a4017ae67", + "shortSha": "35876fc", + "subject": "feat: update Dockerfile and release management scripts", + "author": "Denozordec", + "type": "feat" + }, + { + "sha": "7207d1b74992dabaf35cfa9208aa3c7becf42cbd", + "shortSha": "7207d1b", + "subject": "feat: update release notes and manifest for v1.1.0", + "author": "Denozordec", + "type": "feat" + }, { "sha": "4b92a6eb81ce64fad2ca61f5fa94342959a45349", "shortSha": "4b92a6e", @@ -17,174 +45,6 @@ "subject": "feat: update application versioning and enhance release management", "author": "Denozordec", "type": "feat" - }, - { - "sha": "f282585b525fe787ab6bdbd2f83c9c848b376771", - "shortSha": "f282585", - "subject": "feat: enhance network configuration handling in updater script", - "author": "Denozordec", - "type": "feat" - }, - { - "sha": "40e2040fb05717283d0ee2b7d0a6b3315abecf73", - "shortSha": "40e2040", - "subject": "feat: refactor theme management and remove next-themes dependency", - "author": "Denozordec", - "type": "feat" - }, - { - "sha": "bb07ba0f69966e4b5533f6b4f0d7d96cef0916f1", - "shortSha": "bb07ba0", - "subject": "feat: implement database backup and restore functionality in settings page", - "author": "Denozordec", - "type": "feat" - }, - { - "sha": "1fefed2aa0dbd51f893750e2e179f65bd9386372", - "shortSha": "1fefed2", - "subject": "chore: enhance data source configuration and mock data handling", - "author": "Denozordec", - "type": "chore" - }, - { - "sha": "4489dfb2d62e836b5415b9ead409851bef995eaf", - "shortSha": "4489dfb", - "subject": "chore: enhance frontend configuration for backend integration", - "author": "Denozordec", - "type": "chore" - }, - { - "sha": "4b240d70d0fbc2143cc657f78f932d003ad5d406", - "shortSha": "4b240d7", - "subject": "chore: add server and server_snapshots tables to database schema", - "author": "Denozordec", - "type": "chore" - }, - { - "sha": "63252ae85ed03b772ede2be97a27f1605cc16d0d", - "shortSha": "63252ae", - "subject": "chore: enhance image reference resolution in updater script", - "author": "Denozordec", - "type": "chore" - }, - { - "sha": "a359874a42add6b0f4f2f4844ba6e19268bdc05f", - "shortSha": "a359874", - "subject": "chore: enhance Dockerfile for backend to rebuild better-sqlite3", - "author": "Denozordec", - "type": "chore" - }, - { - "sha": "4ec247f2c02edfccd775c3e5c644c28844358c90", - "shortSha": "4ec247f", - "subject": "chore: update Docker Compose configuration for data volume management", - "author": "Denozordec", - "type": "chore" - }, - { - "sha": "2697a35c0a719396fbe5fdaf6045cb9ca49c28fa", - "shortSha": "2697a35", - "subject": "chore: streamline Docker workflow and enhance build efficiency", - "author": "Denozordec", - "type": "chore" - }, - { - "sha": "7295545cde2fc7b0674ba17b3447d89be2f85218", - "shortSha": "7295545", - "subject": "chore: add updater image configuration to Docker workflow", - "author": "Denozordec", - "type": "chore" - }, - { - "sha": "a5b5a2a3d390bf4f6fb0986a917d43532844a346", - "shortSha": "a5b5a2a", - "subject": "chore: enhance Dockerfiles and package.json for improved build process", - "author": "Denozordec", - "type": "chore" - }, - { - "sha": "01596864dc7801275eefcb67aad67b58785d3a28", - "shortSha": "0159686", - "subject": "chore: update Dockerfiles to ignore scripts during npm install", - "author": "Denozordec", - "type": "chore" - }, - { - "sha": "02119a3acde256a92edd06cf4d00337bb53088ee", - "shortSha": "02119a3", - "subject": "chore: configure dynamic image naming for frontend and backend in Docker workflow", - "author": "Denozordec", - "type": "chore" - }, - { - "sha": "c36994c36b78063068aff5fa509d1d26c522de51", - "shortSha": "c36994c", - "subject": "chore: update configuration and dependencies", - "author": "Denozordec", - "type": "chore" - }, - { - "sha": "dfbaa859ad7616159e373ca50632efd2ad77e8e3", - "shortSha": "dfbaa85", - "subject": "chore: isolate frontend and backend via npm workspaces and @mmapp/contracts", - "author": "Denozordec", - "type": "chore" - }, - { - "sha": "b9a75b6831e4ef27726bedbab37f385366c33067", - "shortSha": "b9a75b6", - "subject": "feat: implement internet path functionality with backend support", - "author": "Denozordec", - "type": "feat" - }, - { - "sha": "11ad94f67d4a3f5567d3e6e9cd8fe7a587e86df1", - "shortSha": "11ad94f", - "subject": "feat: integrate sonner for toast notifications and enhance UI feedback", - "author": "Denozordec", - "type": "feat" - }, - { - "sha": "84ecd4f0619cb434918e3d36d1d2d8d61dc0c759", - "shortSha": "84ecd4f", - "subject": "feat: enhance backups page with live data loading and backup job management", - "author": "Denozordec", - "type": "feat" - }, - { - "sha": "6d8379501caf263fa2def609c91601d8b4bb4298", - "shortSha": "6d83795", - "subject": "refactor: replace custom API fetch logic with requestJson utility across multiple pages", - "author": "Denozordec", - "type": "refactor" - }, - { - "sha": "5f31bb47fb11592ef1dd1c576105efb07cff584e", - "shortSha": "5f31bb4", - "subject": "chore: synchronize pending app/backend updates and repository hygiene", - "author": "Denozordec", - "type": "chore" - }, - { - "sha": "bdb9b72fac5e9cf03faa6e8df463759a9defbf24", - "shortSha": "bdb9b72", - "subject": "Init 2", - "author": "Denozordec", - "type": "other" - }, - { - "sha": "ce00c4c671834df0c3277480ecf1a7bbd03cf3f4", - "shortSha": "ce00c4c", - "subject": "Update .gitignore to include additional build and log files", - "author": "Denozordec", - "type": "other" - }, - { - "sha": "f3f831653f48c24ae02d15cf10b3e929d4d63e14", - "shortSha": "f3f8316", - "subject": "Init commit", - "author": "Denozordec", - "type": "other" } ] } diff --git a/.ci/release/release_notes.md b/.ci/release/release_notes.md index 8deb15f..e69de29 100644 --- a/.ci/release/release_notes.md +++ b/.ci/release/release_notes.md @@ -1,41 +0,0 @@ -# v1.1.0 - -Версия **1.1.0**. - -## feat - -- feat: release v1.1.0 with enhancements and refactoring (`4b92a6e`, Denozordec) -- feat: update application versioning and enhance release management (`9ccc845`, Denozordec) -- feat: enhance network configuration handling in updater script (`f282585`, Denozordec) -- feat: refactor theme management and remove next-themes dependency (`40e2040`, Denozordec) -- feat: implement database backup and restore functionality in settings page (`bb07ba0`, Denozordec) -- feat: implement internet path functionality with backend support (`b9a75b6`, Denozordec) -- feat: integrate sonner for toast notifications and enhance UI feedback (`11ad94f`, Denozordec) -- feat: enhance backups page with live data loading and backup job management (`84ecd4f`, Denozordec) - -## chore - -- chore: enhance data source configuration and mock data handling (`1fefed2`, Denozordec) -- chore: enhance frontend configuration for backend integration (`4489dfb`, Denozordec) -- chore: add server and server_snapshots tables to database schema (`4b240d7`, Denozordec) -- chore: enhance image reference resolution in updater script (`63252ae`, Denozordec) -- chore: enhance Dockerfile for backend to rebuild better-sqlite3 (`a359874`, Denozordec) -- chore: update Docker Compose configuration for data volume management (`4ec247f`, Denozordec) -- chore: streamline Docker workflow and enhance build efficiency (`2697a35`, Denozordec) -- chore: add updater image configuration to Docker workflow (`7295545`, Denozordec) -- chore: enhance Dockerfiles and package.json for improved build process (`a5b5a2a`, Denozordec) -- chore: update Dockerfiles to ignore scripts during npm install (`0159686`, Denozordec) -- chore: configure dynamic image naming for frontend and backend in Docker workflow (`02119a3`, Denozordec) -- chore: update configuration and dependencies (`c36994c`, Denozordec) -- chore: isolate frontend and backend via npm workspaces and @mmapp/contracts (`dfbaa85`, Denozordec) -- chore: synchronize pending app/backend updates and repository hygiene (`5f31bb4`, Denozordec) - -## refactor - -- refactor: replace custom API fetch logic with requestJson utility across multiple pages (`6d83795`, Denozordec) - -## other - -- Init 2 (`bdb9b72`, Denozordec) -- Update .gitignore to include additional build and log files (`ce00c4c`, Denozordec) -- Init commit (`f3f8316`, Denozordec) diff --git a/.ci/scripts/compute-release.mjs b/.ci/scripts/compute-release.mjs index 686e313..1f0b3d3 100644 --- a/.ci/scripts/compute-release.mjs +++ b/.ci/scripts/compute-release.mjs @@ -13,6 +13,7 @@ const outputDir = process.env.RELEASE_OUTPUT_DIR const repository = process.env.GITEA_REPOSITORY ?? process.env.GITHUB_REPOSITORY ?? "" const serverUrl = (process.env.GITEA_SERVER_URL ?? "https://git.shts.su").replace(/\/$/, "") +const MANIFEST_COMMIT_LIMIT = Number(process.env.RELEASE_MANIFEST_COMMIT_LIMIT ?? 6) const CONVENTIONAL_RE = /^(feat|fix|chore|docs|refactor|style|test|build|ci)(\([^)]+\))?!?:\s*(.+)$/i const MINOR_TYPES = new Set(["feat"]) @@ -139,6 +140,23 @@ function getCommitsSince(tag) { ) if (!raw) return [] + return parseCommitLog(raw) +} + +function getRecentCommits(limit = MANIFEST_COMMIT_LIMIT) { + const raw = git( + "log", + "-n", + String(limit), + "HEAD", + "--pretty=format:%H%x1f%h%x1f%an%x1f%s%x1f%b%x1e", + ) + if (!raw) return [] + + return parseCommitLog(raw) +} + +function parseCommitLog(raw) { return raw .split("\x1e") .map((entry) => entry.trim()) @@ -210,7 +228,7 @@ function main() { if (commits.length === 0) { const deployTag = latestTag ?? `v${baseFromTag}` const deployVersion = baseFromTag - const displayCommits = latestTag ? getCommitsSince(getPreviousTag(latestTag)) : [] + const displayCommits = getRecentCommits() const publishedAt = latestTag ? getTagPublishedAt(latestTag) : null mkdirSync(outputDir, { recursive: true }) @@ -244,7 +262,7 @@ function main() { tag, publishedAt, releaseUrl, - commits: toManifestCommits(commits), + commits: toManifestCommits(getRecentCommits()), } mkdirSync(outputDir, { recursive: true }) diff --git a/.ci/scripts/ensure-git-hooks.mjs b/.ci/scripts/ensure-git-hooks.mjs new file mode 100644 index 0000000..d31575a --- /dev/null +++ b/.ci/scripts/ensure-git-hooks.mjs @@ -0,0 +1,21 @@ +#!/usr/bin/env node +import { execFileSync, spawnSync } from "node:child_process" +import path from "node:path" +import { fileURLToPath } from "node:url" + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..") +const gitCheck = spawnSync("git", ["rev-parse", "--show-toplevel"], { + cwd: repoRoot, + encoding: "utf8", +}) + +if (gitCheck.status !== 0) { + process.exit(0) +} + +const topLevel = gitCheck.stdout.trim() + +execFileSync("git", ["config", "core.hooksPath", ".githooks"], { + cwd: topLevel, + stdio: "ignore", +}) diff --git a/.ci/scripts/validate-commit-message.mjs b/.ci/scripts/validate-commit-message.mjs new file mode 100644 index 0000000..58db823 --- /dev/null +++ b/.ci/scripts/validate-commit-message.mjs @@ -0,0 +1,62 @@ +#!/usr/bin/env node +import fs from "node:fs" + +const messagePath = process.argv[2] + +if (!messagePath) { + console.error("validate-commit-message: не указан файл сообщения коммита") + process.exit(2) +} + +if (process.env.SKIP_COMMIT_MESSAGE_RU === "1") { + process.exit(0) +} + +const content = fs.readFileSync(messagePath, "utf8") +const subject = content + .split(/\r?\n/) + .map((line) => line.trim()) + .find((line) => line.length > 0 && !line.startsWith("#")) + +if (!subject) { + process.exit(0) +} + +const skipPatterns = [ + /^Merge\b/i, + /^Revert\b/i, + /^fixup!/i, + /^squash!/i, + /^amend!/i, +] + +if (skipPatterns.some((pattern) => pattern.test(subject))) { + process.exit(0) +} + +const cyrillic = /[\u0400-\u04FF\u0500-\u052F]/ + +if (!cyrillic.test(subject)) { + console.error("") + console.error("Коммит отклонён: subject должен быть на русском.") + console.error("Формат: fix(scope): краткое описание на русском") + console.error("Правила: .cursor/rules/commit-messages-ru.mdc") + console.error("Обход только для аварий: SKIP_COMMIT_MESSAGE_RU=1") + console.error("") + console.error(`Subject: ${subject}`) + process.exit(1) +} + +const conventional = + /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([^)]+\))?!?:\s*(.+)$/ + +const match = subject.match(conventional) + +if (match && !cyrillic.test(match[3])) { + console.error("") + console.error("Коммит отклонён: текст после «:» должен быть на русском.") + console.error(`Subject: ${subject}`) + process.exit(1) +} + +process.exit(0) diff --git a/.cursor/rules/commit-messages-ru.mdc b/.cursor/rules/commit-messages-ru.mdc index e6fdf76..1883d52 100644 --- a/.cursor/rules/commit-messages-ru.mdc +++ b/.cursor/rules/commit-messages-ru.mdc @@ -7,9 +7,10 @@ alwaysApply: true При **Generate Commit Message**, автогенерации subject и предложении `git commit`: -## Язык +## Обязательно -- После `:` — **только русский**. Английский в subject/body запрещён. +- Subject и body — **только на русском** (после `:` в conventional commit). +- Локальный hook `.githooks/commit-msg` **отклоняет** коммиты без кириллицы в subject. - Префикс и scope — английский: `fix(ui):`, `chore(rules):`. ## Тип: сначала не `feat` diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 0000000..4f75f4a --- /dev/null +++ b/.cursorrules @@ -0,0 +1,10 @@ +# Git commit message (Generate Commit Message) + +Всегда генерируй subject коммита на русском. + +- Формат: `тип(scope): описание на русском` — тип и scope на английском, текст после `:` только на русском. +- По умолчанию не `feat`: правки UI/багов/CI → `fix`; правила/README/workflow → `chore`/`ci`; `feat` только для новой пользовательской возможности. +- Примеры: `fix(ui): выровнять модалку релиза`; `chore(rules): усилить правила commit message`. +- Запрещено: `feat: update layout`, `fix: add modal`, любой английский subject. + +Подробнее: `.cursor/rules/commit-messages-ru.mdc`, semver: `.cursor/rules/release-versioning.mdc`. diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100644 index 0000000..4be0979 --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,3 @@ +#!/bin/sh +set -e +node "$(dirname "$0")/../.ci/scripts/validate-commit-message.mjs" "$1" diff --git a/AGENTS.md b/AGENTS.md index 63b163a..23817f0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,5 +7,6 @@ 1. Subject после `:` — **только на русском**. 2. **`feat` не по умолчанию.** Правка UI/бага/CI → `fix`; правила/README/workflow → `chore`/`ci`; новая пользовательская фича → `feat`. 3. Формат: `fix(ui): выровнять модалку релиза`. +4. Локальный hook `.githooks/commit-msg` отклоняет subject без кириллицы; подключение — `npm install` / `npm run prepare`. Полные правила: `.cursor/rules/commit-messages-ru.mdc`, semver — `.cursor/rules/release-versioning.mdc`. diff --git a/app/(main)/releases/page.tsx b/app/(main)/releases/page.tsx index a04f385..69878bf 100644 --- a/app/(main)/releases/page.tsx +++ b/app/(main)/releases/page.tsx @@ -3,6 +3,11 @@ import { PageHeader } from "@/components/page-header" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { buttonVariants } from "@/components/ui/button" import { formatAppVersionLabel, getAppVersion, getReleaseUrl } from "@/lib/app-version" +import { + formatCommitSubject, + getReleaseCommitPreview, + RELEASE_COMMIT_PREVIEW_LIMIT, +} from "@/lib/release-commits" import { readReleaseManifest } from "@/lib/release-manifest" import { cn } from "@/lib/utils" import { ExternalLinkIcon } from "lucide-react" @@ -24,7 +29,7 @@ export default function ReleasesPage() { const version = getAppVersion() const isProdBuild = !version.endsWith("-dev") const releaseUrl = getReleaseUrl() || manifest.releaseUrl || null - const commits = manifest.commits + const commits = getReleaseCommitPreview(manifest.commits) return (
@@ -68,7 +73,7 @@ export default function ReleasesPage() { Сводка коммитов {commits.length > 0 - ? `Изменения, вошедшие в ${formatAppVersionLabel(manifest.version)}.` + ? `Последние ${RELEASE_COMMIT_PREVIEW_LIMIT} коммитов в сборке ${formatAppVersionLabel(manifest.version)}.` : "Для локальной разработки список коммитов пуст. В прод-сборке он заполняется CI."} @@ -84,7 +89,7 @@ export default function ReleasesPage() { {commit.shortSha} {commit.author}
-

{commit.subject}

+

{formatCommitSubject(commit.subject)}

))} diff --git a/components/release-notes-modal.tsx b/components/release-notes-modal.tsx index 11b9960..79b6dab 100644 --- a/components/release-notes-modal.tsx +++ b/components/release-notes-modal.tsx @@ -13,16 +13,14 @@ import { DialogTitle, } from "@/components/ui/dialog" import { formatAppVersionLabel, getAppVersion } from "@/lib/app-version" +import { + formatCommitSubject, + getReleaseCommitPreview, + RELEASE_COMMIT_PREVIEW_LIMIT, +} from "@/lib/release-commits" import type { ReleaseManifest } from "@/lib/release-manifest" import { hasSeenReleaseModal, markReleaseModalSeen } from "@/lib/release-modal-storage" -function formatCommitSubject(subject: string): string { - return subject.replace( - /^(feat|fix|chore|docs|refactor|style|test|build|ci)(\([^)]+\))?!?:\s*/i, - "", - ) -} - export function ReleaseNotesModal() { const [open, setOpen] = React.useState(false) const [manifest, setManifest] = React.useState(null) @@ -60,6 +58,7 @@ export function ReleaseNotesModal() { } const versionLabel = formatAppVersionLabel(manifest?.version ?? getAppVersion()) + const commits = getReleaseCommitPreview(manifest?.commits ?? []) return ( @@ -69,14 +68,14 @@ export function ReleaseNotesModal() { Что нового в {versionLabel} - Краткая сводка изменений текущего релиза MikrotikManager. + Последние {RELEASE_COMMIT_PREVIEW_LIMIT} коммитов текущей сборки MikrotikManager.
- {manifest?.commits.length ? ( + {commits.length ? (
    - {manifest.commits.map((commit) => ( + {commits.map((commit) => (
  • (commits: T[], limit = RELEASE_COMMIT_PREVIEW_LIMIT): T[] { + return commits.slice(0, limit) +} diff --git a/package.json b/package.json index 17c1261..9bd9a45 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "dev": "next dev", "build": "npm run build -w @mmapp/contracts && next build", "postinstall": "npm run build -w @mmapp/contracts", + "prepare": "node .ci/scripts/ensure-git-hooks.mjs", "start": "next start", "lint": "eslint" },