fix(ci): resolve Windows cross-drive engine workspace and strip verbatim paths

- allocate engine workspace on the repository drive when os.tmpdir() is cross-drive on Windows
- resolve RUNNER_TEMP on GitHub Actions to align workspace drive with checkout
- strip Win32 \\?\ verbatim prefixes to ensure Tauri resource normalizer compatibility
This commit is contained in:
NimBold
2026-09-05 10:28:31 +03:30
parent 006f941bae
commit f65d98bbd0
2 changed files with 68 additions and 4 deletions
+48 -4
View File
@@ -1,8 +1,12 @@
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { removePathWithRetry } from './engine-payload-promotion.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, '..');
const ARCH_MAP = { x64: 'x86_64', arm64: 'aarch64' };
const PLATFORM_MAP = {
darwin: 'apple-darwin',
@@ -94,18 +98,58 @@ export function assertSafeOutputRoot(outputRoot, forbiddenRoots = []) {
return canonicalOutputRoot;
}
export function stripVerbatimPrefix(filePath) {
return typeof filePath === 'string' && filePath.startsWith('\\\\?\\')
? filePath.slice(4)
: filePath;
}
export function resolveWorkspaceTempBase(referencePath = repoRoot) {
if (process.env.FIRELINK_ENGINE_WORKSPACE_BASE) {
return path.resolve(process.env.FIRELINK_ENGINE_WORKSPACE_BASE);
}
if (process.platform === 'win32') {
const referenceDrive = path.parse(path.resolve(referencePath)).root.toLowerCase();
if (process.env.RUNNER_TEMP) {
const runnerTemp = path.resolve(process.env.RUNNER_TEMP);
if (path.parse(runnerTemp).root.toLowerCase() === referenceDrive) {
return runnerTemp;
}
}
const osTemp = path.resolve(os.tmpdir());
if (path.parse(osTemp).root.toLowerCase() === referenceDrive) {
return osTemp;
}
const adjacentTemp = path.join(path.resolve(referencePath, '..'), '.firelink-engine-workspaces');
fs.mkdirSync(adjacentTemp, { recursive: true, mode: 0o700 });
return adjacentTemp;
}
if (process.env.RUNNER_TEMP) {
return path.resolve(process.env.RUNNER_TEMP);
}
return os.tmpdir();
}
export function createEngineWorkspace(target) {
assertSafeTarget(target);
const workspace = fs.realpathSync.native(
fs.mkdtempSync(path.join(os.tmpdir(), `firelink-engine-${target}-${process.pid}-`)),
const tempBase = resolveWorkspaceTempBase();
const rawWorkspace = fs.realpathSync.native(
fs.mkdtempSync(path.join(tempBase, `firelink-engine-${target}-${process.pid}-`)),
);
const workspace = stripVerbatimPrefix(rawWorkspace);
const outputRoot = path.join(workspace, 'engine-dist');
fs.mkdirSync(outputRoot, { recursive: true, mode: 0o700 });
return { outputRoot, runtimeRoot: outputRoot, workspace };
}
export function engineResourceConfig(outputRoot) {
const source = `${path.resolve(outputRoot)}${path.sep}`;
const source = `${stripVerbatimPrefix(path.resolve(outputRoot))}${path.sep}`;
return JSON.stringify({
bundle: {
resources: {
@@ -116,7 +160,7 @@ export function engineResourceConfig(outputRoot) {
}
export async function removeEngineWorkspace(workspace) {
const resolved = path.resolve(workspace);
const resolved = stripVerbatimPrefix(path.resolve(workspace));
const basename = path.basename(resolved);
if (!basename.startsWith('firelink-engine-')) {
throw new Error(`Refusing to remove an unexpected engine workspace: ${resolved}`);
+20
View File
@@ -11,6 +11,8 @@ import {
removeEngineWorkspace,
resolveOutputRoot,
resolveTargetTriple,
resolveWorkspaceTempBase,
stripVerbatimPrefix,
} from './engine-workspace.js';
test('target resolution accepts explicit and inline target arguments', () => {
@@ -90,3 +92,21 @@ test('output roots are checked after resolving symlinked parents', () => {
fs.rmSync(temporaryRoot, { recursive: true, force: true });
}
});
test('stripVerbatimPrefix removes Win32 verbatim namespaces', () => {
assert.equal(stripVerbatimPrefix('\\\\?\\D:\\a\\_temp'), 'D:\\a\\_temp');
assert.equal(stripVerbatimPrefix('D:\\a\\_temp'), 'D:\\a\\_temp');
assert.equal(stripVerbatimPrefix('/tmp/firelink'), '/tmp/firelink');
});
test('resolveWorkspaceTempBase honors FIRELINK_ENGINE_WORKSPACE_BASE override', () => {
const custom = path.resolve('/custom/engine/base');
const prev = process.env.FIRELINK_ENGINE_WORKSPACE_BASE;
process.env.FIRELINK_ENGINE_WORKSPACE_BASE = custom;
try {
assert.equal(resolveWorkspaceTempBase(), custom);
} finally {
if (prev === undefined) delete process.env.FIRELINK_ENGINE_WORKSPACE_BASE;
else process.env.FIRELINK_ENGINE_WORKSPACE_BASE = prev;
}
});