@relayfile/local-mount 0.6.15 → 0.7.0
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.
- package/README.md +5 -1
- package/dist/launch.js +14 -10
- package/dist/mount.d.ts +1 -1
- package/dist/mount.js +17 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,9 +14,11 @@ npm install @relayfile/local-mount
|
|
|
14
14
|
|
|
15
15
|
### `createMount(projectDir, mountDir, options)`
|
|
16
16
|
|
|
17
|
-
Builds a mounted copy of `projectDir` at `mountDir` and
|
|
17
|
+
Builds a mounted copy of `projectDir` at `mountDir` and resolves with a handle:
|
|
18
18
|
|
|
19
19
|
```ts
|
|
20
|
+
const handle = await createMount(projectDir, mountDir, options);
|
|
21
|
+
|
|
20
22
|
interface MountHandle {
|
|
21
23
|
mountDir: string;
|
|
22
24
|
syncBack(opts?: { signal?: AbortSignal }): Promise<number>;
|
|
@@ -25,6 +27,8 @@ interface MountHandle {
|
|
|
25
27
|
}
|
|
26
28
|
```
|
|
27
29
|
|
|
30
|
+
`createMount` returns `Promise<MountHandle>`. The walker yields the event loop between directory entries so consumer-side timers (e.g. an `ora` spinner driven by `setInterval`) keep firing while the mount is being built.
|
|
31
|
+
|
|
28
32
|
Behavior:
|
|
29
33
|
- Copies regular files into the mount
|
|
30
34
|
- Applies ignore rules from `ignoredPatterns`
|
package/dist/launch.js
CHANGED
|
@@ -7,14 +7,7 @@ import { createMount } from './mount.js';
|
|
|
7
7
|
* exit code.
|
|
8
8
|
*/
|
|
9
9
|
export async function launchOnMount(opts) {
|
|
10
|
-
|
|
11
|
-
ignoredPatterns: opts.ignoredPatterns ?? [],
|
|
12
|
-
readonlyPatterns: opts.readonlyPatterns ?? [],
|
|
13
|
-
excludeDirs: opts.excludeDirs ?? [],
|
|
14
|
-
agentName: opts.agentName,
|
|
15
|
-
includeGit: opts.includeGit,
|
|
16
|
-
includeDefaultExcludeDirs: opts.includeDefaultExcludeDirs,
|
|
17
|
-
});
|
|
10
|
+
let handle;
|
|
18
11
|
let syncedCount = 0;
|
|
19
12
|
let finalized = false;
|
|
20
13
|
let autoSync;
|
|
@@ -29,6 +22,8 @@ export async function launchOnMount(opts) {
|
|
|
29
22
|
autoSyncChanges = autoSync.totalChanges();
|
|
30
23
|
autoSync = undefined;
|
|
31
24
|
}
|
|
25
|
+
if (!handle)
|
|
26
|
+
return;
|
|
32
27
|
const finalSynced = await handle.syncBack({ signal: opts.shutdownSignal });
|
|
33
28
|
syncedCount = autoSyncChanges + finalSynced;
|
|
34
29
|
if (opts.onAfterSync) {
|
|
@@ -36,10 +31,18 @@ export async function launchOnMount(opts) {
|
|
|
36
31
|
}
|
|
37
32
|
}
|
|
38
33
|
finally {
|
|
39
|
-
handle
|
|
34
|
+
handle?.cleanup();
|
|
40
35
|
}
|
|
41
36
|
};
|
|
42
37
|
try {
|
|
38
|
+
handle = await createMount(opts.projectDir, opts.mountDir, {
|
|
39
|
+
ignoredPatterns: opts.ignoredPatterns ?? [],
|
|
40
|
+
readonlyPatterns: opts.readonlyPatterns ?? [],
|
|
41
|
+
excludeDirs: opts.excludeDirs ?? [],
|
|
42
|
+
agentName: opts.agentName,
|
|
43
|
+
includeGit: opts.includeGit,
|
|
44
|
+
includeDefaultExcludeDirs: opts.includeDefaultExcludeDirs,
|
|
45
|
+
});
|
|
43
46
|
if (opts.onBeforeLaunch) {
|
|
44
47
|
await opts.onBeforeLaunch(handle.mountDir);
|
|
45
48
|
}
|
|
@@ -51,9 +54,10 @@ export async function launchOnMount(opts) {
|
|
|
51
54
|
...process.env,
|
|
52
55
|
...(opts.env ?? {}),
|
|
53
56
|
};
|
|
57
|
+
const mountCwd = handle.mountDir;
|
|
54
58
|
const exitCode = await new Promise((resolve, reject) => {
|
|
55
59
|
const child = spawn(opts.cli, opts.args, {
|
|
56
|
-
cwd:
|
|
60
|
+
cwd: mountCwd,
|
|
57
61
|
stdio: 'inherit',
|
|
58
62
|
env: envVars,
|
|
59
63
|
});
|
package/dist/mount.d.ts
CHANGED
|
@@ -41,4 +41,4 @@ export interface MountHandle {
|
|
|
41
41
|
startAutoSync(opts?: AutoSyncOptions): AutoSyncHandle;
|
|
42
42
|
cleanup(): void;
|
|
43
43
|
}
|
|
44
|
-
export declare function createMount(projectDir: string, mountDir: string, options: MountOptions): MountHandle
|
|
44
|
+
export declare function createMount(projectDir: string, mountDir: string, options: MountOptions): Promise<MountHandle>;
|
package/dist/mount.js
CHANGED
|
@@ -30,7 +30,7 @@ const DEFAULT_ROOT_EXCLUDES = [
|
|
|
30
30
|
const MOUNT_README_FILENAME = '_MOUNT_README.md';
|
|
31
31
|
const MOUNT_MARKER_FILENAME = '.relayfile-local-mount';
|
|
32
32
|
const MOUNT_MARKER_CONTENT = 'This directory is managed by @relayfile/local-mount. Do not place unrelated files here; the directory will be deleted when the mount is torn down.\n';
|
|
33
|
-
export function createMount(projectDir, mountDir, options) {
|
|
33
|
+
export async function createMount(projectDir, mountDir, options) {
|
|
34
34
|
const resolvedProjectDir = realpathSync(projectDir);
|
|
35
35
|
const resolvedMountDir = path.resolve(mountDir);
|
|
36
36
|
const readonlyPatterns = [...options.readonlyPatterns];
|
|
@@ -60,7 +60,7 @@ export function createMount(projectDir, mountDir, options) {
|
|
|
60
60
|
mkdirSync(resolvedMountDir, { recursive: true });
|
|
61
61
|
const realMountDir = realpathSync(resolvedMountDir);
|
|
62
62
|
writeFileSync(path.join(realMountDir, MOUNT_MARKER_FILENAME), MOUNT_MARKER_CONTENT, 'utf8');
|
|
63
|
-
walkProjectTree(resolvedProjectDir, resolvedProjectDir, realMountDir, realMountDir, excludeRules, readonlyMatcher, ignoredMatcher);
|
|
63
|
+
await walkProjectTree(resolvedProjectDir, resolvedProjectDir, realMountDir, realMountDir, excludeRules, readonlyMatcher, ignoredMatcher);
|
|
64
64
|
const readmePath = resolveSafeCopyTarget(realMountDir, path.join(realMountDir, MOUNT_README_FILENAME));
|
|
65
65
|
if (!readmePath) {
|
|
66
66
|
throw new Error('Failed to create mount readme inside mountDir');
|
|
@@ -143,9 +143,19 @@ function assertMountDirSafeToRemove(mountDir, projectDir) {
|
|
|
143
143
|
`Only directories previously created by createMount can be reused as mountDir.`);
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
|
-
|
|
146
|
+
// Yield often enough that a consumer's setInterval (e.g. an `ora` spinner) can
|
|
147
|
+
// tick during init even on flat directories with thousands of entries. The
|
|
148
|
+
// goal is not throughput; it is keeping the parent event loop unblocked.
|
|
149
|
+
const WALK_YIELD_EVERY = 64;
|
|
150
|
+
async function walkProjectTree(projectDir, currentDir, mountDir, currentMountDir, excludeRules, readonlyMatcher, ignoredMatcher) {
|
|
151
|
+
await yieldToEventLoop();
|
|
147
152
|
const entries = readdirSync(currentDir, { withFileTypes: true });
|
|
153
|
+
let processed = 0;
|
|
148
154
|
for (const entry of entries) {
|
|
155
|
+
if (processed > 0 && processed % WALK_YIELD_EVERY === 0) {
|
|
156
|
+
await yieldToEventLoop();
|
|
157
|
+
}
|
|
158
|
+
processed += 1;
|
|
149
159
|
const absolutePath = path.join(currentDir, entry.name);
|
|
150
160
|
const relativePath = normalizeRelativePosix(path.relative(projectDir, absolutePath));
|
|
151
161
|
if (!relativePath || relativePath.startsWith('..')) {
|
|
@@ -166,7 +176,7 @@ function walkProjectTree(projectDir, currentDir, mountDir, currentMountDir, excl
|
|
|
166
176
|
if (!safeMountDir) {
|
|
167
177
|
continue;
|
|
168
178
|
}
|
|
169
|
-
walkProjectTree(projectDir, absolutePath, mountDir, safeMountDir, excludeRules, readonlyMatcher, ignoredMatcher);
|
|
179
|
+
await walkProjectTree(projectDir, absolutePath, mountDir, safeMountDir, excludeRules, readonlyMatcher, ignoredMatcher);
|
|
170
180
|
continue;
|
|
171
181
|
}
|
|
172
182
|
if (entry.isSymbolicLink()) {
|
|
@@ -179,6 +189,9 @@ function walkProjectTree(projectDir, currentDir, mountDir, currentMountDir, excl
|
|
|
179
189
|
copyMountedFile(projectDir, mountDir, absolutePath, mountPath, relativePath, readonlyMatcher);
|
|
180
190
|
}
|
|
181
191
|
}
|
|
192
|
+
function yieldToEventLoop() {
|
|
193
|
+
return new Promise((resolve) => setImmediate(resolve));
|
|
194
|
+
}
|
|
182
195
|
function copySymlinkedFile(projectDir, mountDir, sourcePath, mountPath, relativePath, readonlyMatcher) {
|
|
183
196
|
let realSource;
|
|
184
197
|
let resolvedStat;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@relayfile/local-mount",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Create a symlink/copy mount of a project directory with .agentignore/.agentreadonly semantics, then launch a CLI inside it and sync writable changes back on exit",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|