mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-06 16:16:26 +00:00
92 lines
2.8 KiB
JavaScript
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);
|
|
});
|
|
}
|