From 321dcd790e06017487027ac22b4254d2932d1737 Mon Sep 17 00:00:00 2001 From: Khaliq Date: Sat, 6 Jun 2026 10:47:32 +0200 Subject: [PATCH 1/5] chore(deps): bump agentworkforce 3.0.42 -> 3.0.50 for cloud relay MCP Pulls @agentworkforce/runtime@3.0.50, which wires the Relay MCP server into cloud personas for both Claude and Codex harnesses (workforce #205). The lockfile previously pinned the whole agentworkforce family to 3.0.42, so `npm ci` installed a runtime without the relay-broker MCP wiring. Bump the two direct deps (agentworkforce, @agentworkforce/deploy) and regenerate the lock so the family resolves to 3.0.50. Co-Authored-By: Claude Opus 4.8 --- package-lock.json | 83 ++++++++++++++++++++++++----------------------- package.json | 4 +-- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4241557a..2fc8831c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@agent-relay/cloud": "^8.1.2", "@agent-relay/harness-driver": "^8.1.2", "@agent-relay/sdk": "^8.1.2", - "@agentworkforce/deploy": "^3.0.42", + "@agentworkforce/deploy": "^3.0.50", "@relayburn/sdk": "^3.2.0", "@relayfile/sdk": "^0.8.10", "@xterm/addon-fit": "^0.10.0", @@ -21,7 +21,7 @@ "@xterm/xterm": "^5.5.0", "@xyflow/react": "^12.4.0", "agent-relay": "^8.1.2", - "agentworkforce": "^3.0.42", + "agentworkforce": "^3.0.50", "ai-hist": "^0.2.3", "allotment": "^1.0.9", "electron-updater": "^6.3.9", @@ -263,14 +263,15 @@ } }, "node_modules/@agentworkforce/cli": { - "version": "3.0.42", - "resolved": "https://registry.npmjs.org/@agentworkforce/cli/-/cli-3.0.42.tgz", - "integrity": "sha512-R5qJXydnKbfvcIaPbTbMwRtLDFOPSDEJM4kHtCIg5DuL1Ws4jEcwmakfR9QjM+IdKiiPbsCDCKMRq7Yz+L2Gnw==", + "version": "3.0.50", + "resolved": "https://registry.npmjs.org/@agentworkforce/cli/-/cli-3.0.50.tgz", + "integrity": "sha512-wrYey2j58hPVnGor2mIOPMUvu4ILkumbs5Yd2cp++N9BnHBsBayJapbfsw2jA6ngB6Vru7YhxikBBalmHlXrWA==", "dependencies": { "@agent-relay/cloud": "^6.0.17", - "@agentworkforce/deploy": "3.0.42", - "@agentworkforce/persona-kit": "3.0.42", - "@agentworkforce/workload-router": "3.0.42", + "@agentworkforce/deploy": "3.0.50", + "@agentworkforce/persona-kit": "3.0.50", + "@agentworkforce/runtime": "3.0.50", + "@agentworkforce/workload-router": "3.0.50", "@relayburn/sdk": "^2.5.2", "@relayfile/local-mount": "^0.7.24", "ora": "^9.4.0" @@ -299,14 +300,6 @@ "zod-to-json-schema": "^3.23.1" } }, - "node_modules/@agentworkforce/cli/node_modules/@agentworkforce/workload-router": { - "version": "3.0.42", - "resolved": "https://registry.npmjs.org/@agentworkforce/workload-router/-/workload-router-3.0.42.tgz", - "integrity": "sha512-RvKzlpHq6/O3jEGtYycBCAfiLREAfSC3XS6Z9EXT/PxeFSrUNu2PZ/oVlQRp1oZVZEU49XkpnYTV/+g5qHAcXA==", - "dependencies": { - "@agentworkforce/persona-kit": "3.0.42" - } - }, "node_modules/@agentworkforce/cli/node_modules/@relayburn/sdk": { "version": "2.10.2", "resolved": "https://registry.npmjs.org/@relayburn/sdk/-/sdk-2.10.2.tgz", @@ -382,13 +375,13 @@ } }, "node_modules/@agentworkforce/deploy": { - "version": "3.0.42", - "resolved": "https://registry.npmjs.org/@agentworkforce/deploy/-/deploy-3.0.42.tgz", - "integrity": "sha512-SjqtDsbY+bs2Lz9o446yqYW8VZqJ3rJr/C/37xNgD5gYSSBIzU7WIvGty1PdUcMsOUnDNWvPwl0SsE8r4A0tzA==", + "version": "3.0.50", + "resolved": "https://registry.npmjs.org/@agentworkforce/deploy/-/deploy-3.0.50.tgz", + "integrity": "sha512-nInbeyd4nwD4gGF8TJBBOjBPIxKJAytgQcdRhxbSuZYSiX4qBCC55dohMm7DbsPeAb/KS7B6CmSIuyTBwkrQ4w==", "dependencies": { "@agent-relay/cloud": "^6.0.17", - "@agentworkforce/persona-kit": "3.0.42", - "@agentworkforce/runtime": "3.0.42", + "@agentworkforce/persona-kit": "3.0.50", + "@agentworkforce/runtime": "3.0.50", "@daytonaio/sdk": "^0.179.0", "esbuild": "^0.25.0" } @@ -417,24 +410,32 @@ } }, "node_modules/@agentworkforce/persona-kit": { - "version": "3.0.42", - "resolved": "https://registry.npmjs.org/@agentworkforce/persona-kit/-/persona-kit-3.0.42.tgz", - "integrity": "sha512-lkLObO/9n21hX7RMSFqD3Op5LqZ4QAcd0HYtoK/iGi++mM0SrwBEqnyn/nt5hOnR7tHTFW7/GhAhSohrFUBQRA==", + "version": "3.0.50", + "resolved": "https://registry.npmjs.org/@agentworkforce/persona-kit/-/persona-kit-3.0.50.tgz", + "integrity": "sha512-fslQ5+JjV/WNTSPwhZZZYE493foFIyrAB0nCttPsm3+kKWeLy5Q31jx5x1K7aIzpy7njjd0VEMitDBppoFMoHw==", "dependencies": { "@relayfile/adapter-core": "^0.3.31", "@relayfile/local-mount": "^0.7.24" } }, "node_modules/@agentworkforce/runtime": { - "version": "3.0.42", - "resolved": "https://registry.npmjs.org/@agentworkforce/runtime/-/runtime-3.0.42.tgz", - "integrity": "sha512-Z/CCaxFr9arYQF/djCgj3rB90tjrJx7tZFfXT1ZeVYBoBSFsiLbWvReoqoh2dTC8+OAsNDb3Mt1PDKc/qyhO7w==", + "version": "3.0.50", + "resolved": "https://registry.npmjs.org/@agentworkforce/runtime/-/runtime-3.0.50.tgz", + "integrity": "sha512-xchiI78YgkkkfQUPwUE0LYbeZCRhHyhWLr2KinMgHvJd2g2rAQR+JUF585zlCyiDGMIAdWrY4RaPAKfo7WI43Q==", "dependencies": { "@agent-assistant/proactive": "^0.4.32", - "@agentworkforce/persona-kit": "3.0.42", + "@agentworkforce/persona-kit": "3.0.50", "@relayfile/adapter-core": "^0.3.29" } }, + "node_modules/@agentworkforce/workload-router": { + "version": "3.0.50", + "resolved": "https://registry.npmjs.org/@agentworkforce/workload-router/-/workload-router-3.0.50.tgz", + "integrity": "sha512-AJtdcv3rPKi595Q24Qdz6E0qCnEhFKKVFjAN2km+yv9Z8g5JrBGl7AxNekYBfAs6fHamxjy2V7sJ2bX5PeMuBA==", + "dependencies": { + "@agentworkforce/persona-kit": "3.0.50" + } + }, "node_modules/@aws-crypto/crc32": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", @@ -4846,9 +4847,9 @@ } }, "node_modules/@relayfile/adapter-core": { - "version": "0.3.32", - "resolved": "https://registry.npmjs.org/@relayfile/adapter-core/-/adapter-core-0.3.32.tgz", - "integrity": "sha512-vKWJVC+nvdxsPBl5K5LDtPpLE4mkAe4sIJ/bETGUooqJzSExkEDryf2asPynxuHQjobs8LlNeUr2xQmdgSDt0g==", + "version": "0.3.35", + "resolved": "https://registry.npmjs.org/@relayfile/adapter-core/-/adapter-core-0.3.35.tgz", + "integrity": "sha512-MUKPVpTqmc1RwBGAIidIq99RZKAv7Zb9rnvNtgL71Ylv3Jc4CMIk0GqQGKU3gGQ+TQYHMEdiWdsx0Bs9qIOXIQ==", "license": "MIT", "dependencies": { "@scalar/postman-to-openapi": "^0.6.0", @@ -7203,11 +7204,11 @@ } }, "node_modules/agentworkforce": { - "version": "3.0.42", - "resolved": "https://registry.npmjs.org/agentworkforce/-/agentworkforce-3.0.42.tgz", - "integrity": "sha512-C7xonzxmt0dDIh4h62yRAoTWOQ0CE2rRldZOWZ3rTwhprgOMKXsUleg6hAyACK+KtGpOfRUA3pz9TwBpVlu2kA==", + "version": "3.0.50", + "resolved": "https://registry.npmjs.org/agentworkforce/-/agentworkforce-3.0.50.tgz", + "integrity": "sha512-cawqUArTVt+Gx3gIcH6ZWIJrwu0h1jbGSgoe4nfIhXD5siSOMXDMWuoDgnpLFYsV1QJX4k8DSH6ukK5621KyYw==", "dependencies": { - "@agentworkforce/cli": "3.0.42" + "@agentworkforce/cli": "3.0.50" }, "bin": { "agentworkforce": "bin/agentworkforce.js" @@ -9875,9 +9876,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", "license": "MIT", "engines": { "node": ">=18" @@ -13156,9 +13157,9 @@ } }, "node_modules/string-width": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz", - "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", "license": "MIT", "dependencies": { "get-east-asian-width": "^1.5.0", diff --git a/package.json b/package.json index 73ea1813..e218099e 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "@agent-relay/cloud": "^8.1.2", "@agent-relay/harness-driver": "^8.1.2", "@agent-relay/sdk": "^8.1.2", - "@agentworkforce/deploy": "^3.0.42", + "@agentworkforce/deploy": "^3.0.50", "@relayburn/sdk": "^3.2.0", "@relayfile/sdk": "^0.8.10", "@xterm/addon-fit": "^0.10.0", @@ -36,7 +36,7 @@ "@xterm/xterm": "^5.5.0", "@xyflow/react": "^12.4.0", "agent-relay": "^8.1.2", - "agentworkforce": "^3.0.42", + "agentworkforce": "^3.0.50", "ai-hist": "^0.2.3", "allotment": "^1.0.9", "electron-updater": "^6.3.9", From ca0d80b1c38df0731cf97c4a742dfb956f44094a Mon Sep 17 00:00:00 2001 From: "agent-relay-code[bot]" Date: Sat, 6 Jun 2026 08:53:53 +0000 Subject: [PATCH 2/5] chore: apply pr-reviewer fixes for #121 --- src/main/broker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/broker.ts b/src/main/broker.ts index b00d7d2b..9797cd4f 100644 --- a/src/main/broker.ts +++ b/src/main/broker.ts @@ -365,7 +365,7 @@ const MAX_BROKER_TIMEOUTS_BEFORE_REVIVE = 2 const BROKER_REVIVE_TERM_GRACE_MS = 1_500 const PERSONA_REGISTRATION_TIMEOUT_MS = 5_000 const PERSONA_REGISTRATION_STABILITY_MS = 1_000 -const AGENTWORKFORCE_CLI_VERSION = '3.0.35' +const AGENTWORKFORCE_CLI_VERSION = '3.0.50' function delay(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)) From 6ff0394c3a8c96bbc22b25da41921741f74cf578 Mon Sep 17 00:00:00 2001 From: Khaliq Date: Sat, 6 Jun 2026 13:39:42 +0200 Subject: [PATCH 3/5] Fix relayfile mount binary install publish --- scripts/install-relayfile-mount.mjs | 29 ++++---- .../relayfile-mount-install-script.test.ts | 71 +++++++++++++++++++ 2 files changed, 87 insertions(+), 13 deletions(-) create mode 100644 src/main/__tests__/relayfile-mount-install-script.test.ts diff --git a/scripts/install-relayfile-mount.mjs b/scripts/install-relayfile-mount.mjs index bcc7a808..bdb2e4c3 100644 --- a/scripts/install-relayfile-mount.mjs +++ b/scripts/install-relayfile-mount.mjs @@ -13,7 +13,7 @@ // Release mode (`--release` or RELAYFILE_MOUNT_INSTALL_SOURCE=release) ignores // local binaries and installs from the matching GitHub release unless that // release binary is already present. -import { access, chmod, copyFile, mkdir, readFile, realpath, rename, writeFile } from 'node:fs/promises' +import { access, chmod, copyFile, mkdir, readFile, realpath, rename, rm, writeFile } from 'node:fs/promises' import { constants, createWriteStream } from 'node:fs' import { dirname, join, resolve, sep } from 'node:path' import { fileURLToPath } from 'node:url' @@ -81,30 +81,33 @@ async function installFromFile(source, marker = `local:${source}`) { console.log(`[relayfile-mount] already installed at ${target}`) return } - await copyFile(source, target) - await chmod(target, 0o755) + await replaceTargetWithExecutable((tempPath) => copyFile(source, tempPath)) await writeFile(versionMarker, `${marker}\n`) console.log(`[relayfile-mount] installed ${source} -> ${target}`) } -async function downloadRelease(version) { - const asset = `relayfile-mount-${archSuffix()}` - const url = `https://github.com/AgentWorkforce/relayfile/releases/download/v${version}/${asset}` - console.log(`[relayfile-mount] downloading ${url}`) - const response = await fetch(url, { redirect: 'follow' }) - if (!response.ok || !response.body) { - throw new Error(`download failed: ${response.status} ${response.statusText} for ${url}`) - } +async function replaceTargetWithExecutable(writeTemp) { await mkdir(dirname(target), { recursive: true }) const tempPath = `${target}.tmp-${process.pid}` try { - await pipeline(response.body, createWriteStream(tempPath)) + await writeTemp(tempPath) await chmod(tempPath, 0o755) await rename(tempPath, target) } catch (error) { - await import('node:fs/promises').then(({ rm }) => rm(tempPath, { force: true })) + await rm(tempPath, { force: true }).catch(() => {}) throw error } +} + +async function downloadRelease(version) { + const asset = `relayfile-mount-${archSuffix()}` + const url = `https://github.com/AgentWorkforce/relayfile/releases/download/v${version}/${asset}` + console.log(`[relayfile-mount] downloading ${url}`) + const response = await fetch(url, { redirect: 'follow' }) + if (!response.ok || !response.body) { + throw new Error(`download failed: ${response.status} ${response.statusText} for ${url}`) + } + await replaceTargetWithExecutable((tempPath) => pipeline(response.body, createWriteStream(tempPath))) await writeFile(versionMarker, `${version}\n`) console.log(`[relayfile-mount] installed v${version} -> ${target}`) } diff --git a/src/main/__tests__/relayfile-mount-install-script.test.ts b/src/main/__tests__/relayfile-mount-install-script.test.ts new file mode 100644 index 00000000..e256278c --- /dev/null +++ b/src/main/__tests__/relayfile-mount-install-script.test.ts @@ -0,0 +1,71 @@ +import assert from 'node:assert/strict' +import { execFile } from 'node:child_process' +import { mkdtemp, mkdir, copyFile, lstat, readFile, rm, symlink, writeFile, readdir, stat } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { dirname, join } from 'node:path' +import { promisify } from 'node:util' +import { fileURLToPath } from 'node:url' +import test from 'node:test' + +const execFileAsync = promisify(execFile) +const __dirname = dirname(fileURLToPath(import.meta.url)) +const repoRoot = join(__dirname, '..', '..', '..') +const installScriptPath = join(repoRoot, 'scripts', 'install-relayfile-mount.mjs') + +async function withTempRepo(run: (repoRoot: string) => Promise) { + const tempRoot = await mkdtemp(join(tmpdir(), 'pear-relayfile-install-')) + try { + await mkdir(join(tempRoot, 'scripts'), { recursive: true }) + await copyFile(installScriptPath, join(tempRoot, 'scripts', 'install-relayfile-mount.mjs')) + await writeFile(join(tempRoot, 'package.json'), '{"name":"pear-install-test"}\n') + await run(tempRoot) + } finally { + await rm(tempRoot, { recursive: true, force: true }) + } +} + +async function runInstall(repoRoot: string, source: string) { + await execFileAsync(process.execPath, [join(repoRoot, 'scripts', 'install-relayfile-mount.mjs')], { + cwd: repoRoot, + env: { + ...process.env, + RELAYFILE_MOUNT_BIN: source + } + }) +} + +test('installFromFile replaces the target instead of copying through an existing symlink', async () => { + await withTempRepo(async (repoRoot) => { + const source = join(repoRoot, 'source-relayfile-mount') + const target = join(repoRoot, 'bin', 'relayfile-mount') + const previousTarget = join(repoRoot, 'previous-target') + + await writeFile(source, 'new-binary\n') + await mkdir(dirname(target), { recursive: true }) + await writeFile(previousTarget, 'old-binary\n') + await symlink(previousTarget, target) + + await runInstall(repoRoot, source) + + assert.equal(await readFile(previousTarget, 'utf8'), 'old-binary\n') + assert.equal(await readFile(target, 'utf8'), 'new-binary\n') + assert.equal((await lstat(target)).isSymbolicLink(), false) + assert.equal((await stat(target)).mode & 0o777, 0o755) + assert.equal(await readFile(join(repoRoot, 'bin', '.relayfile-mount-version'), 'utf8'), `local:${source}\n`) + }) +}) + +test('installFromFile removes the staged temp file when publish fails', async () => { + await withTempRepo(async (repoRoot) => { + const source = join(repoRoot, 'source-relayfile-mount') + const target = join(repoRoot, 'bin', 'relayfile-mount') + + await writeFile(source, 'new-binary\n') + await mkdir(target, { recursive: true }) + + await assert.rejects(runInstall(repoRoot, source), /EISDIR|ENOTDIR|directory/) + + const binEntries = await readdir(join(repoRoot, 'bin')) + assert.deepEqual(binEntries, ['relayfile-mount']) + }) +}) From 50484312ff4e4455fd2148cd804030c5761a6fea Mon Sep 17 00:00:00 2001 From: "agent-relay-code[bot]" Date: Sat, 6 Jun 2026 11:46:29 +0000 Subject: [PATCH 4/5] chore: apply pr-reviewer fixes for #128 --- src/main/__tests__/relayfile-mount-install-script.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/__tests__/relayfile-mount-install-script.test.ts b/src/main/__tests__/relayfile-mount-install-script.test.ts index e256278c..027b4e9c 100644 --- a/src/main/__tests__/relayfile-mount-install-script.test.ts +++ b/src/main/__tests__/relayfile-mount-install-script.test.ts @@ -34,7 +34,9 @@ async function runInstall(repoRoot: string, source: string) { }) } -test('installFromFile replaces the target instead of copying through an existing symlink', async () => { +test('installFromFile replaces the target instead of copying through an existing symlink', { + skip: process.platform === 'win32' ? 'Symlinks on Windows require elevated privileges or Developer Mode' : false +}, async () => { await withTempRepo(async (repoRoot) => { const source = join(repoRoot, 'source-relayfile-mount') const target = join(repoRoot, 'bin', 'relayfile-mount') @@ -63,7 +65,7 @@ test('installFromFile removes the staged temp file when publish fails', async () await writeFile(source, 'new-binary\n') await mkdir(target, { recursive: true }) - await assert.rejects(runInstall(repoRoot, source), /EISDIR|ENOTDIR|directory/) + await assert.rejects(runInstall(repoRoot, source), /EISDIR|ENOTDIR|EPERM|EACCES|directory/) const binEntries = await readdir(join(repoRoot, 'bin')) assert.deepEqual(binEntries, ['relayfile-mount']) From ae946e522b9c990b5548e9d3559a804224407d95 Mon Sep 17 00:00:00 2001 From: "agent-relay-code[bot]" Date: Sat, 6 Jun 2026 12:06:03 +0000 Subject: [PATCH 5/5] chore: apply pr-reviewer fixes for #128 --- scripts/install-relayfile-mount.mjs | 5 +++-- .../relayfile-mount-install-script.test.ts | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/scripts/install-relayfile-mount.mjs b/scripts/install-relayfile-mount.mjs index bdb2e4c3..eff6ca98 100644 --- a/scripts/install-relayfile-mount.mjs +++ b/scripts/install-relayfile-mount.mjs @@ -13,7 +13,7 @@ // Release mode (`--release` or RELAYFILE_MOUNT_INSTALL_SOURCE=release) ignores // local binaries and installs from the matching GitHub release unless that // release binary is already present. -import { access, chmod, copyFile, mkdir, readFile, realpath, rename, rm, writeFile } from 'node:fs/promises' +import { access, chmod, copyFile, lstat, mkdir, readFile, realpath, rename, rm, writeFile } from 'node:fs/promises' import { constants, createWriteStream } from 'node:fs' import { dirname, join, resolve, sep } from 'node:path' import { fileURLToPath } from 'node:url' @@ -75,7 +75,8 @@ function fail(message) { async function installFromFile(source, marker = `local:${source}`) { await mkdir(dirname(target), { recursive: true }) - if ((await realpath(source).catch(() => null)) === (await realpath(target).catch(() => null))) { + const targetIsSymlink = await lstat(target).then((stats) => stats.isSymbolicLink()).catch(() => false) + if (!targetIsSymlink && (await realpath(source).catch(() => null)) === (await realpath(target).catch(() => null))) { await chmod(target, 0o755) await writeFile(versionMarker, `${marker}\n`) console.log(`[relayfile-mount] already installed at ${target}`) diff --git a/src/main/__tests__/relayfile-mount-install-script.test.ts b/src/main/__tests__/relayfile-mount-install-script.test.ts index 027b4e9c..8c496461 100644 --- a/src/main/__tests__/relayfile-mount-install-script.test.ts +++ b/src/main/__tests__/relayfile-mount-install-script.test.ts @@ -57,6 +57,26 @@ test('installFromFile replaces the target instead of copying through an existing }) }) +test('installFromFile replaces an existing symlink to the requested source', { + skip: process.platform === 'win32' ? 'Symlinks on Windows require elevated privileges or Developer Mode' : false +}, async () => { + await withTempRepo(async (repoRoot) => { + const source = join(repoRoot, 'source-relayfile-mount') + const target = join(repoRoot, 'bin', 'relayfile-mount') + + await writeFile(source, 'new-binary\n') + await mkdir(dirname(target), { recursive: true }) + await symlink(source, target) + + await runInstall(repoRoot, source) + + assert.equal(await readFile(target, 'utf8'), 'new-binary\n') + assert.equal((await lstat(target)).isSymbolicLink(), false) + assert.equal((await stat(target)).mode & 0o777, 0o755) + assert.equal(await readFile(join(repoRoot, 'bin', '.relayfile-mount-version'), 'utf8'), `local:${source}\n`) + }) +}) + test('installFromFile removes the staged temp file when publish fails', async () => { await withTempRepo(async (repoRoot) => { const source = join(repoRoot, 'source-relayfile-mount')