@relayfile/sdk 0.8.19 → 0.8.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
1
  import { spawn } from "node:child_process";
2
- import { constants as fsConstants, createWriteStream } from "node:fs";
3
- import { access, mkdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
2
+ import { createWriteStream } from "node:fs";
3
+ import { mkdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import process from "node:process";
6
- import { fileURLToPath } from "node:url";
7
6
  import { RelayFileClient } from "./client.js";
7
+ import { getRelayfileMountBinaryPath } from "./mount-path.js";
8
8
  import { CloudAbortError, MountModeUnavailableError, MountReadyTimeoutError, RelayfileSetupError } from "./setup-errors.js";
9
9
  const DEFAULT_READY_POLL_INTERVAL_MS = 250;
10
10
  const DEFAULT_STOP_TIMEOUT_MS = 10_000;
@@ -295,43 +295,11 @@ function normalizeNonEmptyString(value) {
295
295
  return typeof value === "string" && value.trim() !== "" ? value : undefined;
296
296
  }
297
297
  async function resolveRelayfileMountCommand() {
298
- const executableFromPath = await findExecutableInPath("relayfile-mount");
299
- if (executableFromPath) {
300
- return executableFromPath;
301
- }
302
- const candidates = [
303
- process.env.RELAYFILE_MOUNT_BIN,
304
- fileURLToPath(new URL("../../../../bin/relayfile-mount", import.meta.url))
305
- ];
306
- for (const candidate of candidates) {
307
- if (!candidate) {
308
- continue;
309
- }
310
- if (await isExecutable(candidate)) {
311
- return candidate;
312
- }
313
- }
314
- return "relayfile-mount";
315
- }
316
- async function findExecutableInPath(command) {
317
- const pathValue = process.env.PATH ?? "";
318
- const pathEntries = pathValue.split(path.delimiter).filter(Boolean);
319
- for (const entry of pathEntries) {
320
- const candidate = path.join(entry, command);
321
- if (await isExecutable(candidate)) {
322
- return candidate;
323
- }
324
- }
325
- return null;
326
- }
327
- async function isExecutable(candidate) {
328
- try {
329
- await access(candidate, fsConstants.X_OK);
330
- return true;
331
- }
332
- catch {
333
- return false;
334
- }
298
+ // Delegates to the shared resolver, which checks the RELAYFILE_MOUNT_BIN
299
+ // override, local source-checkout builds, the platform-specific optional-dep
300
+ // package (@relayfile/mount-<platform>-<arch>), then PATH. Falls back to the
301
+ // bare command name so spawn surfaces a clear ENOENT if nothing is found.
302
+ return getRelayfileMountBinaryPath() ?? "relayfile-mount";
335
303
  }
336
304
  async function rotateMountLogIfNeeded(logPath) {
337
305
  try {
@@ -0,0 +1,18 @@
1
+ export declare function getOptionalDepPackageName(platform?: string, arch?: string): string;
2
+ /**
3
+ * Resolve the relayfile-mount binary path.
4
+ *
5
+ * Search order:
6
+ * 1. Explicit env override (RELAYFILE_MOUNT_BIN)
7
+ * 2. Local source-checkout build (bin/ or dist/), when loaded from a checkout
8
+ * 3. Platform-specific optional-dep package — primary production path
9
+ * 4. PATH lookup
10
+ *
11
+ * @returns Absolute path to the binary, or null if none is found.
12
+ */
13
+ export declare function getRelayfileMountBinaryPath(): string | null;
14
+ /**
15
+ * Human-readable error explaining that the optional-dep package for the
16
+ * current platform/arch isn't installed.
17
+ */
18
+ export declare function formatMountNotFoundError(): string;
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Resolves the relayfile-mount binary path at runtime.
3
+ *
4
+ * Mirrors the agent-relay broker resolver (`@agent-relay/harness-driver`'s
5
+ * broker-path): the binary ships as platform-specific optional-dependency
6
+ * packages (`@relayfile/mount-<platform>-<arch>`) that npm installs only for
7
+ * the matching os/cpu, and the SDK locates the right one via `require.resolve`.
8
+ * No postinstall download is involved.
9
+ *
10
+ * Usage:
11
+ * import { getRelayfileMountBinaryPath } from "@relayfile/sdk/mount-path"
12
+ * const binPath = getRelayfileMountBinaryPath()
13
+ */
14
+ import { existsSync } from "node:fs";
15
+ import { createRequire } from "node:module";
16
+ import { delimiter, dirname, join, resolve } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+ const MOUNT_BIN = "relayfile-mount";
19
+ /**
20
+ * Node's `process.arch` ("x64"/"arm64") differs from Go's GOARCH
21
+ * ("amd64"/"arm64"). The optional-dep package name follows Node's convention
22
+ * (matching the broker packages); the source-checkout `dist/` file names follow
23
+ * Go's. This maps Node arch -> Go arch for dev resolution.
24
+ */
25
+ function goArch(arch = process.arch) {
26
+ return arch === "x64" ? "amd64" : arch;
27
+ }
28
+ export function getOptionalDepPackageName(platform = process.platform, arch = process.arch) {
29
+ return `@relayfile/mount-${platform}-${arch}`;
30
+ }
31
+ function getCurrentModuleDir() {
32
+ try {
33
+ return dirname(fileURLToPath(import.meta.url));
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ function addUnique(paths, candidate) {
40
+ if (candidate && !paths.includes(candidate)) {
41
+ paths.push(candidate);
42
+ }
43
+ }
44
+ /**
45
+ * Anchors from which to run `require.resolve` for the optional-dep package.
46
+ * Covers the SDK's own location, the entry script (CLI consumers / bundled
47
+ * installs where the SDK lives under the consumer's node_modules), and the
48
+ * consumer's cwd.
49
+ */
50
+ function getResolutionReferences() {
51
+ const refs = [];
52
+ const moduleDir = getCurrentModuleDir();
53
+ addUnique(refs, moduleDir ? join(moduleDir, "mount-path.js") : null);
54
+ if (process.argv[1]) {
55
+ addUnique(refs, process.argv[1]);
56
+ }
57
+ addUnique(refs, join(process.cwd(), "package.json"));
58
+ return refs;
59
+ }
60
+ /**
61
+ * Resolve the binary via the platform-specific optional-dependency package.
62
+ * Returns null when the optional dep is not installed (expected under
63
+ * --omit=optional, or before the package has been published for a platform).
64
+ */
65
+ function getOptionalDepBinaryPath() {
66
+ const pkgName = getOptionalDepPackageName();
67
+ for (const ref of getResolutionReferences()) {
68
+ try {
69
+ const pkgJsonPath = createRequire(ref).resolve(`${pkgName}/package.json`);
70
+ const binPath = join(dirname(pkgJsonPath), "bin", MOUNT_BIN);
71
+ if (existsSync(binPath)) {
72
+ return binPath;
73
+ }
74
+ }
75
+ catch {
76
+ // Try the next reference.
77
+ }
78
+ }
79
+ return null;
80
+ }
81
+ function isSourceCheckoutRoot(candidate) {
82
+ const root = resolve(candidate);
83
+ return (existsSync(join(root, "go.mod")) &&
84
+ existsSync(join(root, "cmd", "relayfile-mount", "main.go")));
85
+ }
86
+ function findAncestorSourceCheckoutRoot(start) {
87
+ let current = resolve(start);
88
+ for (let i = 0; i < 8; i += 1) {
89
+ if (isSourceCheckoutRoot(current)) {
90
+ return current;
91
+ }
92
+ const parent = resolve(current, "..");
93
+ if (parent === current) {
94
+ break;
95
+ }
96
+ current = parent;
97
+ }
98
+ return null;
99
+ }
100
+ /**
101
+ * Local `make build`/`make build-all` outputs, used when the SDK is loaded
102
+ * from a relayfile source checkout.
103
+ */
104
+ function getSourceCheckoutBinaryPaths() {
105
+ const paths = [];
106
+ const roots = new Set();
107
+ const addRoot = (candidate) => {
108
+ if (!candidate) {
109
+ return;
110
+ }
111
+ const root = resolve(candidate);
112
+ if (!isSourceCheckoutRoot(root) || roots.has(root)) {
113
+ return;
114
+ }
115
+ roots.add(root);
116
+ addUnique(paths, join(root, "bin", MOUNT_BIN));
117
+ addUnique(paths, join(root, "dist", `relayfile-mount-${process.platform}-${goArch()}`));
118
+ };
119
+ addRoot(findAncestorSourceCheckoutRoot(process.cwd()));
120
+ const moduleDir = getCurrentModuleDir();
121
+ if (moduleDir) {
122
+ // packages/sdk/typescript/dist -> repo root is four levels up.
123
+ addRoot(findAncestorSourceCheckoutRoot(moduleDir));
124
+ }
125
+ return paths;
126
+ }
127
+ function findExecutableInPath() {
128
+ const ext = process.platform === "win32" ? ".exe" : "";
129
+ const entries = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
130
+ for (const entry of entries) {
131
+ const candidate = join(entry, `${MOUNT_BIN}${ext}`);
132
+ if (existsSync(candidate)) {
133
+ return candidate;
134
+ }
135
+ }
136
+ return null;
137
+ }
138
+ /**
139
+ * Resolve the relayfile-mount binary path.
140
+ *
141
+ * Search order:
142
+ * 1. Explicit env override (RELAYFILE_MOUNT_BIN)
143
+ * 2. Local source-checkout build (bin/ or dist/), when loaded from a checkout
144
+ * 3. Platform-specific optional-dep package — primary production path
145
+ * 4. PATH lookup
146
+ *
147
+ * @returns Absolute path to the binary, or null if none is found.
148
+ */
149
+ export function getRelayfileMountBinaryPath() {
150
+ const override = process.env.RELAYFILE_MOUNT_BIN;
151
+ if (override) {
152
+ const resolved = resolve(override);
153
+ if (existsSync(resolved)) {
154
+ return resolved;
155
+ }
156
+ }
157
+ for (const sourcePath of getSourceCheckoutBinaryPaths()) {
158
+ if (existsSync(sourcePath)) {
159
+ return sourcePath;
160
+ }
161
+ }
162
+ const optionalDepBinary = getOptionalDepBinaryPath();
163
+ if (optionalDepBinary) {
164
+ return optionalDepBinary;
165
+ }
166
+ return findExecutableInPath();
167
+ }
168
+ /**
169
+ * Human-readable error explaining that the optional-dep package for the
170
+ * current platform/arch isn't installed.
171
+ */
172
+ export function formatMountNotFoundError() {
173
+ const pkgName = getOptionalDepPackageName();
174
+ return (`relayfile couldn't find a relayfile-mount binary for ` +
175
+ `${process.platform}-${process.arch}. The optional dependency ${pkgName} ` +
176
+ `is expected to be installed alongside @relayfile/sdk. Try reinstalling ` +
177
+ `with --include=optional, or set RELAYFILE_MOUNT_BIN to a binary you've ` +
178
+ `built or downloaded manually.`);
179
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relayfile/sdk",
3
- "version": "0.8.19",
3
+ "version": "0.8.20",
4
4
  "description": "TypeScript SDK for relayfile — real-time filesystem for humans and agents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,6 +26,10 @@
26
26
  "types": "./dist/mount-harness.d.ts",
27
27
  "default": "./dist/mount-harness.js"
28
28
  },
29
+ "./mount-path": {
30
+ "types": "./dist/mount-path.d.ts",
31
+ "default": "./dist/mount-path.js"
32
+ },
29
33
  "./workspace-seeder": {
30
34
  "types": "./dist/workspace-seeder.d.ts",
31
35
  "default": "./dist/workspace-seeder.js"
@@ -55,10 +59,16 @@
55
59
  "prepublishOnly": "npm run build"
56
60
  },
57
61
  "dependencies": {
58
- "@relayfile/core": "0.8.19",
62
+ "@relayfile/core": "0.8.20",
59
63
  "ignore": "^7.0.5",
60
64
  "tar": "^7.5.10"
61
65
  },
66
+ "optionalDependencies": {
67
+ "@relayfile/mount-darwin-arm64": "0.8.20",
68
+ "@relayfile/mount-darwin-x64": "0.8.20",
69
+ "@relayfile/mount-linux-arm64": "0.8.20",
70
+ "@relayfile/mount-linux-x64": "0.8.20"
71
+ },
62
72
  "peerDependencies": {},
63
73
  "devDependencies": {
64
74
  "typescript": "^5.7.3",