Pulse/frontend-modern/scripts/sync-embed-dist.mjs
2026-03-18 16:06:30 +00:00

92 lines
2.8 KiB
JavaScript

import { cp, mkdir, rename, rm, stat } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { withExclusiveLock } from '../../scripts/exclusive-lock.mjs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const repoRoot = path.resolve(__dirname, '..', '..');
const sourceDir = path.join(repoRoot, 'frontend-modern', 'dist');
const targetDir = path.join(repoRoot, 'internal', 'api', 'frontend-modern', 'dist');
const lockPath = path.join(repoRoot, 'tmp', 'locks', 'frontend-embed-sync.lock');
async function ensureSourceExists() {
try {
const sourceStat = await stat(sourceDir);
if (!sourceStat.isDirectory()) {
throw new Error(`${sourceDir} is not a directory`);
}
} catch (error) {
throw new Error(`frontend build output missing at ${sourceDir}`, { cause: error });
}
}
async function publishDirAtomically() {
const targetParent = path.dirname(targetDir);
const targetName = path.basename(targetDir);
const nonce = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
const stagingDir = path.join(targetParent, `${targetName}.${nonce}.staging`);
const backupDir = path.join(targetParent, `${targetName}.${nonce}.backup`);
await rm(stagingDir, { force: true, recursive: true });
await rm(backupDir, { force: true, recursive: true });
await mkdir(targetParent, { recursive: true });
await cp(sourceDir, stagingDir, { recursive: true });
let replacedExistingTarget = false;
try {
try {
await rename(targetDir, backupDir);
replacedExistingTarget = true;
} catch (error) {
if (!error || typeof error !== 'object' || !('code' in error) || error.code !== 'ENOENT') {
throw error;
}
}
await rename(stagingDir, targetDir);
} catch (error) {
await rm(stagingDir, { force: true, recursive: true });
if (replacedExistingTarget) {
try {
await rename(backupDir, targetDir);
} catch {
// Leave the original failure intact; cleanup best effort only.
}
}
throw error;
}
if (replacedExistingTarget) {
await rm(backupDir, { force: true, recursive: true });
}
}
export async function syncEmbedDir({ lock = true } = {}) {
await ensureSourceExists();
const action = async () => {
await publishDirAtomically();
};
if (lock) {
await withExclusiveLock(lockPath, action, {
description: 'frontend embedded asset sync',
});
} else {
await action();
}
console.log(`Synced frontend embed assets to ${targetDir}`);
}
const isMainModule = process.argv[1]
? pathToFileURL(process.argv[1]).href === import.meta.url
: false;
if (isMainModule) {
syncEmbedDir().catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
});
}