cangku/frontend/scripts/verify-public-build.mjs

112 lines
3.4 KiB
JavaScript

import { readFile } from 'node:fs/promises';
import path from 'node:path';
import process from 'node:process';
import { pathToFileURL } from 'node:url';
import { extractEntryAssetPath as extractEntryAssetPathFromHtml } from './verify-public-build.shared.mjs';
const DEFAULT_INDEX_PATH = '/index.html';
const DEFAULT_VERSION_PATH = '/__build_version__.json';
function normalizeBaseUrl(value) {
const text = String(value || '').trim();
if (!text) {
return '';
}
return text.endsWith('/') ? text.slice(0, -1) : text;
}
async function fetchText(url) {
const response = await fetch(url, {
cache: 'no-store',
headers: { 'cache-control': 'no-cache' },
});
if (!response.ok) {
throw new Error(`Request failed: ${url} -> ${response.status}`);
}
return response.text();
}
function extractBuildVersion(text) {
if (!text) {
return '';
}
try {
const parsed = JSON.parse(text);
return String(parsed?.version || '').trim();
} catch {
return '';
}
}
async function readLocalVersion(distDir) {
const versionPath = path.join(distDir, '__build_version__.json');
return extractBuildVersion(await readFile(versionPath, 'utf8'));
}
async function readLocalEntry(distDir) {
const indexHtmlPath = path.join(distDir, 'index.html');
return extractEntryAssetPathFromHtml(await readFile(indexHtmlPath, 'utf8'));
}
export async function verifyPublicBuild({
baseUrl = process.env.PUBLIC_APP_URL,
distDir = path.resolve(process.cwd(), 'dist'),
} = {}) {
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
if (!normalizedBaseUrl) {
throw new Error('PUBLIC_APP_URL is required for verifyPublicBuild.');
}
const localEntryAssetPath = await readLocalEntry(distDir);
const localBuildVersion = await readLocalVersion(distDir);
const remoteIndexHtml = await fetchText(`${normalizedBaseUrl}${DEFAULT_INDEX_PATH}`);
const remoteVersionText = await fetchText(`${normalizedBaseUrl}${DEFAULT_VERSION_PATH}`);
const remoteEntryAssetPath = extractEntryAssetPathFromHtml(remoteIndexHtml);
const remoteBuildVersion = extractBuildVersion(remoteVersionText);
if (!localEntryAssetPath || !remoteEntryAssetPath) {
throw new Error('Unable to extract entry asset path from local or remote index.html.');
}
if (localEntryAssetPath !== remoteEntryAssetPath || localBuildVersion !== remoteBuildVersion) {
throw new Error(
[
'Public build does not match local dist.',
`localEntry=${localEntryAssetPath}`,
`remoteEntry=${remoteEntryAssetPath}`,
`localVersion=${localBuildVersion || '--'}`,
`remoteVersion=${remoteBuildVersion || '--'}`,
].join(' '),
);
}
return {
baseUrl: normalizedBaseUrl,
localEntryAssetPath,
remoteEntryAssetPath,
localBuildVersion,
remoteBuildVersion,
};
}
async function main() {
const result = await verifyPublicBuild({
baseUrl: process.argv[2] || process.env.PUBLIC_APP_URL,
distDir: process.argv[3] ? path.resolve(process.cwd(), process.argv[3]) : path.resolve(process.cwd(), 'dist'),
});
process.stdout.write(
`Verified public build ${result.remoteBuildVersion} at ${result.baseUrl} using ${result.remoteEntryAssetPath}\n`,
);
}
const isDirectExecution = process.argv[1]
? pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url
: false;
if (isDirectExecution) {
main().catch((error) => {
process.stderr.write(`${error.message}\n`);
process.exitCode = 1;
});
}