固定桌面壳本地产物收集路径
桌面壳 release 二进制复制到根 build/native/desktop 根级原生壳门禁验证 staged 桌面二进制产物 同步原生壳方案文档和共享决策记录
This commit is contained in:
@@ -192,6 +192,11 @@ const desktopShellNavigationSource = fs.readFileSync(
|
||||
desktopShellNavigationPath,
|
||||
'utf8',
|
||||
);
|
||||
const stageReleaseBinaryPath = new URL(
|
||||
'../scripts/stage-release-binary.mjs',
|
||||
import.meta.url,
|
||||
);
|
||||
const stageReleaseBinarySource = fs.readFileSync(stageReleaseBinaryPath, 'utf8');
|
||||
const productionSourceRoots = [
|
||||
new URL('../package.json', import.meta.url),
|
||||
new URL('../scripts/', import.meta.url),
|
||||
@@ -317,6 +322,7 @@ const expectedDesktopShellRustFiles = [
|
||||
];
|
||||
const expectedDesktopShellScripts = [
|
||||
'check-config.mjs',
|
||||
'stage-release-binary.mjs',
|
||||
];
|
||||
|
||||
function extractCargoPackageString(source, key) {
|
||||
@@ -644,12 +650,12 @@ assertDesktopSourceLayout();
|
||||
for (const snippet of [
|
||||
'label: \'desktop-shell-release-build-smoke\'',
|
||||
"args: ['run', 'desktop-shell:build', '--', '--no-bundle']",
|
||||
"label: 'desktop-shell-stage-release-binary'",
|
||||
"args: ['run', 'desktop-shell:stage-release-binary']",
|
||||
'function assertDesktopReleaseBinaryArtifact()',
|
||||
"'apps'",
|
||||
"'desktop-shell'",
|
||||
"'src-tauri'",
|
||||
"'target'",
|
||||
"'release'",
|
||||
"'build'",
|
||||
"'native'",
|
||||
"'desktop'",
|
||||
'genarrative-desktop-shell.exe',
|
||||
'genarrative-desktop-shell',
|
||||
'desktop release binary is missing',
|
||||
@@ -674,9 +680,28 @@ for (const snippet of [
|
||||
}
|
||||
}
|
||||
|
||||
for (const snippet of [
|
||||
"'apps'",
|
||||
"'desktop-shell'",
|
||||
"'src-tauri'",
|
||||
"'target'",
|
||||
"'release'",
|
||||
"'build'",
|
||||
"'native'",
|
||||
"'desktop'",
|
||||
'fs.copyFileSync(sourcePath, stagedPath)',
|
||||
'fs.chmodSync(stagedPath, sourceMode & 0o777)',
|
||||
"console.log(`[desktop-shell:stage-release-binary] ${stagedPath}`)",
|
||||
]) {
|
||||
if (!stageReleaseBinarySource.includes(snippet)) {
|
||||
throw new Error(`desktop shell release staging script missing ${snippet}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [scriptName, expected] of Object.entries({
|
||||
dev: 'WEB_PORT=3000 tauri dev',
|
||||
build: 'tauri build',
|
||||
'stage-release-binary': 'node scripts/stage-release-binary.mjs',
|
||||
typecheck: 'node scripts/check-config.mjs',
|
||||
})) {
|
||||
assertPackageScript(packageConfig, 'desktop shell package', scriptName, expected);
|
||||
@@ -685,6 +710,7 @@ for (const [scriptName, expected] of Object.entries({
|
||||
for (const [scriptName, expected] of Object.entries({
|
||||
'desktop-shell:dev': 'npm --prefix apps/desktop-shell run dev',
|
||||
'desktop-shell:build': 'npm --prefix apps/desktop-shell run build --',
|
||||
'desktop-shell:stage-release-binary': 'npm --prefix apps/desktop-shell run stage-release-binary',
|
||||
'desktop-shell:typecheck': 'npm --prefix apps/desktop-shell run typecheck',
|
||||
'desktop-shell:test': 'cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml',
|
||||
})) {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const repoRoot = path.resolve(new URL('../../../', import.meta.url).pathname);
|
||||
const releaseDir = path.join(
|
||||
repoRoot,
|
||||
'apps',
|
||||
'desktop-shell',
|
||||
'src-tauri',
|
||||
'target',
|
||||
'release',
|
||||
);
|
||||
const executableName =
|
||||
process.platform === 'win32'
|
||||
? 'genarrative-desktop-shell.exe'
|
||||
: 'genarrative-desktop-shell';
|
||||
const sourcePath = path.join(releaseDir, executableName);
|
||||
const stagedDir = path.join(repoRoot, 'build', 'native', 'desktop');
|
||||
const stagedPath = path.join(stagedDir, executableName);
|
||||
|
||||
function assertExecutable(filePath, label) {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw new Error(`desktop release binary is missing: ${filePath}`);
|
||||
}
|
||||
|
||||
const stat = fs.statSync(filePath);
|
||||
if (!stat.isFile() || stat.size < 1024 * 1024) {
|
||||
throw new Error(`${label} must be a real non-empty executable file`);
|
||||
}
|
||||
|
||||
const handle = fs.openSync(filePath, 'r');
|
||||
try {
|
||||
const header = Buffer.alloc(8);
|
||||
fs.readSync(handle, header, 0, header.length, 0);
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
const isElf =
|
||||
header[0] === 0x7f &&
|
||||
header[1] === 0x45 &&
|
||||
header[2] === 0x4c &&
|
||||
header[3] === 0x46;
|
||||
if (!isElf || (stat.mode & 0o111) === 0) {
|
||||
throw new Error(`${label} must be an executable ELF file`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
const machMagic = header.readUInt32BE(0);
|
||||
const isMachO =
|
||||
machMagic === 0xcafebabe ||
|
||||
machMagic === 0xcafed00d ||
|
||||
machMagic === 0xfeedface ||
|
||||
machMagic === 0xfeedfacf;
|
||||
if (!isMachO || (stat.mode & 0o111) === 0) {
|
||||
throw new Error(`${label} must be an executable Mach-O file`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
if (header[0] !== 0x4d || header[1] !== 0x5a) {
|
||||
throw new Error(`${label} must be a PE executable`);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
fs.closeSync(handle);
|
||||
}
|
||||
}
|
||||
|
||||
assertExecutable(sourcePath, 'desktop release binary');
|
||||
fs.mkdirSync(stagedDir, {recursive: true});
|
||||
fs.copyFileSync(sourcePath, stagedPath);
|
||||
const sourceMode = fs.statSync(sourcePath).mode;
|
||||
fs.chmodSync(stagedPath, sourceMode & 0o777);
|
||||
assertExecutable(stagedPath, 'staged desktop release binary');
|
||||
|
||||
console.log(`[desktop-shell:stage-release-binary] ${stagedPath}`);
|
||||
Reference in New Issue
Block a user