- Add the event results page with schedule, rankings, Battle bracket, athletes, records, bilingual labels, and navigation entry. - Add the read/write competition data service, a complete Excel example, empty public state, and validation smoke tests. - Handle Furion statusCode/data responses from the production API and retain static fallback for the public page. - Document Docker sidecar deployment beside test_eventapi, persistent storage, private admin access, Nginx routing, and OSS asset rewriting. Verification: - node server/competition-api.smoke.mjs - node scripts/competition-response-test.mjs - node scripts/battle-layout-test.mjs - npm run build -- --base=https://assets.example.test/static/test/ - npm run rewrite:oss -- https://assets.example.test/static/test/ - Production /api/competition/state returned HTTP 200 after the sidecar started. Deployment note: publish the updated YD-WEB static build before public results can consume the unwrapped API response. The event remains unpublished until an administrator saves and publishes data.
69 lines
2.3 KiB
JavaScript
69 lines
2.3 KiB
JavaScript
import { readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
import { extname, join, resolve } from 'node:path';
|
|
|
|
const rawBase = process.argv[2];
|
|
|
|
if (!rawBase || !/^https:\/\//.test(rawBase)) {
|
|
throw new Error('Usage: npm run rewrite:oss -- https://bucket.example/path/');
|
|
}
|
|
|
|
const base = rawBase.endsWith('/') ? rawBase : `${rawBase}/`;
|
|
const distDir = resolve('dist');
|
|
const textExtensions = new Set(['.html', '.js', '.css']);
|
|
const files = [];
|
|
|
|
const collectFiles = (directory) => {
|
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
const fullPath = join(directory, entry.name);
|
|
if (entry.isDirectory()) collectFiles(fullPath);
|
|
else if (textExtensions.has(extname(entry.name))) files.push(fullPath);
|
|
}
|
|
};
|
|
|
|
collectFiles(distDir);
|
|
|
|
// Vite has already rewritten static template/CSS URLs. Only rewrite root asset
|
|
// paths that still begin immediately after a JavaScript quote or template tick.
|
|
const rootAssetPattern = /(["'`])\/(site|materials|sports-hall|competition)\//g;
|
|
const rootCompetitionDataPattern = /(["'`])\/competition-state\.json/g;
|
|
let changedFiles = 0;
|
|
|
|
for (const file of files.filter((item) => extname(item) === '.js')) {
|
|
const source = readFileSync(file, 'utf8');
|
|
const rewritten = source.replace(
|
|
rootAssetPattern,
|
|
(_match, quote, folder) => `${quote}${base}${folder}/`,
|
|
).replace(rootCompetitionDataPattern, (_match, quote) => `${quote}${base}competition-state.json`);
|
|
|
|
if (rewritten !== source) {
|
|
writeFileSync(file, rewritten, 'utf8');
|
|
changedFiles += 1;
|
|
}
|
|
}
|
|
|
|
const duplicatePrefix = `${base.slice(0, -1)}${base}`;
|
|
const unresolved = [];
|
|
const duplicated = [];
|
|
|
|
for (const file of files) {
|
|
const content = readFileSync(file, 'utf8');
|
|
if (content.includes(duplicatePrefix) || /https:\/\/[^\s"'`]+https:\/\//.test(content)) {
|
|
duplicated.push(file);
|
|
}
|
|
if (extname(file) === '.js' && (rootAssetPattern.test(content) || rootCompetitionDataPattern.test(content))) {
|
|
unresolved.push(file);
|
|
}
|
|
rootAssetPattern.lastIndex = 0;
|
|
rootCompetitionDataPattern.lastIndex = 0;
|
|
}
|
|
|
|
if (duplicated.length) {
|
|
throw new Error(`Duplicated OSS prefix found in: ${duplicated.join(', ')}`);
|
|
}
|
|
|
|
if (unresolved.length) {
|
|
throw new Error(`Root asset paths remain in: ${unresolved.join(', ')}`);
|
|
}
|
|
|
|
console.log(`OSS paths verified. Rewritten files: ${changedFiles}`);
|