From f65d98bbd0c34c28ecdf50b2000662c3ec25b3ad Mon Sep 17 00:00:00 2001 From: NimBold Date: Sat, 5 Sep 2026 10:28:31 +0330 Subject: [PATCH] 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 --- scripts/engine-workspace.js | 52 ++++++++++++++++++++++++--- scripts/engine-workspace.node-test.js | 20 +++++++++++ 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/scripts/engine-workspace.js b/scripts/engine-workspace.js index 45a7fb1..86e224a 100644 --- a/scripts/engine-workspace.js +++ b/scripts/engine-workspace.js @@ -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}`); diff --git a/scripts/engine-workspace.node-test.js b/scripts/engine-workspace.node-test.js index 51d9ef7..1b9cc96 100644 --- a/scripts/engine-workspace.node-test.js +++ b/scripts/engine-workspace.node-test.js @@ -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; + } +});