Initial clean project import
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const archiver = require('archiver');
|
||||
const config = require('./resource-bundles.config.cjs');
|
||||
|
||||
function ensureDir(dir) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
function listFiles(dir) {
|
||||
if (!fs.existsSync(dir)) return [];
|
||||
const files = [];
|
||||
const walk = (current) => {
|
||||
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
||||
const full = path.join(current, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
walk(full);
|
||||
} else if (entry.isFile()) {
|
||||
files.push(full);
|
||||
}
|
||||
}
|
||||
};
|
||||
walk(dir);
|
||||
files.sort();
|
||||
return files;
|
||||
}
|
||||
|
||||
function hashDirectory(dir) {
|
||||
const hash = crypto.createHash('sha256');
|
||||
for (const file of listFiles(dir)) {
|
||||
const rel = path.relative(dir, file).replace(/\\/g, '/');
|
||||
const stat = fs.statSync(file);
|
||||
hash.update(rel);
|
||||
hash.update(String(stat.size));
|
||||
hash.update(fs.readFileSync(file));
|
||||
}
|
||||
return hash.digest('hex');
|
||||
}
|
||||
|
||||
function hashFile(file) {
|
||||
const hash = crypto.createHash('sha256');
|
||||
hash.update(fs.readFileSync(file));
|
||||
return hash.digest('hex');
|
||||
}
|
||||
|
||||
function readJson(file, fallback) {
|
||||
if (!fs.existsSync(file)) return fallback;
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function zipDirectory(sourceDir, outFile) {
|
||||
return new Promise((resolve, reject) => {
|
||||
ensureDir(path.dirname(outFile));
|
||||
const output = fs.createWriteStream(outFile);
|
||||
const archive = archiver('zip', { zlib: { level: 9 } });
|
||||
output.on('close', resolve);
|
||||
archive.on('error', reject);
|
||||
archive.pipe(output);
|
||||
archive.directory(sourceDir, false);
|
||||
archive.finalize();
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
ensureDir(config.outputDir);
|
||||
ensureDir(path.dirname(config.releaseManifestPath));
|
||||
ensureDir(path.dirname(config.embeddedManifestPath));
|
||||
ensureDir(path.dirname(config.buildStatePath));
|
||||
|
||||
const previousState = readJson(config.buildStatePath, { bundles: {} });
|
||||
const nextState = { generatedAt: new Date().toISOString(), bundles: {} };
|
||||
const manifest = {
|
||||
manifestVersion: 1,
|
||||
generatedAt: new Date().toISOString(),
|
||||
appVersion: process.env.npm_package_version || '',
|
||||
bundles: {},
|
||||
};
|
||||
|
||||
const changedBundles = [];
|
||||
|
||||
for (const bundle of config.bundles) {
|
||||
if (!fs.existsSync(bundle.source)) {
|
||||
if (bundle.required) {
|
||||
throw new Error(`Required resource bundle source missing: ${bundle.name} (${bundle.source})`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const sourceFiles = listFiles(bundle.source);
|
||||
if (bundle.required && sourceFiles.length === 0) {
|
||||
throw new Error(`Required resource bundle source is empty: ${bundle.name} (${bundle.source})`);
|
||||
}
|
||||
|
||||
const version = hashDirectory(bundle.source).slice(0, 16);
|
||||
const archive = `${bundle.name}-${version}.zip`;
|
||||
const archivePath = path.join(config.outputDir, archive);
|
||||
const previous = previousState.bundles?.[bundle.name];
|
||||
|
||||
if (!previous || previous.version !== version || !fs.existsSync(archivePath)) {
|
||||
await zipDirectory(bundle.source, archivePath);
|
||||
changedBundles.push(bundle.name);
|
||||
}
|
||||
|
||||
const size = fs.existsSync(archivePath) ? fs.statSync(archivePath).size : 0;
|
||||
const sha256 = fs.existsSync(archivePath) ? hashFile(archivePath) : '';
|
||||
if (bundle.required && (!fs.existsSync(archivePath) || size <= 0 || !sha256)) {
|
||||
throw new Error(`Required resource bundle archive was not created: ${bundle.name} (${archivePath})`);
|
||||
}
|
||||
|
||||
const archiveUrl = process.env[config.releaseBaseUrlEnv] || config.releaseBaseUrl
|
||||
? `${(process.env[config.releaseBaseUrlEnv] || config.releaseBaseUrl).replace(/\/$/, '')}/${archive}`
|
||||
: archive;
|
||||
|
||||
manifest.bundles[bundle.name] = {
|
||||
version,
|
||||
archive,
|
||||
url: archiveUrl,
|
||||
sha256,
|
||||
size,
|
||||
required: !!bundle.required,
|
||||
extractTo: bundle.extractTo,
|
||||
source: path.relative(path.join(__dirname, '..'), bundle.source).replace(/\\/g, '/'),
|
||||
};
|
||||
|
||||
nextState.bundles[bundle.name] = { version, archive, sha256, size };
|
||||
}
|
||||
|
||||
fs.writeFileSync(config.releaseManifestPath, JSON.stringify(manifest, null, 2));
|
||||
fs.writeFileSync(config.embeddedManifestPath, JSON.stringify(manifest, null, 2));
|
||||
fs.writeFileSync(config.buildStatePath, JSON.stringify(nextState, null, 2));
|
||||
|
||||
const summaryPath = path.join(config.outputDir, 'changed-bundles.json');
|
||||
fs.writeFileSync(summaryPath, JSON.stringify({ changedBundles, manifest: path.basename(config.releaseManifestPath) }, null, 2));
|
||||
|
||||
console.log('[resource-build] changed bundles:', changedBundles.join(', ') || 'none');
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('[resource-build] failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user