feat: update application versioning and enhance release management
Docker images / prepare-release (push) Successful in 38s
Docker images / backend-image (push) Has been skipped
Docker images / frontend-image (push) Has been skipped
Docker images / updater-image (push) Has been skipped
Docker images / publish-release (push) Has been skipped
Docker images / notify-webhook (push) Has been skipped
Docker images / prepare-release (push) Successful in 38s
Docker images / backend-image (push) Has been skipped
Docker images / frontend-image (push) Has been skipped
Docker images / updater-image (push) Has been skipped
Docker images / publish-release (push) Has been skipped
Docker images / notify-webhook (push) Has been skipped
- Bumped application version to 1.0.0 across all relevant package files, ensuring consistency in versioning. - Introduced new environment variables for application version and release URL in Dockerfiles, improving deployment transparency. - Enhanced the health check endpoint to return the current application version, providing better visibility for monitoring. - Updated CI/CD workflows to include steps for preparing and publishing releases, streamlining the release process. - Added a new "Releases" section in the application sidebar for easier access to version information.
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
#!/usr/bin/env node
|
||||
import { execFileSync } from "node:child_process"
|
||||
import { mkdirSync, writeFileSync } from "node:fs"
|
||||
import { dirname, join } from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const repoRoot = join(__dirname, "..", "..")
|
||||
const baseVersion = process.env.BASE_VERSION ?? "1.0.0"
|
||||
const outputDir = process.env.RELEASE_OUTPUT_DIR ?? join(repoRoot, ".ci", "release")
|
||||
const repository = process.env.GITEA_REPOSITORY ?? process.env.GITHUB_REPOSITORY ?? ""
|
||||
const serverUrl = (process.env.GITEA_SERVER_URL ?? "https://git.shts.su").replace(/\/$/, "")
|
||||
|
||||
const CONVENTIONAL_RE =
|
||||
/^(feat|fix|chore|docs|refactor|style|test|build|ci)(\([^)]+\))?!?:\s*(.+)$/i
|
||||
const MINOR_TYPES = new Set(["feat"])
|
||||
const PATCH_TYPES = new Set([
|
||||
"fix",
|
||||
"chore",
|
||||
"docs",
|
||||
"refactor",
|
||||
"style",
|
||||
"test",
|
||||
"build",
|
||||
"ci",
|
||||
])
|
||||
|
||||
function git(...args) {
|
||||
return execFileSync("git", args, {
|
||||
cwd: repoRoot,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
}).trim()
|
||||
}
|
||||
|
||||
function parseSemver(version) {
|
||||
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version)
|
||||
if (!match) {
|
||||
throw new Error(`Invalid semver: ${version}`)
|
||||
}
|
||||
return {
|
||||
major: Number(match[1]),
|
||||
minor: Number(match[2]),
|
||||
patch: Number(match[3]),
|
||||
}
|
||||
}
|
||||
|
||||
function formatSemver(parts) {
|
||||
return `${parts.major}.${parts.minor}.${parts.patch}`
|
||||
}
|
||||
|
||||
function bumpVersion(version, bump) {
|
||||
const parts = parseSemver(version)
|
||||
if (bump === "minor") {
|
||||
parts.minor += 1
|
||||
parts.patch = 0
|
||||
return formatSemver(parts)
|
||||
}
|
||||
parts.patch += 1
|
||||
return formatSemver(parts)
|
||||
}
|
||||
|
||||
function parseCommit(subject, body = "") {
|
||||
const match = CONVENTIONAL_RE.exec(subject.trim())
|
||||
const breaking =
|
||||
/BREAKING CHANGE/i.test(body) ||
|
||||
(match?.[1] && subject.includes(`${match[1]}!:`))
|
||||
|
||||
if (!match) {
|
||||
return { type: "other", scope: null, description: subject.trim(), bump: "patch" }
|
||||
}
|
||||
|
||||
const type = match[1].toLowerCase()
|
||||
const scope = match[2]?.slice(1, -1) ?? null
|
||||
const description = match[3].trim()
|
||||
let bump = "patch"
|
||||
|
||||
if (breaking || MINOR_TYPES.has(type)) {
|
||||
bump = "minor"
|
||||
} else if (PATCH_TYPES.has(type)) {
|
||||
bump = "patch"
|
||||
}
|
||||
|
||||
return { type, scope, description, bump }
|
||||
}
|
||||
|
||||
function getLatestTag() {
|
||||
try {
|
||||
const tags = git("tag", "--list", "v*.*.*", "--sort=-v:refname")
|
||||
const first = tags.split("\n").map((line) => line.trim()).find(Boolean)
|
||||
return first ?? null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function getCommitsSince(tag) {
|
||||
const range = tag ? `${tag}..HEAD` : "HEAD"
|
||||
const raw = git(
|
||||
"log",
|
||||
range,
|
||||
"--pretty=format:%H%x1f%h%x1f%an%x1f%s%x1f%b%x1e",
|
||||
)
|
||||
if (!raw) return []
|
||||
|
||||
return raw
|
||||
.split("\x1e")
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean)
|
||||
.map((entry) => {
|
||||
const [sha, shortSha, author, subject, body] = entry.split("\x1f")
|
||||
const parsed = parseCommit(subject, body)
|
||||
return {
|
||||
sha,
|
||||
shortSha,
|
||||
author,
|
||||
subject,
|
||||
type: parsed.type,
|
||||
scope: parsed.scope,
|
||||
bump: parsed.bump,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function groupCommits(commits) {
|
||||
const groups = new Map()
|
||||
for (const commit of commits) {
|
||||
const key = commit.type
|
||||
if (!groups.has(key)) groups.set(key, [])
|
||||
groups.get(key).push(commit)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
function renderReleaseNotes({ tag, version, commits }) {
|
||||
const lines = [`# ${tag}`, "", `Версия **${version}**.`, ""]
|
||||
const groups = groupCommits(commits)
|
||||
const order = ["feat", "fix", "chore", "docs", "refactor", "style", "test", "build", "ci", "other"]
|
||||
|
||||
for (const type of order) {
|
||||
const items = groups.get(type)
|
||||
if (!items?.length) continue
|
||||
lines.push(`## ${type}`, "")
|
||||
for (const item of items) {
|
||||
lines.push(`- ${item.subject} (\`${item.shortSha}\`, ${item.author})`)
|
||||
}
|
||||
lines.push("")
|
||||
}
|
||||
|
||||
return `${lines.join("\n").trim()}\n`
|
||||
}
|
||||
|
||||
function buildReleaseUrl(tag) {
|
||||
if (!repository) return ""
|
||||
const [owner, repo] = repository.split("/")
|
||||
if (!owner || !repo) return ""
|
||||
return `${serverUrl}/${owner}/${repo}/releases/tag/${encodeURIComponent(tag)}`
|
||||
}
|
||||
|
||||
function writeGithubOutput(values) {
|
||||
const outputFile = process.env.GITHUB_OUTPUT
|
||||
if (!outputFile) return
|
||||
const lines = Object.entries(values)
|
||||
.map(([key, value]) => `${key}=${String(value).replace(/\r?\n/g, "%0A")}`)
|
||||
.join("\n")
|
||||
writeFileSync(outputFile, `${lines}\n`, { encoding: "utf8", flag: "a" })
|
||||
}
|
||||
|
||||
function main() {
|
||||
const latestTag = getLatestTag()
|
||||
const baseFromTag = latestTag?.replace(/^v/, "") ?? baseVersion
|
||||
const commits = latestTag ? getCommitsSince(latestTag) : []
|
||||
|
||||
if (!latestTag || commits.length === 0) {
|
||||
mkdirSync(outputDir, { recursive: true })
|
||||
writeFileSync(join(outputDir, "release_notes.md"), "", "utf8")
|
||||
writeFileSync(
|
||||
join(outputDir, "release-manifest.json"),
|
||||
`${JSON.stringify(
|
||||
{
|
||||
version: baseFromTag,
|
||||
tag: latestTag ?? `v${baseFromTag}`,
|
||||
publishedAt: null,
|
||||
releaseUrl: latestTag ? buildReleaseUrl(latestTag) : "",
|
||||
commits: [],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
"utf8",
|
||||
)
|
||||
|
||||
writeGithubOutput({
|
||||
should_release: "false",
|
||||
version: baseFromTag,
|
||||
tag: latestTag ?? `v${baseFromTag}`,
|
||||
bump: "none",
|
||||
release_url: latestTag ? buildReleaseUrl(latestTag) : "",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const bump = commits.some((commit) => commit.bump === "minor") ? "minor" : "patch"
|
||||
const version = bumpVersion(baseFromTag, bump)
|
||||
const tag = `v${version}`
|
||||
const publishedAt = new Date().toISOString()
|
||||
const releaseUrl = buildReleaseUrl(tag)
|
||||
const releaseNotes = renderReleaseNotes({ tag, version, commits })
|
||||
const manifest = {
|
||||
version,
|
||||
tag,
|
||||
publishedAt,
|
||||
releaseUrl,
|
||||
commits: commits.map((commit) => ({
|
||||
sha: commit.sha,
|
||||
shortSha: commit.shortSha,
|
||||
subject: commit.subject,
|
||||
author: commit.author,
|
||||
type: commit.type,
|
||||
})),
|
||||
}
|
||||
|
||||
mkdirSync(outputDir, { recursive: true })
|
||||
writeFileSync(join(outputDir, "release_notes.md"), releaseNotes, "utf8")
|
||||
writeFileSync(join(outputDir, "release-manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8")
|
||||
|
||||
writeGithubOutput({
|
||||
should_release: "true",
|
||||
version,
|
||||
tag,
|
||||
bump,
|
||||
release_url: releaseUrl,
|
||||
})
|
||||
|
||||
process.stdout.write(`${tag}\n`)
|
||||
}
|
||||
|
||||
main()
|
||||
Reference in New Issue
Block a user