forked from lunanting/cangku
112 lines
3.4 KiB
JavaScript
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;
|
|
});
|
|
}
|