-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(plugin-compat): update fsevents patch to support virtual path #1671
Changes from 5 commits
48e0dc0
8eb79ad
b1bb8b4
ce7f7a2
202140c
f653dbc
4e27756
99024a7
411d392
e7c7c9c
d471246
bf77d82
7e18b89
4de6b1c
781ff65
fc0dcf5
938ec0c
e95ef87
ee2b36a
3816be3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
releases: | ||
"@yarnpkg/plugin-compat": patch |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
diff --git a/fsevents.js b/fsevents.js | ||
semver exclusivity 2.1.1 | ||
--- a/fsevents.js | ||
+++ b/fsevents.js | ||
@@ -21,12 +21,11 @@ function watch(path, handler) { | ||
throw new TypeError(`fsevents argument 2 must be a function and not a ${typeof handler}`); | ||
} | ||
|
||
- let instance = Native.start(path, handler); | ||
- if (!instance) throw new Error(`could not watch: ${path}`); | ||
+ let VFS = require('./vfs'); | ||
+ let vfs = new VFS(path, Native); | ||
+ vfs.watch(handler); | ||
return () => { | ||
- const result = instance ? Promise.resolve(instance).then(Native.stop) : null; | ||
- instance = null; | ||
- return result; | ||
+ return vfs.stop(); | ||
}; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,22 @@ | ||
diff --git a/fsevents.js b/fsevents.js | ||
semver exclusivity ^2.1 | ||
semver exclusivity ^2.1.2 | ||
--- a/fsevents.js | ||
+++ b/fsevents.js | ||
@@ -21,5 +21,7 @@ function watch(path, handler) { | ||
@@ -21,13 +21,10 @@ function watch(path, handler) { | ||
throw new TypeError(`fsevents argument 2 must be a function and not a ${typeof handler}`); | ||
} | ||
|
||
- let instance = Native.start(path, handler); | ||
- if (!instance) throw new Error(`could not watch: ${path}`); | ||
+ let VFS = require('./vfs'); | ||
+ let vfs = new VFS(path); | ||
+ let instance = Native.start(vfs.resolvedPath, vfs.wrap(handler)); | ||
if (!instance) throw new Error(`could not watch: ${path}`); | ||
+ let vfs = new VFS(path, Native); | ||
+ vfs.watch(handler); | ||
return () => { | ||
- const result = instance | ||
- ? Promise.resolve(instance).then(Native.stop) | ||
- : Promise.resolve(undefined); | ||
- instance = undefined; | ||
- return result; | ||
+ return vfs.stop(); | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,45 +2,108 @@ diff --git a/vfs.js b/vfs.js | |
new file mode 100644 | ||
--- /dev/null | ||
+++ b/vfs.js | ||
@@ -0,0 +1,41 @@ | ||
@@ -0,0 +1,104 @@ | ||
+const path = require(`path`); | ||
+ | ||
+const NUMBER_REGEXP = /^[0-9]+$/; | ||
+const VIRTUAL_REGEXP = /^(\/(?:[^\/]+\/)*?\$\$virtual)((?:\/([^\/]+)(?:\/([^\/]+))?)?((?:\/.*)?))$/; | ||
+function getPnpApi() { | ||
+ let pnpApi; | ||
+ try { | ||
+ if (process.versions.pnp) { | ||
+ pnpApi = require(`pnpapi`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @arcanis I am assuming calling |
||
+ } | ||
+ } catch (ex) {} | ||
+ return pnpApi; | ||
+} | ||
+ | ||
+function resolveVirtual(p) { | ||
+ const match = p.match(VIRTUAL_REGEXP); | ||
+ if (!match) | ||
+ return p; | ||
+ | ||
+ const target = path.dirname(match[1]); | ||
+ if (!match[3] || !match[4]) | ||
+ return target; | ||
+ const pnpapi = getPnpApi(); | ||
+ if (pnpapi) { | ||
+ const resolved = pnpapi.resolveVirtual(p); | ||
+ // pnp resolveVirtual will return null for non-virtual path. | ||
+ return resolved == null ? p : resolved; | ||
+ } | ||
+ return p; | ||
+} | ||
+ | ||
+ const isnum = NUMBER_REGEXP.test(match[4]); | ||
+ if (!isnum) | ||
+ return p; | ||
+function findVirtualPaths(root) { | ||
+ const paths = []; | ||
+ const pnpapi = getPnpApi(); | ||
+ if (pnpapi) { | ||
+ for (const locator of pnpapi.getDependencyTreeRoots()) { | ||
+ const pkg = pnpapi.getPackageInformation(locator); | ||
+ for (const [name, referencish] of pkg.packageDependencies) { | ||
+ if (referencish === null) continue; | ||
+ if (referencish.indexOf(`virtual:`) === 0) { | ||
+ const virtualLocator = pnpapi.getLocator(name, referencish); | ||
+ const virtualPkg = pnpapi.getPackageInformation(virtualLocator); | ||
+ if (virtualPkg && virtualPkg.packageLocation.indexOf(root) === 0) { | ||
+ // virtual path fall under root | ||
+ paths.push(virtualPkg.packageLocation); | ||
+ } | ||
+ } | ||
+ } | ||
+ } | ||
+ } | ||
+ return paths; | ||
+} | ||
+ | ||
+ const depth = Number(match[4]); | ||
+ const backstep = `../`.repeat(depth); | ||
+ const subpath = (match[5] || `.`); | ||
+class VFS { | ||
+ constructor(p, Native) { | ||
+ this.root = path.resolve(p); | ||
+ this.native = Native; | ||
+ this.watchers = []; | ||
+ } | ||
+ | ||
+ return resolveVirtual(path.join(target, backstep, subpath)); | ||
+} | ||
+ transpose(rawPath, resolvedPath, p) { | ||
+ const transposePath = rawPath + p.substr(resolvedPath.length); | ||
+ return transposePath; | ||
+ } | ||
+ | ||
+module.exports = class FsePnp { | ||
+ constructor(p) { | ||
+ this.normalizedPath = path.resolve(p); | ||
+ this.resolvedPath = resolveVirtual(this.normalizedPath); | ||
+ /** | ||
+ * build raw and resolved path mapping | ||
+ * @param {*} root | ||
+ */ | ||
+ buildPathMap() { | ||
+ const pathMap = new Map(); | ||
+ this.resolvedRoot = resolveVirtual(this.root); | ||
+ pathMap.set(this.resolvedRoot, this.root); | ||
+ if (!path.extname(this.root)) { | ||
+ // find all direct virtual paths for given root. | ||
+ const virtualPaths = findVirtualPaths(this.root); | ||
+ virtualPaths.forEach((virtualPath) => { | ||
+ const resolvedVirtual = resolveVirtual(virtualPath); | ||
+ if (resolvedVirtual.indexOf(this.resolvedRoot) < 0) { | ||
+ pathMap.set(resolvedVirtual, virtualPath); | ||
+ } | ||
+ }); | ||
+ } | ||
+ return pathMap; | ||
+ } | ||
+ | ||
+ transpose(p) { | ||
+ return this.normalizedPath + p.substr(this.resolvedPath.length); | ||
+ watch(handler) { | ||
+ const pathMap = this.buildPathMap(); | ||
+ pathMap.forEach((virtualPath, resolvedPath) => { | ||
+ const watcher = this.native.start(resolvedPath, (p, ...args) => { | ||
+ return handler(this.transpose(virtualPath, resolvedPath, p), ...args); | ||
+ }); | ||
arcanis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
+ if (!watcher) throw new Error(`could not watch: ${resolvedPath}`); | ||
+ this.watchers.push(watcher); | ||
+ }); | ||
+ return this.watchers; | ||
+ } | ||
+ | ||
+ wrap(fn) { | ||
+ return (path, ...args) => { | ||
+ return fn(this.transpose(path), ...args); | ||
+ }; | ||
+ stop() { | ||
+ const results = this.watchers.map((watcher) => { | ||
+ const p = Promise.resolve(watcher); | ||
+ if (watcher) { | ||
+ p.then(this.native.stop); | ||
+ } | ||
+ return p; | ||
+ }); | ||
+ this.watchers = []; | ||
+ this.resolvedRoot = undefined; | ||
+ return results[0]; | ||
+ } | ||
+}; | ||
+} | ||
+ | ||
+module.exports = VFS; |
Large diffs are not rendered by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the API fsevents 1.x exposed makes it quite challenging to watch multiple files so I only watch the path file instead.( same behavior as current patch)