Skip to content
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

Adds a patchfile for fsevents #692

Merged
merged 17 commits into from
Jan 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ indent_size = 2
[*.md]
# trailing whitespace is significant in markdown -> do not remove
trim_trailing_whitespace = false

[*.patch]
trim_trailing_whitespace = false
insert_final_newline = false
160 changes: 160 additions & 0 deletions .github/workflows/e2e-fsevents-workflow.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
on:
schedule:
- cron: '0 */4 * * *'
push:
branches:
- master
pull_request:
paths:
- .github/workflows/e2e-fsevents-workflow.yml
- scripts/e2e-setup-ci.sh

name: 'E2E FSEvents'
jobs:
chore:
name: 'Validating FSEvents'
runs-on: macos-latest

steps:
- uses: actions/checkout@master

- name: 'Use Node.js 10.x'
uses: actions/setup-node@master
with:
node-version: 10.x

- name: 'Build the standard bundle'
run: |
node ./scripts/run-yarn.js build:cli

- name: 'Running the integration test (FSEvents ^1)'
run: |
source scripts/e2e-setup-ci.sh
yarn init
yarn add fsevents@^1

cat > test.js <<EOT
const assert = require('assert');
const fsevents = require('fsevents');
const fs = require('fs');
const path = require('path');

async function sleep(n) {
return new Promise(resolve => {
setTimeout(resolve, n);
});
}

async function runTestOn(dir, fileName) {
const file = path.join(dir, fileName);

const events = [];
const watcher = fsevents(dir);
watcher.on('change', (...args) => events.push(args));
watcher.start();

async function main() {
await sleep(1000);
fs.writeFileSync(file, '');
await sleep(1000);

if (events.length === 0)
throw new Error('No events recorded');

for (const [p] of events) {
if (!p.startsWith(dir)) {
throw new Error(p);
}
}
}

try {
return await main();
} finally {
watcher.stop();
}
}

async function registerTest(dir, fileName) {
try {
await runTestOn(dir, fileName);
console.log('Succeeded test for ' + dir);
} catch (error) {
console.log('Failed test for ' + dir + ': ' + error.message);
process.exitCode = 1;
}
}

Promise.resolve().then(() => {
return registerTest(process.cwd(), 'hello');
}).then(() => {
return registerTest(process.cwd() + '/\$\$virtual/foo/0', 'world');
});
EOT

yarn node ./test.js

- name: 'Running the integration test (FSEvents latest)'
run: |
source scripts/e2e-setup-ci.sh
yarn init
yarn add fsevents@latest

cat > test.js <<EOT
const assert = require('assert');
const fsevents = require('fsevents');
const fs = require('fs');
const path = require('path');

async function sleep(n) {
return new Promise(resolve => {
setTimeout(resolve, n);
});
}

async function runTestOn(dir, fileName) {
const file = path.join(dir, fileName);

const events = [];
const stop = fsevents.watch(dir, (...args) => events.push(args));

async function main() {
await sleep(1000);
fs.writeFileSync(file, '');
await sleep(1000);

if (events.length === 0)
throw new Error('No events recorded');

for (const [p] of events) {
if (!p.startsWith(dir)) {
throw new Error(p);
}
}
}

try {
return await main();
} finally {
await stop();
}
}

async function registerTest(dir, fileName) {
try {
await runTestOn(dir, fileName);
console.log('Succeeded test for ' + dir);
} catch (error) {
console.log('Failed test for ' + dir + ': ' + error.message);
process.exitCode = 1;
}
}

Promise.resolve().then(() => {
return registerTest(process.cwd(), 'hello');
}).then(() => {
return registerTest(process.cwd() + '/\$\$virtual/foo/0', 'world');
});
EOT

yarn node ./test.js
22 changes: 11 additions & 11 deletions .pnp.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added .yarn/cache/fsevents-patch-0e5d215591-1.zip
Binary file not shown.
Binary file added .yarn/cache/fsevents-patch-5e63278d20-1.zip
Binary file not shown.
20 changes: 20 additions & 0 deletions .yarn/versions/d732ee1c.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
releases:
"@yarnpkg/[email protected]": prerelease
"@yarnpkg/[email protected]": prerelease
"@yarnpkg/[email protected]": prerelease

