Skip to content

Commit

Permalink
Adds a patchfile for fsevents (yarnpkg#692)
Browse files Browse the repository at this point in the history
* Adds a patchfile for fsevents

* Fix & debug

* Removes old-style from the tests

* Increases the timeout on CI?

* Refactors the test

* Removes snapshot

* Escapes heredoc

* Adds the release file

* No backticks allowed in Bash

* Checks whether the problem comes from the patch or native

* Skips fsevents@1 and goes to latest

* Calls the watch fn

* Fixes the 2.x patch

* Adds the CI tests to the README

* Updates FSEvents

* Fixes the patch

* Fixes the checked-in artifacts
  • Loading branch information
arcanis authored Jan 15, 2020
1 parent 4499649 commit d955efa
Show file tree
Hide file tree
Showing 17 changed files with 328 additions and 208 deletions.
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

0 comments on commit d955efa

Please sign in to comment.