Files
tehran_taj/scripts/app-version.mjs
T
s.esmaeiliandClaude Opus 5.5 dbead8a6ba feat(apps): version on every build, a «what changed» modal, and a clean update
- The build number is the commit count, so every build raises it with no
  file to bump. scripts/app-version.mjs bakes it into each front-end
  (__APP_BUILD__) and writes dist/version.json with that app's notes.
- Notes come from commit messages: only Persian lines that start with
  one of the four app tags are shown, each to its own app
  (docs/RELEASE-NOTES.md).
- Both customer apps and the admin panel poll version.json (every 5
  minutes and on return to the tab, bypassing caches). A newer build opens
  a modal that cannot be dismissed. «به‌روزرسانی» unregisters the service
  worker, empties Cache Storage and reloads; localStorage (sign-in, cart,
  settings) is kept.
- Replaces legacy's version dialog, which compared against a
  package.json version that was never bumped and so never opened, and
  useRefresh, which swapped in a new service worker silently.
- nginx (all three apps and nginx-spa.conf): index.html, sw.js and
  version.json are never cached. sw.js used to fall under the 6-month rule,
  which could pin a browser to an old release.

Checked: served with the real nginx config, a newer version.json opens
the modal with its notes; the same build does not.

اپ: اجرت هر کالا حالا همان است که در فاکتور حساب می‌شود؛ درصد خود شما و تخفیف‌ها هم در آن آمده است.
اپ: علاقه‌مندی‌های شما ذخیره می‌شوند و در هر دستگاهی که وارد شوید دیده می‌شوند.
اپ: از این به بعد هر نسخه‌ی تازه با فهرست تغییراتش اعلام می‌شود.
پنل: بخش تازه‌ی «اجرت»: فرمول اجرت را با یک نمونه امتحان و بعد تأیید کنید.
پنل: شهرها مثل قبل به‌صورت درخت‌اند (کشور، استان، شهر، محله)؛ شهری که درصد ندارد درصد شهر بالاتر را می‌گیرد.
پنل: «تاریخ تولد» به فرم مشتری عمده و تکی برگشت.
پنل: در گزارش تجمیعی سفارش‌ها، دکمه‌ی «دریافت فایل اکسل» اضافه شد.
پنل: رد احراز هویت مشتری حالا دلیل هم می‌گیرد.
پنل: با تأیید یا رد سند مالی، به مشتری پیامک می‌رود؛ در پیامک گروهی، %name با نام مشتری عوض می‌شود.
پنل: از این به بعد هر نسخه‌ی تازه‌ی پنل با فهرست تغییراتش اعلام می‌شود.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:45:52 +03:30

90 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:
//
// اپ: … both customer apps
// اپ۱: … user-app only
// اپ۲: … user-app-v2 only
// پنل: … 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': ['اپ', 'اپ۱', 'اپ1'],
'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) });
},
};
}