@relayfile/local-mount 0.6.14 → 0.6.15
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 +43 -2
- package/dist/auto-sync.d.ts +10 -7
- package/dist/auto-sync.js +9 -11
- package/dist/launch.d.ts +5 -0
- package/dist/launch.js +1 -0
- package/dist/mount.d.ts +6 -0
- package/dist/mount.js +84 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ Behavior:
|
|
|
29
29
|
- Copies regular files into the mount
|
|
30
30
|
- Applies ignore rules from `ignoredPatterns`
|
|
31
31
|
- Marks read-only matches as mode `0o444`
|
|
32
|
-
- Excludes `.git`, `node_modules`,
|
|
32
|
+
- Excludes `.git`, `node_modules`, `.npm-cache`, and common build/cache output directories by default. Pass `includeGit: true` to opt the project's `.git` directory back in (see [Including `.git`](#including-git))
|
|
33
33
|
- Writes `_MOUNT_README.md` and `.relayfile-local-mount` into the mount
|
|
34
34
|
- Skips syncing `_MOUNT_README.md`, `.relayfile-local-mount`, ignored files, read-only files, and symlinks back to the source project
|
|
35
35
|
|
|
@@ -110,6 +110,47 @@ Conflict and delete rules:
|
|
|
110
110
|
- readonly paths never flow mount→project; project-side edits still flow into the mount (the mount copy is re-chmodded `0o444`)
|
|
111
111
|
- `_MOUNT_README.md`, `.relayfile-local-mount`, ignored paths, and excluded directories never cross
|
|
112
112
|
|
|
113
|
+
## Default Excludes
|
|
114
|
+
|
|
115
|
+
By default, mounts skip directories and files that are usually large generated output
|
|
116
|
+
or local caches. These names match at any path depth:
|
|
117
|
+
|
|
118
|
+
```txt
|
|
119
|
+
.git
|
|
120
|
+
node_modules
|
|
121
|
+
.npm-cache
|
|
122
|
+
__pycache__
|
|
123
|
+
.pytest_cache
|
|
124
|
+
.mypy_cache
|
|
125
|
+
.ruff_cache
|
|
126
|
+
.gradle
|
|
127
|
+
.nyc_output
|
|
128
|
+
.turbo
|
|
129
|
+
.cache
|
|
130
|
+
.DS_Store
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
These more generic names match only at the project root so source paths such as
|
|
134
|
+
`src/build/` or `packages/env/` are still mounted:
|
|
135
|
+
|
|
136
|
+
```txt
|
|
137
|
+
target
|
|
138
|
+
.next
|
|
139
|
+
dist
|
|
140
|
+
build
|
|
141
|
+
out
|
|
142
|
+
.venv
|
|
143
|
+
venv
|
|
144
|
+
env
|
|
145
|
+
coverage
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Pass `includeDefaultExcludeDirs: false` to opt out of the broad build/cache list.
|
|
149
|
+
For safety, `.git` stays excluded unless you also pass `includeGit: true`.
|
|
150
|
+
`excludeDirs` still appends to whichever default set is active; bare caller entries
|
|
151
|
+
retain the historical any-depth behavior, while path-style entries are root-relative
|
|
152
|
+
prefixes.
|
|
153
|
+
|
|
113
154
|
## Including `.git`
|
|
114
155
|
|
|
115
156
|
By default, the project's `.git` directory is excluded from the mount, which means git commands inside the mount fail with `fatal: not a git repository`. Pass `includeGit: true` (on `createMount` or `launchOnMount`) to copy `.git` into the mount with **one-way project→mount sync**:
|
|
@@ -192,7 +233,7 @@ const result = await launchOnMount({
|
|
|
192
233
|
mountDir,
|
|
193
234
|
ignoredPatterns,
|
|
194
235
|
readonlyPatterns,
|
|
195
|
-
excludeDirs: ['
|
|
236
|
+
excludeDirs: ['vendor-cache'],
|
|
196
237
|
agentName: 'reviewer',
|
|
197
238
|
onBeforeLaunch: async (dir) => {
|
|
198
239
|
// Add extra instructions or scratch files inside the mount if needed.
|
package/dist/auto-sync.d.ts
CHANGED
|
@@ -3,14 +3,17 @@ export interface AutoSyncContext {
|
|
|
3
3
|
realProjectDir: string;
|
|
4
4
|
isExcluded: (relPosix: string) => boolean;
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
9
|
-
* to skip subscribing to. The in-handler `isSyncCandidate` filter remains
|
|
10
|
-
* authoritative; this is just a perf hint that keeps the watcher from
|
|
11
|
-
* recursing into heavy trees like `node_modules` or `.npm-cache`.
|
|
6
|
+
* Normalized directory names that drive any-depth `isExcluded` matches.
|
|
7
|
+
* Used purely to hint `@parcel/watcher` which subtrees to skip subscribing
|
|
8
|
+
* to. The in-handler `isSyncCandidate` filter remains authoritative.
|
|
12
9
|
*/
|
|
13
|
-
|
|
10
|
+
excludedAnyDepthNames: readonly string[];
|
|
11
|
+
/**
|
|
12
|
+
* Root-anchored excluded names/prefixes such as `build` or `packages/cache`.
|
|
13
|
+
* These are matched only from the watch root to avoid hiding legitimate
|
|
14
|
+
* nested source directories like `src/build`.
|
|
15
|
+
*/
|
|
16
|
+
excludedRootPrefixes: readonly string[];
|
|
14
17
|
/**
|
|
15
18
|
* Directory-only ignore patterns (ending in `/`) must only match when the
|
|
16
19
|
* path is a directory. Callers that know the path's type pass `isDirectory`;
|
package/dist/auto-sync.js
CHANGED
|
@@ -170,23 +170,21 @@ function buildIgnoreGlobs(ctx, watchRoot) {
|
|
|
170
170
|
// `isExcludedPath`'s semantics, so a watcher-suppressed event never differs
|
|
171
171
|
// from what the in-handler filter would have rejected.
|
|
172
172
|
//
|
|
173
|
-
// -
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
// -
|
|
173
|
+
// - Any-depth names (e.g. `node_modules`) emit `**/<name>` plus
|
|
174
|
+
// `**/<name>/**`. picomatch turns both into depth-agnostic regexes
|
|
175
|
+
// that catch the dir and its descendants.
|
|
176
|
+
// - Root prefixes (e.g. `build` or `build/cache`) are root-anchored
|
|
177
177
|
// in `isExcludedPath` — they only match `<root>/build/cache`, NOT
|
|
178
178
|
// `<root>/src/build/cache`. Emit absolute patterns rooted at the
|
|
179
179
|
// watch dir so the watcher hides the same set: a literal absolute
|
|
180
180
|
// path (which the wrapper routes to ignorePaths) plus an anchored
|
|
181
181
|
// descendant glob.
|
|
182
182
|
const globs = [];
|
|
183
|
-
for (const name of ctx.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
globs.push(`**/${name}`, `**/${name}/**`);
|
|
189
|
-
}
|
|
183
|
+
for (const name of ctx.excludedAnyDepthNames) {
|
|
184
|
+
globs.push(`**/${name}`, `**/${name}/**`);
|
|
185
|
+
}
|
|
186
|
+
for (const prefix of ctx.excludedRootPrefixes) {
|
|
187
|
+
globs.push(`${watchRoot}/${prefix}`, `${watchRoot}/${prefix}/**`);
|
|
190
188
|
}
|
|
191
189
|
return globs;
|
|
192
190
|
}
|
package/dist/launch.d.ts
CHANGED
|
@@ -14,6 +14,11 @@ export interface LaunchOnMountOptions {
|
|
|
14
14
|
readonlyPatterns?: string[];
|
|
15
15
|
/** Extra directory names to exclude from the mount on top of defaults. */
|
|
16
16
|
excludeDirs?: string[];
|
|
17
|
+
/**
|
|
18
|
+
* Include the built-in cache/build exclusion list. Defaults to true. `.git`
|
|
19
|
+
* remains excluded unless `includeGit` is true.
|
|
20
|
+
*/
|
|
21
|
+
includeDefaultExcludeDirs?: boolean;
|
|
17
22
|
/**
|
|
18
23
|
* Include the project's `.git` directory inside the mount with one-way
|
|
19
24
|
* project→mount sync. Defaults to false. See {@link MountOptions.includeGit}
|
package/dist/launch.js
CHANGED
|
@@ -13,6 +13,7 @@ export async function launchOnMount(opts) {
|
|
|
13
13
|
excludeDirs: opts.excludeDirs ?? [],
|
|
14
14
|
agentName: opts.agentName,
|
|
15
15
|
includeGit: opts.includeGit,
|
|
16
|
+
includeDefaultExcludeDirs: opts.includeDefaultExcludeDirs,
|
|
16
17
|
});
|
|
17
18
|
let syncedCount = 0;
|
|
18
19
|
let finalized = false;
|
package/dist/mount.d.ts
CHANGED
|
@@ -21,6 +21,12 @@ export interface MountOptions {
|
|
|
21
21
|
* are discarded with the mount on cleanup. Push to a remote to keep them.
|
|
22
22
|
*/
|
|
23
23
|
includeGit?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Include the built-in list of large cache/build output directories in
|
|
26
|
+
* the mount exclusion set. Default: true. The `.git` directory remains
|
|
27
|
+
* excluded unless `includeGit` is true, even when this is false.
|
|
28
|
+
*/
|
|
29
|
+
includeDefaultExcludeDirs?: boolean;
|
|
24
30
|
}
|
|
25
31
|
export interface MountHandle {
|
|
26
32
|
mountDir: string;
|
package/dist/mount.js
CHANGED
|
@@ -2,7 +2,31 @@ import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync,
|
|
|
2
2
|
import ignore from 'ignore';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { startAutoSync, } from './auto-sync.js';
|
|
5
|
-
const
|
|
5
|
+
const DEFAULT_ANY_DEPTH_EXCLUDES = [
|
|
6
|
+
'.git',
|
|
7
|
+
'node_modules',
|
|
8
|
+
'.npm-cache',
|
|
9
|
+
'__pycache__',
|
|
10
|
+
'.pytest_cache',
|
|
11
|
+
'.mypy_cache',
|
|
12
|
+
'.ruff_cache',
|
|
13
|
+
'.gradle',
|
|
14
|
+
'.nyc_output',
|
|
15
|
+
'.turbo',
|
|
16
|
+
'.cache',
|
|
17
|
+
'.DS_Store',
|
|
18
|
+
];
|
|
19
|
+
const DEFAULT_ROOT_EXCLUDES = [
|
|
20
|
+
'target',
|
|
21
|
+
'.next',
|
|
22
|
+
'dist',
|
|
23
|
+
'build',
|
|
24
|
+
'out',
|
|
25
|
+
'.venv',
|
|
26
|
+
'venv',
|
|
27
|
+
'env',
|
|
28
|
+
'coverage',
|
|
29
|
+
];
|
|
6
30
|
const MOUNT_README_FILENAME = '_MOUNT_README.md';
|
|
7
31
|
const MOUNT_MARKER_FILENAME = '.relayfile-local-mount';
|
|
8
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';
|
|
@@ -14,16 +38,12 @@ export function createMount(projectDir, mountDir, options) {
|
|
|
14
38
|
const includeGit = options.includeGit === true;
|
|
15
39
|
const readonlyMatcher = createPathMatcher(readonlyPatterns);
|
|
16
40
|
const ignoredMatcher = createPathMatcher(ignoredPatterns);
|
|
17
|
-
|
|
41
|
+
const includeDefaultExcludeDirs = options.includeDefaultExcludeDirs !== false;
|
|
42
|
+
// `.git` is in the default any-depth excludes so the mount stays small and git
|
|
18
43
|
// operations don't accidentally cross-mutate the host repo. When the caller
|
|
19
44
|
// opts in via `includeGit`, drop it from the defaults and instead route it
|
|
20
45
|
// through the noSyncBack matcher below so it stays one-way.
|
|
21
|
-
const
|
|
22
|
-
? DEFAULT_EXCLUDED_DIRS.filter((d) => d !== '.git')
|
|
23
|
-
: DEFAULT_EXCLUDED_DIRS;
|
|
24
|
-
const excludeSet = new Set([...defaultExcludes, ...options.excludeDirs]
|
|
25
|
-
.map((entry) => normalizeRelativePosix(entry).replace(/^\/+|\/+$/g, ''))
|
|
26
|
-
.filter(Boolean));
|
|
46
|
+
const excludeRules = createExcludeRules(options.excludeDirs, includeGit, includeDefaultExcludeDirs);
|
|
27
47
|
const noSyncBackPatterns = includeGit ? ['.git', '.git/**'] : [];
|
|
28
48
|
const noSyncBackMatcher = createPathMatcher(noSyncBackPatterns);
|
|
29
49
|
// Guard against mountDir === projectDir. We compare both the realpath'd
|
|
@@ -40,7 +60,7 @@ export function createMount(projectDir, mountDir, options) {
|
|
|
40
60
|
mkdirSync(resolvedMountDir, { recursive: true });
|
|
41
61
|
const realMountDir = realpathSync(resolvedMountDir);
|
|
42
62
|
writeFileSync(path.join(realMountDir, MOUNT_MARKER_FILENAME), MOUNT_MARKER_CONTENT, 'utf8');
|
|
43
|
-
walkProjectTree(resolvedProjectDir, resolvedProjectDir, realMountDir,
|
|
63
|
+
walkProjectTree(resolvedProjectDir, resolvedProjectDir, realMountDir, realMountDir, excludeRules, readonlyMatcher, ignoredMatcher);
|
|
44
64
|
const readmePath = resolveSafeCopyTarget(realMountDir, path.join(realMountDir, MOUNT_README_FILENAME));
|
|
45
65
|
if (!readmePath) {
|
|
46
66
|
throw new Error('Failed to create mount readme inside mountDir');
|
|
@@ -49,8 +69,9 @@ export function createMount(projectDir, mountDir, options) {
|
|
|
49
69
|
const autoSyncContext = {
|
|
50
70
|
realMountDir,
|
|
51
71
|
realProjectDir: resolvedProjectDir,
|
|
52
|
-
isExcluded: (relPosix) => isExcludedPath(relPosix,
|
|
53
|
-
|
|
72
|
+
isExcluded: (relPosix) => isExcludedPath(relPosix, excludeRules),
|
|
73
|
+
excludedAnyDepthNames: [...excludeRules.anyDepthNames],
|
|
74
|
+
excludedRootPrefixes: [...excludeRules.rootPrefixes],
|
|
54
75
|
isIgnored: (relPosix, isDir) => isPathMatched(relPosix, ignoredMatcher, isDir),
|
|
55
76
|
isReadonly: (relPosix) => isPathMatched(relPosix, readonlyMatcher),
|
|
56
77
|
isNoSyncBack: (relPosix) => isPathMatched(relPosix, noSyncBackMatcher),
|
|
@@ -68,7 +89,7 @@ export function createMount(projectDir, mountDir, options) {
|
|
|
68
89
|
if (signal?.aborted) {
|
|
69
90
|
break;
|
|
70
91
|
}
|
|
71
|
-
const syncedForFile = syncMountedFileBack(sourceFile, realMountDir, realProjectDir, readonlyMatcher, ignoredMatcher, noSyncBackMatcher);
|
|
92
|
+
const syncedForFile = syncMountedFileBack(sourceFile, realMountDir, realProjectDir, readonlyMatcher, ignoredMatcher, noSyncBackMatcher, (relPosix) => isExcludedPath(relPosix, excludeRules));
|
|
72
93
|
synced += syncedForFile;
|
|
73
94
|
if (signal && syncedForFile > 0 && !signal.aborted) {
|
|
74
95
|
await new Promise((resolve) => setImmediate(resolve));
|
|
@@ -122,7 +143,7 @@ function assertMountDirSafeToRemove(mountDir, projectDir) {
|
|
|
122
143
|
`Only directories previously created by createMount can be reused as mountDir.`);
|
|
123
144
|
}
|
|
124
145
|
}
|
|
125
|
-
function walkProjectTree(projectDir, currentDir, mountDir,
|
|
146
|
+
function walkProjectTree(projectDir, currentDir, mountDir, currentMountDir, excludeRules, readonlyMatcher, ignoredMatcher) {
|
|
126
147
|
const entries = readdirSync(currentDir, { withFileTypes: true });
|
|
127
148
|
for (const entry of entries) {
|
|
128
149
|
const absolutePath = path.join(currentDir, entry.name);
|
|
@@ -133,18 +154,19 @@ function walkProjectTree(projectDir, currentDir, mountDir, excludeSet, readonlyM
|
|
|
133
154
|
if (isPathWithinRoot(absolutePath, mountDir)) {
|
|
134
155
|
continue;
|
|
135
156
|
}
|
|
136
|
-
if (isExcludedPath(relativePath,
|
|
157
|
+
if (isExcludedPath(relativePath, excludeRules)) {
|
|
137
158
|
continue;
|
|
138
159
|
}
|
|
139
160
|
if (isPathMatched(relativePath, ignoredMatcher, entry.isDirectory())) {
|
|
140
161
|
continue;
|
|
141
162
|
}
|
|
142
|
-
const mountPath = path.join(
|
|
163
|
+
const mountPath = path.join(currentMountDir, entry.name);
|
|
143
164
|
if (entry.isDirectory()) {
|
|
144
|
-
|
|
165
|
+
const safeMountDir = ensureDirectoryWithinRoot(mountDir, mountPath);
|
|
166
|
+
if (!safeMountDir) {
|
|
145
167
|
continue;
|
|
146
168
|
}
|
|
147
|
-
walkProjectTree(projectDir, absolutePath, mountDir,
|
|
169
|
+
walkProjectTree(projectDir, absolutePath, mountDir, safeMountDir, excludeRules, readonlyMatcher, ignoredMatcher);
|
|
148
170
|
continue;
|
|
149
171
|
}
|
|
150
172
|
if (entry.isSymbolicLink()) {
|
|
@@ -194,15 +216,15 @@ function ensureDirectory(pathValue) {
|
|
|
194
216
|
}
|
|
195
217
|
function ensureDirectoryWithinRoot(rootPath, dirPath) {
|
|
196
218
|
if (!isPathWithinRoot(dirPath, rootPath)) {
|
|
197
|
-
return
|
|
219
|
+
return null;
|
|
198
220
|
}
|
|
199
221
|
try {
|
|
200
222
|
ensureDirectory(dirPath);
|
|
201
223
|
const realDir = realpathSync(dirPath);
|
|
202
|
-
return isPathWithinRoot(realDir, rootPath);
|
|
224
|
+
return isPathWithinRoot(realDir, rootPath) ? realDir : null;
|
|
203
225
|
}
|
|
204
226
|
catch {
|
|
205
|
-
return
|
|
227
|
+
return null;
|
|
206
228
|
}
|
|
207
229
|
}
|
|
208
230
|
function listFiles(baseDir) {
|
|
@@ -231,6 +253,38 @@ function normalizeRelativePosix(filePath) {
|
|
|
231
253
|
function createPathMatcher(patterns) {
|
|
232
254
|
return ignore().add(patterns.map((pattern) => pattern.trim()).filter((pattern) => pattern !== '' && !pattern.startsWith('#')));
|
|
233
255
|
}
|
|
256
|
+
function createExcludeRules(excludeDirs, includeGit, includeDefaultExcludeDirs) {
|
|
257
|
+
const anyDepthNames = new Set();
|
|
258
|
+
const rootPrefixes = new Set();
|
|
259
|
+
if (includeDefaultExcludeDirs) {
|
|
260
|
+
addExcludeEntries(anyDepthNames, rootPrefixes, DEFAULT_ANY_DEPTH_EXCLUDES, 'any-depth');
|
|
261
|
+
addExcludeEntries(anyDepthNames, rootPrefixes, DEFAULT_ROOT_EXCLUDES, 'root-prefix');
|
|
262
|
+
}
|
|
263
|
+
else if (!includeGit) {
|
|
264
|
+
addExcludeEntries(anyDepthNames, rootPrefixes, ['.git'], 'any-depth');
|
|
265
|
+
}
|
|
266
|
+
if (includeGit) {
|
|
267
|
+
anyDepthNames.delete('.git');
|
|
268
|
+
}
|
|
269
|
+
// Preserve caller-supplied excludeDirs semantics: bare names match at any
|
|
270
|
+
// depth, while path-style entries are root-anchored prefixes.
|
|
271
|
+
addExcludeEntries(anyDepthNames, rootPrefixes, excludeDirs, 'legacy');
|
|
272
|
+
return { anyDepthNames, rootPrefixes };
|
|
273
|
+
}
|
|
274
|
+
function addExcludeEntries(anyDepthNames, rootPrefixes, entries, mode) {
|
|
275
|
+
for (const entry of entries) {
|
|
276
|
+
const normalized = normalizeRelativePosix(entry).replace(/^\/+|\/+$/g, '');
|
|
277
|
+
if (!normalized) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (mode === 'root-prefix' || (mode === 'legacy' && normalized.includes('/'))) {
|
|
281
|
+
rootPrefixes.add(normalized);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
anyDepthNames.add(normalized);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
234
288
|
function isPathMatched(relPath, matcher, isDirectory = false) {
|
|
235
289
|
const normalized = normalizeRelativePosix(relPath);
|
|
236
290
|
return matcher.ignores(normalized) || (isDirectory && matcher.ignores(`${normalized}/`));
|
|
@@ -254,8 +308,8 @@ function hasSameContent(left, right) {
|
|
|
254
308
|
return false;
|
|
255
309
|
}
|
|
256
310
|
}
|
|
257
|
-
function syncMountedFileBack(sourceFile, mountDir, projectDir, readonlyMatcher, ignoredMatcher, noSyncBackMatcher) {
|
|
258
|
-
const relative = resolveSyncRelativePath(sourceFile, mountDir, readonlyMatcher, ignoredMatcher, noSyncBackMatcher);
|
|
311
|
+
function syncMountedFileBack(sourceFile, mountDir, projectDir, readonlyMatcher, ignoredMatcher, noSyncBackMatcher, isExcluded) {
|
|
312
|
+
const relative = resolveSyncRelativePath(sourceFile, mountDir, readonlyMatcher, ignoredMatcher, noSyncBackMatcher, isExcluded);
|
|
259
313
|
if (!relative)
|
|
260
314
|
return 0;
|
|
261
315
|
const safeTargetPath = resolveVerifiedSyncTarget(projectDir, relative);
|
|
@@ -267,7 +321,7 @@ function syncMountedFileBack(sourceFile, mountDir, projectDir, readonlyMatcher,
|
|
|
267
321
|
copyFileSync(sourceFile, safeTargetPath);
|
|
268
322
|
return 1;
|
|
269
323
|
}
|
|
270
|
-
function resolveSyncRelativePath(sourceFile, mountDir, readonlyMatcher, ignoredMatcher, noSyncBackMatcher) {
|
|
324
|
+
function resolveSyncRelativePath(sourceFile, mountDir, readonlyMatcher, ignoredMatcher, noSyncBackMatcher, isExcluded) {
|
|
271
325
|
const relative = path.relative(mountDir, sourceFile);
|
|
272
326
|
if (relative === '' || relative.startsWith('..'))
|
|
273
327
|
return null;
|
|
@@ -276,7 +330,8 @@ function resolveSyncRelativePath(sourceFile, mountDir, readonlyMatcher, ignoredM
|
|
|
276
330
|
return null;
|
|
277
331
|
if (relativePosix === MOUNT_MARKER_FILENAME)
|
|
278
332
|
return null;
|
|
279
|
-
if (
|
|
333
|
+
if (isExcluded(relativePosix) ||
|
|
334
|
+
isPathMatched(relative, readonlyMatcher) ||
|
|
280
335
|
isPathMatched(relative, ignoredMatcher) ||
|
|
281
336
|
isPathMatched(relative, noSyncBackMatcher))
|
|
282
337
|
return null;
|
|
@@ -311,14 +366,14 @@ function resolveVerifiedSyncTarget(projectDir, relativePath) {
|
|
|
311
366
|
return null;
|
|
312
367
|
}
|
|
313
368
|
}
|
|
314
|
-
function isExcludedPath(relativePath,
|
|
369
|
+
function isExcludedPath(relativePath, excludeRules) {
|
|
315
370
|
const normalized = normalizeRelativePosix(relativePath).replace(/^\/+|\/+$/g, '');
|
|
316
371
|
if (!normalized)
|
|
317
372
|
return false;
|
|
318
373
|
const segments = normalized.split('/');
|
|
319
374
|
return segments.some((segment, index) => {
|
|
320
375
|
const prefix = segments.slice(0, index + 1).join('/');
|
|
321
|
-
return
|
|
376
|
+
return excludeRules.anyDepthNames.has(segment) || excludeRules.rootPrefixes.has(prefix);
|
|
322
377
|
});
|
|
323
378
|
}
|
|
324
379
|
function isPathWithinRoot(candidatePath, rootPath) {
|
|
@@ -331,19 +386,11 @@ function resolveSafeCopyTarget(rootPath, candidatePath) {
|
|
|
331
386
|
return null;
|
|
332
387
|
}
|
|
333
388
|
const parentPath = path.dirname(candidatePath);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
try {
|
|
338
|
-
const realParent = realpathSync(parentPath);
|
|
339
|
-
if (!isPathWithinRoot(realParent, rootPath)) {
|
|
340
|
-
return null;
|
|
341
|
-
}
|
|
342
|
-
return path.join(realParent, path.basename(candidatePath));
|
|
343
|
-
}
|
|
344
|
-
catch {
|
|
389
|
+
const realParent = ensureDirectoryWithinRoot(rootPath, parentPath);
|
|
390
|
+
if (!realParent) {
|
|
345
391
|
return null;
|
|
346
392
|
}
|
|
393
|
+
return path.join(realParent, path.basename(candidatePath));
|
|
347
394
|
}
|
|
348
395
|
function resolveVerifiedFilePath(rootPath, candidatePath) {
|
|
349
396
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@relayfile/local-mount",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.15",
|
|
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",
|