mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-03-22 15:17:23 +01:00
feat: implement version bumping script and pre-push hook
This commit is contained in:
parent
a66b60b5a7
commit
1f81dd2395
5 changed files with 285 additions and 3 deletions
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "fantasy-map-generator",
|
||||
"version": "1.113.2",
|
||||
"version": "1.113.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "fantasy-map-generator",
|
||||
"version": "1.113.2",
|
||||
"version": "1.113.6",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"alea": "^1.0.1",
|
||||
|
|
@ -1353,6 +1353,7 @@
|
|||
"integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
|
|
@ -1393,6 +1394,7 @@
|
|||
"integrity": "sha512-gfajTHVCiwpxRj1qh0Sh/5bbGLG4F/ZH/V9xvFVoFddpITfMta9YGow0W6ZpTTORv2vdJuz9TnrNSmjKvpOf4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/browser": "4.0.18",
|
||||
"@vitest/mocker": "4.0.18",
|
||||
|
|
@ -1874,6 +1876,7 @@
|
|||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
|
|
@ -2160,6 +2163,7 @@
|
|||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
|
@ -2471,6 +2475,7 @@
|
|||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
|
|
@ -2546,6 +2551,7 @@
|
|||
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.0.18",
|
||||
"@vitest/mocker": "4.0.18",
|
||||
|
|
|
|||
|
|
@ -606,4 +606,6 @@ dr_not_sam
|
|||
Mie96
|
||||
Riley
|
||||
Amber Davis
|
||||
tomtom1969vlbg`;
|
||||
tomtom1969vlbg
|
||||
Eric Knight
|
||||
Adeline Lefizelier`;
|
||||
|
|
|
|||
212
scripts/bump-version.js
Normal file
212
scripts/bump-version.js
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Bump the project version (patch / minor / major).
|
||||
*
|
||||
* Updates:
|
||||
* - public/versioning.js — VERSION constant
|
||||
* - package.json — "version" field
|
||||
* - src/index.html — ?v= cache-busting hashes for changed public/*.js files
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/bump-version.js # interactive prompt
|
||||
* node scripts/bump-version.js patch # non-interactive
|
||||
* node scripts/bump-version.js minor
|
||||
* node scripts/bump-version.js major
|
||||
* node scripts/bump-version.js --dry-run # preview only, no writes
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const readline = require("readline");
|
||||
const {execSync} = require("child_process");
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Paths
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const repoRoot = path.resolve(__dirname, "..");
|
||||
const packageJsonPath = path.join(repoRoot, "package.json");
|
||||
const versioningPath = path.join(repoRoot, "public", "versioning.js");
|
||||
const indexHtmlPath = path.join(repoRoot, "src", "index.html");
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function readFile(filePath) {
|
||||
return fs.readFileSync(filePath, "utf8");
|
||||
}
|
||||
|
||||
function writeFile(filePath, content) {
|
||||
fs.writeFileSync(filePath, content, "utf8");
|
||||
}
|
||||
|
||||
function parseCurrentVersion() {
|
||||
const content = readFile(versioningPath);
|
||||
const match = content.match(/const VERSION = "(\d+\.\d+\.\d+)";/);
|
||||
if (!match) throw new Error("Could not find VERSION constant in public/versioning.js");
|
||||
return match[1];
|
||||
}
|
||||
|
||||
function bumpVersion(version, type) {
|
||||
const [major, minor, patch] = version.split(".").map(Number);
|
||||
if (type === "major") return `${major + 1}.0.0`;
|
||||
if (type === "minor") return `${major}.${minor + 1}.0`;
|
||||
return `${major}.${minor}.${patch + 1}`;
|
||||
}
|
||||
|
||||
function escapeRegExp(str) {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns public/*.js paths (relative to repo root) that have changed.
|
||||
* Checks (in order, deduplicating):
|
||||
* 1. Upstream branch diff — catches everything on a feature/PR branch
|
||||
* 2. Staged (index) diff — catches files staged but not yet committed
|
||||
* 3. Last-commit diff — fallback for main / detached HEAD
|
||||
*/
|
||||
function getChangedPublicJsFiles() {
|
||||
const run = cmd => execSync(cmd, {encoding: "utf8", cwd: repoRoot});
|
||||
const parseFiles = output =>
|
||||
output
|
||||
.split("\n")
|
||||
.map(f => f.trim())
|
||||
.filter(f => f.startsWith("public/") && f.endsWith(".js"));
|
||||
|
||||
const seen = new Set();
|
||||
const collect = files => files.forEach(f => seen.add(f));
|
||||
|
||||
// 1. Upstream branch diff
|
||||
try {
|
||||
const upstream = run("git rev-parse --abbrev-ref --symbolic-full-name @{upstream}").trim();
|
||||
collect(parseFiles(run(`git diff --name-only ${upstream}...HEAD`)));
|
||||
} catch {
|
||||
/* no upstream */
|
||||
}
|
||||
|
||||
// 2. Staged changes (useful when building before committing)
|
||||
try {
|
||||
collect(parseFiles(run("git diff --name-only --cached")));
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
|
||||
if (seen.size > 0) return [...seen];
|
||||
|
||||
// 3. Fallback: last commit diff
|
||||
try {
|
||||
return parseFiles(run("git diff --name-only HEAD~1 HEAD"));
|
||||
} catch {
|
||||
/* shallow / single-commit repo */
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// File updaters
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function updateVersioningJs(newVersion, dry) {
|
||||
const original = readFile(versioningPath);
|
||||
const updated = original.replace(/const VERSION = "\d+\.\d+\.\d+";/, `const VERSION = "${newVersion}";`);
|
||||
if (original === updated) throw new Error("Failed to update VERSION in public/versioning.js");
|
||||
if (!dry) writeFile(versioningPath, updated);
|
||||
console.log(` public/versioning.js → ${newVersion}`);
|
||||
}
|
||||
|
||||
function updatePackageJson(newVersion, dry) {
|
||||
const original = readFile(packageJsonPath);
|
||||
const pkg = JSON.parse(original);
|
||||
const oldVersion = pkg.version;
|
||||
pkg.version = newVersion;
|
||||
if (!dry) writeFile(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
||||
console.log(` package.json ${oldVersion} → ${newVersion}`);
|
||||
}
|
||||
|
||||
function updateIndexHtmlHashes(newVersion, dry) {
|
||||
const changedFiles = getChangedPublicJsFiles();
|
||||
|
||||
if (changedFiles.length === 0) {
|
||||
console.log(" src/index.html (no changed public/*.js files detected)");
|
||||
return;
|
||||
}
|
||||
|
||||
let html = readFile(indexHtmlPath);
|
||||
const updated = [];
|
||||
|
||||
for (const publicPath of changedFiles) {
|
||||
const htmlPath = publicPath.replace(/^public\//, "");
|
||||
const pattern = new RegExp(`${escapeRegExp(htmlPath)}\\?v=[0-9.]+`, "g");
|
||||
if (pattern.test(html)) {
|
||||
html = html.replace(pattern, `${htmlPath}?v=${newVersion}`);
|
||||
updated.push(htmlPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (updated.length > 0) {
|
||||
if (!dry) writeFile(indexHtmlPath, html);
|
||||
console.log(` src/index.html hashes updated for:\n - ${updated.join("\n - ")}`);
|
||||
} else {
|
||||
console.log(
|
||||
` src/index.html (changed files not referenced: ${changedFiles.map(f => f.replace("public/", "")).join(", ")})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Prompt
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function promptBumpType(currentVersion) {
|
||||
return new Promise(resolve => {
|
||||
const rl = readline.createInterface({input: process.stdin, output: process.stdout});
|
||||
process.stdout.write(`\nCurrent version: ${currentVersion}\nBump type (patch / minor / major) [patch]: `);
|
||||
rl.once("line", answer => {
|
||||
rl.close();
|
||||
const input = answer.trim().toLowerCase();
|
||||
if (input === "minor" || input === "mi") return resolve("minor");
|
||||
if (input === "major" || input === "maj") return resolve("major");
|
||||
resolve("patch");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2).map(a => a.toLowerCase());
|
||||
const dry = args.includes("--dry-run");
|
||||
|
||||
if (dry) console.log("\n[bump-version] DRY RUN — no files will be changed\n");
|
||||
|
||||
const currentVersion = parseCurrentVersion();
|
||||
|
||||
// Determine bump type: CLI arg → stdin prompt → default patch
|
||||
let bumpType;
|
||||
if (args.includes("major")) bumpType = "major";
|
||||
else if (args.includes("minor")) bumpType = "minor";
|
||||
else if (args.includes("patch")) bumpType = "patch";
|
||||
else if (process.stdin.isTTY) bumpType = await promptBumpType(currentVersion);
|
||||
else bumpType = "patch"; // non-interactive (CI / pipe)
|
||||
|
||||
const newVersion = bumpVersion(currentVersion, bumpType);
|
||||
|
||||
console.log(`\n[bump-version] ${bumpType}: ${currentVersion} → ${newVersion}\n`);
|
||||
|
||||
updateVersioningJs(newVersion, dry);
|
||||
updatePackageJson(newVersion, dry);
|
||||
updateIndexHtmlHashes(newVersion, dry);
|
||||
|
||||
console.log(`\n[bump-version] ${dry ? "(dry run) " : ""}done.\n`);
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error("\n[bump-version] Error:", err.message || err);
|
||||
process.exit(1);
|
||||
});
|
||||
33
scripts/install-hooks.js
Normal file
33
scripts/install-hooks.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env node
|
||||
// Installs scripts/pre-push as .git/hooks/pre-push.
|
||||
// Runs automatically via the `prepare` npm lifecycle hook (npm install).
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const repoRoot = path.resolve(__dirname, "..");
|
||||
const hooksDir = path.join(repoRoot, ".git", "hooks");
|
||||
const source = path.join(repoRoot, "scripts", "pre-push");
|
||||
const target = path.join(hooksDir, "pre-push");
|
||||
|
||||
if (!fs.existsSync(path.join(repoRoot, ".git"))) {
|
||||
// Not a git repo (e.g. Docker / CI build from tarball) — skip silently.
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(hooksDir)) {
|
||||
fs.mkdirSync(hooksDir, {recursive: true});
|
||||
}
|
||||
|
||||
try {
|
||||
// Symlink so changes to scripts/pre-push are reflected immediately.
|
||||
if (fs.existsSync(target) || fs.lstatSync(target).isSymbolicLink()) {
|
||||
fs.unlinkSync(target);
|
||||
}
|
||||
} catch {
|
||||
// Target doesn't exist yet — that's fine.
|
||||
}
|
||||
|
||||
fs.symlinkSync(source, target);
|
||||
fs.chmodSync(source, 0o755);
|
||||
console.log("[prepare] Installed git pre-push hook → .git/hooks/pre-push");
|
||||
29
scripts/pre-push
Executable file
29
scripts/pre-push
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/env sh
|
||||
# Git pre-push hook — automatically bumps the version before pushing.
|
||||
# Installed by: npm run prepare (scripts/install-hooks.js)
|
||||
#
|
||||
# Prompts for patch / minor / major (default: patch), updates:
|
||||
# - public/versioning.js
|
||||
# - package.json
|
||||
# - src/index.html (cache-busting ?v= hashes for changed modules)
|
||||
# then commits those changes so they are included in the push.
|
||||
|
||||
set -e
|
||||
|
||||
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
||||
|
||||
echo ""
|
||||
node "$REPO_ROOT/scripts/bump-version.js"
|
||||
|
||||
# Stage files that may have been modified by the bump
|
||||
git add \
|
||||
"$REPO_ROOT/public/versioning.js" \
|
||||
"$REPO_ROOT/package.json" \
|
||||
"$REPO_ROOT/src/index.html" 2>/dev/null || true
|
||||
|
||||
# Only commit if there are staged changes from the bump
|
||||
if ! git diff --cached --quiet; then
|
||||
NEW_VERSION=$(node -e "const f=require('fs');const m=f.readFileSync('$REPO_ROOT/public/versioning.js','utf8').match(/const VERSION = \"([\d.]+)\"/);console.log(m[1])")
|
||||
git commit -m "chore: bump version to $NEW_VERSION"
|
||||
echo "[pre-push] Committed version bump → $NEW_VERSION"
|
||||
fi
|
||||
Loading…
Add table
Add a link
Reference in a new issue