const fs = require('fs'); const path = require('path'); const root = path.join(__dirname, '..'); const buildDir = path.join(root, 'dist-release-final'); const bundleDir = path.join(buildDir, 'resource-bundles'); const releaseDir = path.join(buildDir, 'release-assets'); const legacyPrefixes = ['尚创IP智能体V8']; const currentPrefix = 'SC-IP-Agent'; const manifestPath = path.join(buildDir, 'resource-manifest.json'); const isOemFullOfflineBuild = process.env.OEM_BATCH_BUILD === '1'; const readJson = (file, fallback) => { if (!fs.existsSync(file)) return fallback; try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return fallback; } }; fs.mkdirSync(releaseDir, { recursive: true }); const waitForFile = (filePath, maxWait = 10000) => { const startTime = Date.now(); while (Date.now() - startTime < maxWait) { try { const fd = fs.openSync(filePath, 'r+'); fs.closeSync(fd); return true; } catch { if (Date.now() - startTime < maxWait) { const sleepMs = 200; const sleepUntil = Date.now() + sleepMs; while (Date.now() < sleepUntil) { } } } } console.warn(`[assemble] Warning: ${filePath} still locked after ${maxWait}ms, attempting copy anyway...`); return false; }; const copyIfNeeded = (sourcePath, destPath) => { if (fs.existsSync(destPath)) { const srcBuffer = fs.readFileSync(sourcePath); const destBuffer = fs.readFileSync(destPath); if (srcBuffer.length === destBuffer.length && srcBuffer.equals(destBuffer)) { return false; } } waitForFile(sourcePath); fs.copyFileSync(sourcePath, destPath); return true; }; const compatAliasesFor = (name) => { for (const legacyPrefix of legacyPrefixes) { const prefix = `${legacyPrefix}-`; if (name.startsWith(prefix)) { return [`${currentPrefix}-${name.slice(prefix.length)}`]; } } return []; }; const releaseExtensions = ['.exe', '.blockmap', '.yml', '.7z']; const hasRootInstaller = fs.readdirSync(buildDir) .some(name => { const full = path.join(buildDir, name); return fs.statSync(full).isFile() && /setup.*\.exe$/i.test(name) && !name.startsWith('__uninstaller') && fs.statSync(full).size > 1024 * 1024; }); const hasNsisWebOutput = !hasRootInstaller && fs.existsSync(path.join(buildDir, 'nsis-web')); const copyReleaseFiles = (dir) => { if (!fs.existsSync(dir)) return; if (path.resolve(dir) === path.resolve(releaseDir)) return; for (const name of fs.readdirSync(dir)) { const full = path.join(dir, name); const stat = fs.statSync(full); if (stat.isDirectory()) { if (path.resolve(dir) === path.resolve(buildDir) && name === 'nsis-web') { copyReleaseFiles(full); } continue; } if (!stat.isFile()) continue; if (!releaseExtensions.some(ext => name.endsWith(ext))) continue; if (hasNsisWebOutput && path.resolve(dir) === path.resolve(buildDir)) continue; const dest = path.join(releaseDir, name); try { copyIfNeeded(full, dest); } catch (err) { if (err.code === 'EBUSY') { console.warn(`[assemble] ${name} is locked, skipping (likely being scanned by antivirus)`); } else { throw err; } } } }; copyReleaseFiles(buildDir); for (const name of fs.readdirSync(releaseDir)) { const full = path.join(releaseDir, name); if (!fs.statSync(full).isFile()) continue; for (const alias of compatAliasesFor(name)) { const aliasPath = path.join(releaseDir, alias); if (aliasPath === full || fs.existsSync(aliasPath)) continue; fs.copyFileSync(full, aliasPath); console.log(`[assemble] Created compatibility alias: ${alias}`); } } if (!isOemFullOfflineBuild && fs.existsSync(manifestPath)) { fs.copyFileSync(manifestPath, path.join(releaseDir, 'resource-manifest.json')); } if (!isOemFullOfflineBuild && fs.existsSync(bundleDir)) { const manifest = readJson(manifestPath, { bundles: {} }); const currentBundleArchives = new Set( Object.values(manifest.bundles || {}) .map(bundle => bundle && bundle.archive) .filter(Boolean) ); for (const name of fs.readdirSync(bundleDir)) { const full = path.join(bundleDir, name); const shouldCopy = name.endsWith('.json') || currentBundleArchives.has(name); if (fs.statSync(full).isFile() && shouldCopy) { fs.copyFileSync(full, path.join(releaseDir, name)); } } } if (isOemFullOfflineBuild) { for (const name of fs.readdirSync(releaseDir)) { if (name.endsWith('.zip') || name === 'resource-manifest.json' || name === 'changed-bundles.json') { fs.rmSync(path.join(releaseDir, name), { force: true }); } } } console.log('[release-assemble] output:', releaseDir);