declined:
- "@yarnpkg/[email protected]"
- "@yarnpkg/[email protected]"
- "@yarnpkg/[email protected]"
- "@yarnpkg/[email protected]"
- "@yarnpkg/[email protected]"
- "@yarnpkg/[email protected]"
- "@yarnpkg/[email protected]"
- "@yarnpkg/[email protected]"
- "@yarnpkg/[email protected]"
- "@yarnpkg/[email protected]"
- "@yarnpkg/[email protected]"
- "@yarnpkg/[email protected]"
- "@yarnpkg/[email protected]"
- "@yarnpkg/[email protected]"
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ On top of our classic integration tests, we also run Yarn every day against the
</td><td valign="top">

[![](https://github.com/yarnpkg/berry/workflows/E2E%20ESLint/badge.svg?event=schedule)](https://github.com/yarnpkg/berry/blob/master/.github/workflows/e2e-eslint-workflow.yml)<br/>
[![](https://github.com/yarnpkg/berry/workflows/E2E%20FSEvents/badge.svg?event=schedule)](https://github.com/yarnpkg/berry/blob/master/.github/workflows/e2e-fsevents-workflow.yml)<br/>
[![](https://github.com/yarnpkg/berry/workflows/E2E%20Husky/badge.svg?event=schedule)](https://github.com/yarnpkg/berry/blob/master/.github/workflows/e2e-husky-workflow.yml)<br/>
[![](https://github.com/yarnpkg/berry/workflows/E2E%20Jest/badge.svg?event=schedule)](https://github.com/yarnpkg/berry/blob/master/.github/workflows/e2e-jest-workflow.yml)<br/>
[![](https://github.com/yarnpkg/berry/workflows/E2E%20Mocha/badge.svg?event=schedule)](https://github.com/yarnpkg/berry/blob/master/.github/workflows/e2e-mocha-workflow.yml)<br/>
Expand Down
21 changes: 21 additions & 0 deletions packages/plugin-compat/extra/fsevents/1.2.11.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 ^1
--- a/fsevents.js
+++ b/fsevents.js
@@ -36,11 +36,15 @@ module.exports.Constants = Native.Constants;
var defer = global.setImmediate || process.nextTick;

function watch(path) {
- var fse = new FSEvents(String(path || ''), handler);
+ var VFS = require('./vfs');
+ var vfs = new VFS(String(path || ''));
+
+ var fse = new FSEvents(vfs.resolvedPath, handler);
EventEmitter.call(fse);
return fse;

function handler(path, flags, id) {
+ path = vfs.transpose(path);
defer(function() {
fse.emit('fsevent', path, flags, id);
var info = getInfo(path, flags);
13 changes: 13 additions & 0 deletions packages/plugin-compat/extra/fsevents/2.1.2.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/fsevents.js b/fsevents.js
semver exclusivity ^2
--- a/fsevents.js
+++ b/fsevents.js
@@ -21,5 +21,7 @@ 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);
+ 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}`);
46 changes: 46 additions & 0 deletions packages/plugin-compat/extra/fsevents/common.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
diff --git a/vfs.js b/vfs.js
new file mode 100644
--- /dev/null
+++ b/vfs.js
@@ -0,0 +1,41 @@
+const path = require(`path`);
+
+const NUMBER_REGEXP = /^[0-9]+$/;
+const VIRTUAL_REGEXP = /^(\/(?:[^\/]+\/)*?\$\$virtual)((?:\/([^\/]+)(?:\/([^\/]+))?)?((?:\/.*)?))$/;
+
+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 isnum = NUMBER_REGEXP.test(match[4]);
+ if (!isnum)
+ return p;
+
+ const depth = Number(match[4]);
+ const backstep = `../`.repeat(depth);
+ const subpath = (match[5] || `.`);
+
+ return resolveVirtual(path.join(target, backstep, subpath));
+}
+
+module.exports = class FsePnp {
+ constructor(p) {
+ this.normalizedPath = path.resolve(p);
+ this.resolvedPath = resolveVirtual(this.normalizedPath);
+ }
+
+ transpose(p) {
+ return this.normalizedPath + p.substr(this.resolvedPath.length);
+ }
+
+ wrap(fn) {
+ return (path, ...args) => {
+ return fn(this.transpose(path), ...args);
+ };
+ }
+};
20 changes: 20 additions & 0 deletions packages/plugin-compat/extra/fsevents/gen-fsevents-patch.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
set -ex

THIS_DIR=$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)
TEMP_DIR="$(mktemp -d)"

echo $TEMP_DIR

PATCHFILE="$THIS_DIR"/../../sources/patches/fsevents.patch.ts
rm -f "$PATCHFILE" && touch "$PATCHFILE"

echo 'export const patch =' \
>> "$PATCHFILE"
(cat "$THIS_DIR"/1.2.11.patch \
"$THIS_DIR"/2.1.2.patch \
"$THIS_DIR"/common.patch) \
> "$TEMP_DIR"/patch.tmp || true
node "$THIS_DIR"/../jsonEscape.js < "$TEMP_DIR"/patch.tmp \
>> "$PATCHFILE"
echo ';' \
>> "$PATCHFILE"
2 changes: 2 additions & 0 deletions packages/plugin-compat/sources/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {Hooks as CoreHooks, Plugin, structUtils} from '@yarnpkg/core';
import {Hooks as PatchHooks} from '@yarnpkg/plugin-patch';

import {patch as fseventsPatch} from './patches/fsevents.patch';
import {patch as resolvePatch} from './patches/resolve.patch';
import {patch as typescriptPatch} from './patches/typescript.patch';

const PATCHES = new Map([
[structUtils.makeIdent(null, `fsevents`).identHash, fseventsPatch],
[structUtils.makeIdent(null, `resolve`).identHash, resolvePatch],
[structUtils.makeIdent(null, `typescript`).identHash, typescriptPatch],
]);
Expand Down
3 changes: 3 additions & 0 deletions packages/plugin-compat/sources/patches/fsevents.patch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const patch =
"diff --git a/fsevents.js b/fsevents.js\nsemver exclusivity ^1\n--- a/fsevents.js\n+++ b/fsevents.js\n@@ -36,11 +36,15 @@ module.exports.Constants = Native.Constants;\n var defer = global.setImmediate || process.nextTick;\n\n function watch(path) {\n- var fse = new FSEvents(String(path || ''), handler);\n+ var VFS = require('./vfs');\n+ var vfs = new VFS(String(path || ''));\n+\n+ var fse = new FSEvents(vfs.resolvedPath, handler);\n EventEmitter.call(fse);\n return fse;\n\n function handler(path, flags, id) {\n+ path = vfs.transpose(path);\n defer(function() {\n fse.emit('fsevent', path, flags, id);\n var info = getInfo(path, flags);\ndiff --git a/fsevents.js b/fsevents.js\nsemver exclusivity ^2\n--- a/fsevents.js\n+++ b/fsevents.js\n@@ -21,5 +21,7 @@ function watch(path, handler) {\n throw new TypeError(`fsevents argument 2 must be a function and not a ${typeof handler}`);\n }\n\n- let instance = Native.start(path, handler);\n+ let VFS = require('./vfs');\n+ let vfs = new VFS(path);\n+ let instance = Native.start(vfs.resolvedPath, vfs.wrap(handler));\n if (!instance) throw new Error(`could not watch: ${path}`);\ndiff --git a/vfs.js b/vfs.js\nnew file mode 100644\n--- /dev/null\n+++ b/vfs.js\n@@ -0,0 +1,41 @@\n+const path = require(`path`);\n+\n+const NUMBER_REGEXP = /^[0-9]+$/;\n+const VIRTUAL_REGEXP = /^(\\/(?:[^\\/]+\\/)*?\\$\\$virtual)((?:\\/([^\\/]+)(?:\\/([^\\/]+))?)?((?:\\/.*)?))$/;\n+\n+function resolveVirtual(p) {\n+ const match = p.match(VIRTUAL_REGEXP);\n+ if (!match)\n+ return p;\n+\n+ const target = path.dirname(match[1]);\n+ if (!match[3] || !match[4])\n+ return target;\n+\n+ const isnum = NUMBER_REGEXP.test(match[4]);\n+ if (!isnum)\n+ return p;\n+\n+ const depth = Number(match[4]);\n+ const backstep = `../`.repeat(depth);\n+ const subpath = (match[5] || `.`);\n+\n+ return resolveVirtual(path.join(target, backstep, subpath));\n+}\n+\n+module.exports = class FsePnp {\n+ constructor(p) {\n+ this.normalizedPath = path.resolve(p);\n+ this.resolvedPath = resolveVirtual(this.normalizedPath);\n+ }\n+\n+ transpose(p) {\n+ return this.normalizedPath + p.substr(this.resolvedPath.length);\n+ }\n+\n+ wrap(fn) {\n+ return (path, ...args) => {\n+ return fn(this.transpose(path), ...args);\n+ };\n+ }\n+};\n"
;
Loading