174 lines
5.7 KiB
JavaScript
174 lines
5.7 KiB
JavaScript
|
|
const fs = require('fs');
|
||
|
|
const path = require('path');
|
||
|
|
|
||
|
|
const projectRoot = path.resolve(__dirname, '..');
|
||
|
|
const webDir = path.join(projectRoot, 'web');
|
||
|
|
const versionsPath = path.join(webDir, 'asset-versions.json');
|
||
|
|
const statePath = path.join(webDir, '.asset-version-state.json');
|
||
|
|
|
||
|
|
const usage = `
|
||
|
|
用法:
|
||
|
|
node scripts/bump-web-asset-version.js --sync
|
||
|
|
node scripts/bump-web-asset-version.js --auto
|
||
|
|
node scripts/bump-web-asset-version.js --bump ai-assistant.js public-status.js
|
||
|
|
node scripts/bump-web-asset-version.js --bump-all
|
||
|
|
|
||
|
|
说明:
|
||
|
|
--sync 仅按 asset-versions.json 同步所有 HTML 中的 ?v= 引用
|
||
|
|
--auto 自动检查受管静态资源是否发生变更;有变更则递增对应版本并同步 HTML
|
||
|
|
--bump 仅递增指定静态资源版本号,并同步 HTML 引用
|
||
|
|
--bump-all 递增 asset-versions.json 中所有资源版本号,并同步 HTML 引用
|
||
|
|
`;
|
||
|
|
|
||
|
|
const args = process.argv.slice(2);
|
||
|
|
const hasFlag = (flag) => args.includes(flag);
|
||
|
|
|
||
|
|
const localPathPattern = /^\/?[^?#]+\.(?:js|css)(?:\?[^"]*)?$/i;
|
||
|
|
|
||
|
|
const readJson = (filePath) => JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||
|
|
const writeJson = (filePath, value) => {
|
||
|
|
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
||
|
|
};
|
||
|
|
const readJsonIfExists = (filePath, fallback) => {
|
||
|
|
if (!fs.existsSync(filePath)) return fallback;
|
||
|
|
return readJson(filePath);
|
||
|
|
};
|
||
|
|
|
||
|
|
const collectHtmlFiles = (dir) => {
|
||
|
|
const files = [];
|
||
|
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||
|
|
const fullPath = path.join(dir, entry.name);
|
||
|
|
if (entry.isDirectory()) {
|
||
|
|
files.push(...collectHtmlFiles(fullPath));
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (entry.isFile() && entry.name.toLowerCase().endsWith('.html')) {
|
||
|
|
files.push(fullPath);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return files;
|
||
|
|
};
|
||
|
|
|
||
|
|
const normalizeAssetName = (value) => path.basename(String(value || '').trim());
|
||
|
|
|
||
|
|
const withVersion = (originalPath, version) => {
|
||
|
|
const [pathname] = originalPath.split('?');
|
||
|
|
return `${pathname}?v=${version}`;
|
||
|
|
};
|
||
|
|
|
||
|
|
const getManagedAssetStats = (versions) => {
|
||
|
|
const stats = {};
|
||
|
|
Object.keys(versions).forEach((assetName) => {
|
||
|
|
const assetPath = path.join(webDir, assetName);
|
||
|
|
if (!fs.existsSync(assetPath)) return;
|
||
|
|
const fileStat = fs.statSync(assetPath);
|
||
|
|
stats[assetName] = {
|
||
|
|
size: fileStat.size,
|
||
|
|
mtimeMs: fileStat.mtimeMs
|
||
|
|
};
|
||
|
|
});
|
||
|
|
return stats;
|
||
|
|
};
|
||
|
|
|
||
|
|
const syncHtmlReferences = (versions) => {
|
||
|
|
const htmlFiles = collectHtmlFiles(webDir);
|
||
|
|
const refPattern = /((?:src|href)\s*=\s*")([^"]+\.(?:js|css)(?:\?[^"]*)?)(")/gi;
|
||
|
|
let changedFiles = 0;
|
||
|
|
|
||
|
|
htmlFiles.forEach((filePath) => {
|
||
|
|
const source = fs.readFileSync(filePath, 'utf8');
|
||
|
|
const updated = source.replace(refPattern, (match, prefix, assetPath, suffix) => {
|
||
|
|
if (/^https?:\/\//i.test(assetPath) || !localPathPattern.test(assetPath)) return match;
|
||
|
|
const assetName = normalizeAssetName(assetPath.split('?')[0]);
|
||
|
|
const version = versions[assetName];
|
||
|
|
if (!version) return match;
|
||
|
|
return `${prefix}${withVersion(assetPath, version)}${suffix}`;
|
||
|
|
});
|
||
|
|
|
||
|
|
if (updated !== source) {
|
||
|
|
fs.writeFileSync(filePath, updated, 'utf8');
|
||
|
|
changedFiles += 1;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return changedFiles;
|
||
|
|
};
|
||
|
|
|
||
|
|
const versions = readJson(versionsPath);
|
||
|
|
|
||
|
|
if (hasFlag('--help') || hasFlag('-h') || !args.length) {
|
||
|
|
console.log(usage.trim());
|
||
|
|
process.exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (hasFlag('--bump-all')) {
|
||
|
|
Object.keys(versions).forEach((key) => {
|
||
|
|
versions[key] += 1;
|
||
|
|
});
|
||
|
|
writeJson(versionsPath, versions);
|
||
|
|
const changed = syncHtmlReferences(versions);
|
||
|
|
console.log(`已递增全部资源版本,共 ${Object.keys(versions).length} 项;同步 HTML ${changed} 个文件。`);
|
||
|
|
process.exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (hasFlag('--sync')) {
|
||
|
|
const changed = syncHtmlReferences(versions);
|
||
|
|
console.log(`已按 asset-versions.json 同步 HTML 引用,共更新 ${changed} 个文件。`);
|
||
|
|
process.exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (hasFlag('--auto')) {
|
||
|
|
const previousState = readJsonIfExists(statePath, {});
|
||
|
|
const currentState = getManagedAssetStats(versions);
|
||
|
|
const changedAssets = Object.keys(currentState).filter((assetName) => {
|
||
|
|
const prev = previousState[assetName];
|
||
|
|
const curr = currentState[assetName];
|
||
|
|
if (!prev) return true;
|
||
|
|
return prev.size !== curr.size || prev.mtimeMs !== curr.mtimeMs;
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!changedAssets.length) {
|
||
|
|
const synced = syncHtmlReferences(versions);
|
||
|
|
writeJson(statePath, currentState);
|
||
|
|
console.log(`未发现静态资源变更;已校准 HTML 引用 ${synced} 个文件。`);
|
||
|
|
process.exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
changedAssets.forEach((assetName) => {
|
||
|
|
versions[assetName] += 1;
|
||
|
|
});
|
||
|
|
writeJson(versionsPath, versions);
|
||
|
|
writeJson(statePath, currentState);
|
||
|
|
const synced = syncHtmlReferences(versions);
|
||
|
|
console.log(`检测到资源变更: ${changedAssets.join(', ')};已自动递增版本并同步 HTML ${synced} 个文件。`);
|
||
|
|
process.exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
const bumpIndex = args.indexOf('--bump');
|
||
|
|
if (bumpIndex !== -1) {
|
||
|
|
const requested = args.slice(bumpIndex + 1).map(normalizeAssetName).filter(Boolean);
|
||
|
|
if (!requested.length) {
|
||
|
|
console.error('请在 --bump 后提供至少一个静态资源文件名,例如 ai-assistant.js');
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
const unknown = requested.filter((name) => !(name in versions));
|
||
|
|
if (unknown.length) {
|
||
|
|
console.error(`以下资源尚未纳入版本管理: ${unknown.join(', ')}`);
|
||
|
|
console.error('请先在 web/asset-versions.json 中补充后再执行。');
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
requested.forEach((name) => {
|
||
|
|
versions[name] += 1;
|
||
|
|
});
|
||
|
|
writeJson(versionsPath, versions);
|
||
|
|
const changed = syncHtmlReferences(versions);
|
||
|
|
console.log(`已递增资源版本: ${requested.join(', ')};同步 HTML ${changed} 个文件。`);
|
||
|
|
process.exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
console.error('未识别的参数。');
|
||
|
|
console.error(usage.trim());
|
||
|
|
process.exit(1);
|