Files
tehran_taj/scripts/app-version.mjs
T
mahmoodandCursor 520454481b Remove user-app v1 and ship pending order, auth, and app fixes.
Drop the duplicate customer app from the monorepo and keep user-app-v2 as the only app; include order pricing, admin cart, OTP/SMS, and dashboard cache updates.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-28 16:22:32 +03:30

88 lines
3.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// App version + «what changed» notes, baked into each front-end at build time.
//
// Version: the build number is the commit count of HEAD, so it only ever goes
// up and needs no file to bump. It is compiled into the bundle (__APP_BUILD__)
// and written to dist/version.json; the running app polls that file and, when
// the server's build is newer than its own, shows the update modal.
//
// Notes come from commit messages. Technical text is never shown: only lines
// that start with a Persian tag, written for the person using that part:
//
// اپ: … the customer app (user-app-v2)
// اپ۲: … same (kept for older notes that used the v2 tag)
// پنل: … the admin panel
//
// A commit may carry several such lines. Each app shows only its own.
import { execFileSync } from 'node:child_process';
const TAGS = {
'user-app-v2': ['اپ', 'اپ۲', 'اپ2'],
'admin-panel': ['پنل'],
};
/** How many released builds with notes the file keeps. */
const HISTORY = 30;
function git(args, cwd) {
return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
}
/** Pure, for testing: the note lines in one commit message that belong to `app`. */
export function notesFor(app, message) {
const tags = TAGS[app] ?? [];
const out = [];
for (const raw of message.split(/\r?\n/)) {
const m = /^\s*([^\s::]+)\s*[::]\s*(.+?)\s*$/.exec(raw);
// The note must be Persian: a wrapped English line that happens to begin
// with a tag (e.g. in a commit body explaining the tags) is not a note.
if (m && tags.includes(m[1]) && /[؀-ۿ]/.test(m[2])) out.push(m[2]);
}
return out;
}
export function computeAppVersion(app, cwd = process.cwd()) {
const builtAt = new Date().toISOString();
try {
const build = Number(git(['rev-list', '--count', 'HEAD'], cwd).trim());
const commit = git(['rev-parse', '--short', 'HEAD'], cwd).trim();
// Newest first; a record separator between commits, unit separator between fields.
const log = git(['log', '-n', '400', '--format=%ct%x1f%B%x1e'], cwd);
const history = [];
log.split('\x1e').forEach((rec, i) => {
const [ts, body] = rec.replace(/^\s+/, '').split('\x1f');
if (!body) return;
const notes = notesFor(app, body);
if (!notes.length) return;
history.push({ build: build - i, date: new Date(Number(ts) * 1000).toISOString(), notes });
});
return { app, build, version: `1.${build}`, commit, builtAt, history: history.slice(0, HISTORY) };
} catch {
// Not a git checkout (e.g. a copied tree): still a valid, if note-less, build.
const build = Math.floor(Date.now() / 1000);
return { app, build, version: `1.${build}`, commit: null, builtAt, history: [] };
}
}
/**
* Vite plugin: defines __APP_BUILD__ / __APP_VERSION__ and emits version.json.
* The dev server has no version.json, so the update check stays quiet there.
*/
export function appVersion(app) {
const info = computeAppVersion(app);
return {
name: 'app-version',
config() {
return {
define: {
__APP_BUILD__: JSON.stringify(info.build),
__APP_VERSION__: JSON.stringify(info.version),
},
};
},
generateBundle() {
this.emitFile({ type: 'asset', fileName: 'version.json', source: JSON.stringify(info, null, 2) });
},
};
}