122 lines
4.2 KiB
JavaScript
122 lines
4.2 KiB
JavaScript
|
|
const cp = require('node:child_process');
|
||
|
|
const fs = require('node:fs');
|
||
|
|
const path = require('node:path');
|
||
|
|
const yaml = require('js-yaml');
|
||
|
|
|
||
|
|
const backendDir = path.resolve(__dirname, '..');
|
||
|
|
const repoDir = path.resolve(backendDir, '..', '..');
|
||
|
|
const outputDir = path.join(backendDir, 'build', 'pkg-output');
|
||
|
|
const trayProject = path.join(repoDir, 'packages', 'windows-tray', 'Neta.Tray', 'Neta.Tray.csproj');
|
||
|
|
const trayOutputDir = path.join(backendDir, 'build', 'tray-output');
|
||
|
|
const nodeRuntimeDir = path.join(backendDir, 'build', 'node-runtime');
|
||
|
|
const skillsSourceDir = path.join(backendDir, 'skills');
|
||
|
|
const skillsOutputDir = path.join(backendDir, 'build', 'skills-output');
|
||
|
|
const toolsSourceDir = path.join(backendDir, 'tools', 'win32');
|
||
|
|
const toolsOutputDir = path.join(backendDir, 'build', 'tools-output', 'win32');
|
||
|
|
const issPath = path.join(backendDir, 'installer', 'setup.iss');
|
||
|
|
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||
|
|
|
||
|
|
function copyBundledNodeRuntime() {
|
||
|
|
if (process.platform !== 'win32') {
|
||
|
|
console.log('Skipping bundled node.exe copy on non-Windows host.');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
fs.rmSync(nodeRuntimeDir, { recursive: true, force: true });
|
||
|
|
fs.mkdirSync(nodeRuntimeDir, { recursive: true });
|
||
|
|
fs.copyFileSync(process.execPath, path.join(nodeRuntimeDir, 'node.exe'));
|
||
|
|
}
|
||
|
|
|
||
|
|
function readSkillConfig(skillDir) {
|
||
|
|
const configPath = path.join(skillDir, 'skill.config.yaml');
|
||
|
|
if (!fs.existsSync(configPath)) return null;
|
||
|
|
return yaml.load(fs.readFileSync(configPath, 'utf8')) || null;
|
||
|
|
}
|
||
|
|
|
||
|
|
function prepareSkillsOutput() {
|
||
|
|
fs.rmSync(skillsOutputDir, { recursive: true, force: true });
|
||
|
|
fs.cpSync(skillsSourceDir, skillsOutputDir, {
|
||
|
|
recursive: true,
|
||
|
|
filter: source => path.basename(source) !== 'node_modules',
|
||
|
|
});
|
||
|
|
|
||
|
|
for (const entry of fs.readdirSync(skillsOutputDir, { withFileTypes: true })) {
|
||
|
|
if (!entry.isDirectory()) continue;
|
||
|
|
|
||
|
|
const skillDir = path.join(skillsOutputDir, entry.name);
|
||
|
|
const config = readSkillConfig(skillDir);
|
||
|
|
const nodePackages = config?.runtime === 'node' && config?.entrypoint
|
||
|
|
? config?.dependencies?.node?.packages
|
||
|
|
: null;
|
||
|
|
if (!Array.isArray(nodePackages) || nodePackages.length === 0) continue;
|
||
|
|
|
||
|
|
console.log(`Installing Node dependencies for skill ${entry.name}...`);
|
||
|
|
cp.execFileSync(
|
||
|
|
npmCmd,
|
||
|
|
[
|
||
|
|
'install',
|
||
|
|
'--omit=dev',
|
||
|
|
'--no-audit',
|
||
|
|
'--no-fund',
|
||
|
|
'--package-lock=false',
|
||
|
|
...nodePackages,
|
||
|
|
],
|
||
|
|
{ cwd: skillDir, stdio: 'inherit' }
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!fs.existsSync(issPath)) {
|
||
|
|
throw new Error(`缺少安装器脚本: ${issPath}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!fs.existsSync(path.join(outputDir, 'backend.exe'))) {
|
||
|
|
console.log('backend.exe not found, running pkg build first...');
|
||
|
|
cp.execFileSync('node', ['scripts/pkg-build.js'], { cwd: backendDir, stdio: 'inherit' });
|
||
|
|
}
|
||
|
|
|
||
|
|
copyBundledNodeRuntime();
|
||
|
|
prepareSkillsOutput();
|
||
|
|
|
||
|
|
fs.rmSync(trayOutputDir, { recursive: true, force: true });
|
||
|
|
cp.execFileSync(
|
||
|
|
'dotnet',
|
||
|
|
[
|
||
|
|
'publish',
|
||
|
|
trayProject,
|
||
|
|
'-c', 'Release',
|
||
|
|
'-r', 'win-x64',
|
||
|
|
'--self-contained', 'true',
|
||
|
|
'/p:PublishSingleFile=true',
|
||
|
|
'/p:PublishTrimmed=false',
|
||
|
|
'-o', trayOutputDir,
|
||
|
|
],
|
||
|
|
{ cwd: repoDir, stdio: 'inherit' }
|
||
|
|
);
|
||
|
|
|
||
|
|
const publishedTrayExePath = path.join(trayOutputDir, 'Neta.Tray.exe');
|
||
|
|
if (!fs.existsSync(publishedTrayExePath)) {
|
||
|
|
throw new Error(`缺少托盘产物: ${publishedTrayExePath}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 架构 C: 把 tools/win32/*.ps1 拷贝到 installer stage 目录(weixin-db 的 key 抽取脚本)
|
||
|
|
fs.rmSync(toolsOutputDir, { recursive: true, force: true });
|
||
|
|
fs.mkdirSync(toolsOutputDir, { recursive: true });
|
||
|
|
if (fs.existsSync(toolsSourceDir)) {
|
||
|
|
fs.cpSync(toolsSourceDir, toolsOutputDir, { recursive: true });
|
||
|
|
console.log(`[installer] copied tools/win32 → ${toolsOutputDir}`);
|
||
|
|
} else {
|
||
|
|
console.warn(`[installer] tools/win32 not found at ${toolsSourceDir}, skip`);
|
||
|
|
}
|
||
|
|
|
||
|
|
const isccCandidates = [
|
||
|
|
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Inno Setup 6', 'ISCC.exe'),
|
||
|
|
'C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe',
|
||
|
|
'C:\\Program Files\\Inno Setup 6\\ISCC.exe',
|
||
|
|
];
|
||
|
|
const iscc = isccCandidates.find(p => fs.existsSync(p)) || 'iscc';
|
||
|
|
|
||
|
|
console.log('Building installer with Inno Setup...');
|
||
|
|
cp.execFileSync(iscc, [issPath], { cwd: backendDir, stdio: 'inherit' });
|
||
|
|
console.log('Installer build complete.');
|