chore(rules): добавить локальный hook для проверки сообщений коммитов
Docker images / prepare-release (push) Successful in 5s
Docker images / backend-image (push) Successful in 2m36s
Docker images / frontend-image (push) Successful in 2m25s
Docker images / updater-image (push) Successful in 37s
Docker images / notify-webhook (push) Has been skipped
Docker images / publish-release (push) Successful in 6s
Docker images / prepare-release (push) Successful in 5s
Docker images / backend-image (push) Successful in 2m36s
Docker images / frontend-image (push) Successful in 2m25s
Docker images / updater-image (push) Successful in 37s
Docker images / notify-webhook (push) Has been skipped
Docker images / publish-release (push) Successful in 6s
This commit is contained in:
@@ -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 })
|
||||
|
||||
@@ -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",
|
||||
})
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user