-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add node.js only parcel watcher watchman back-end (#9789)
- Loading branch information
Showing
12 changed files
with
320 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,38 @@ | ||
// @flow | ||
|
||
// Derived from TypeScript typings and source code from | ||
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/fb-watchman/index.d.ts | ||
|
||
declare module 'fb-watchman' { | ||
// Emit the responses to these when they get sent down to us | ||
declare type UnilateralTags = "unilateralTags" | "log"; | ||
|
||
declare interface ClientOptions { | ||
/** | ||
* Absolute path to the watchman binary. | ||
* If not provided, the Client locates the binary using the PATH specified | ||
* by the node child_process's default env. | ||
*/ | ||
watchmanBinaryPath?: string | void; | ||
} | ||
|
||
declare interface Capabilities { | ||
optional: any[]; | ||
required: any[]; | ||
} | ||
|
||
declare type doneCallback = (error?: Error | null, resp?: any) => any; | ||
|
||
declare class Client extends events$EventEmitter { | ||
constructor(options?: ClientOptions): this; | ||
sendNextCommand(): void; | ||
cancelCommands(why: string): void; | ||
connect(): void; | ||
command(args: any, done: doneCallback): void; | ||
capabilityCheck( | ||
caps: Capabilities, | ||
done: doneCallback, | ||
): void; | ||
end(): void; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,7 @@ | ||
# @parcel/watcher-watchman-js | ||
|
||
This package acts as a drop-in replacements for `@parcel/watcher` but only for | ||
watchman. It's a temporary workaround for some bugs and feature gaps with the | ||
C watchman client. | ||
|
||
It is intended to replace `@parcel/watcher` within node_modules. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,13 @@ | ||
{ | ||
"name": "@parcel/watcher-watchman-js", | ||
"version": "2.12.0", | ||
"main": "lib/index.js", | ||
"source": "src/index.js", | ||
"dependencies": { | ||
"@parcel/utils": "2.12.0", | ||
"fb-watchman": "^2.0.2" | ||
}, | ||
"devDependencies": { | ||
"@parcel/watcher": "^2.4.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,8 @@ | ||
import {ParcelWatcherWatchmanJS} from './wrapper'; | ||
|
||
const wrapper = new ParcelWatcherWatchmanJS(); | ||
|
||
export const writeSnapshot = wrapper.writeSnapshot.bind(wrapper); | ||
export const getEventsSince = wrapper.getEventsSince.bind(wrapper); | ||
export const subscribe = wrapper.subscribe.bind(wrapper); | ||
export const unsubscribe = wrapper.unsubscribe.bind(wrapper); |
177 changes: 177 additions & 0 deletions
177
packages/utils/parcel-watcher-watchman-js/src/wrapper.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,177 @@ | ||
// @flow | ||
import * as fs from 'fs'; | ||
import * as path from 'path'; | ||
import * as watchman from 'fb-watchman'; | ||
import {isGlob} from '@parcel/utils'; | ||
import type { | ||
Options, | ||
Event, | ||
SubscribeCallback, | ||
AsyncSubscription, | ||
} from '@parcel/watcher'; | ||
|
||
type WatchmanArgs = any; | ||
type FilePath = string; | ||
type GlobPattern = string; | ||
|
||
// Matches the Watcher API from "@parcel/watcher" | ||
export interface Watcher { | ||
getEventsSince( | ||
dir: FilePath, | ||
snapshot: FilePath, | ||
opts?: Options, | ||
): Promise<Array<Event>>; | ||
subscribe( | ||
dir: FilePath, | ||
fn: SubscribeCallback, | ||
opts?: Options, | ||
): Promise<AsyncSubscription>; | ||
unsubscribe( | ||
dir: FilePath, | ||
fn: SubscribeCallback, | ||
opts?: Options, | ||
): Promise<void>; | ||
writeSnapshot( | ||
dir: FilePath, | ||
snapshot: FilePath, | ||
opts?: Options, | ||
): Promise<FilePath>; | ||
} | ||
|
||
export class ParcelWatcherWatchmanJS implements Watcher { | ||
subscriptionName: string; | ||
client: watchman.Client; | ||
|
||
constructor() { | ||
this.subscriptionName = 'parcel-watcher-subscription-' Date.now(); | ||
this.client = new watchman.Client(); | ||
} | ||
|
||
commandAsync(args: any[]): Promise<any> { | ||
return new Promise((resolve, reject) => { | ||
const client = this.client; | ||
// $FlowFixMe | ||
client.command(args, (err: Error | null | undefined, response: any) => { | ||
if (err) reject(err); | ||
else resolve(response); | ||
}); | ||
}); | ||
} | ||
|
||
// Types should match @parcel/watcher/index.js.flow | ||
async writeSnapshot(dir: string, snapshot: FilePath): Promise<string> { | ||
const response = await this.commandAsync(['clock', dir]); | ||
fs.writeFileSync(snapshot, response.clock, { | ||
encoding: 'utf-8', | ||
}); | ||
return response.clock; | ||
} | ||
|
||
async getEventsSince( | ||
dir: string, | ||
snapshot: FilePath, | ||
opts?: Options, | ||
): Promise<Event[]> { | ||
const clock = fs.readFileSync(snapshot, { | ||
encoding: 'utf-8', | ||
}); | ||
|
||
const response = await this.commandAsync([ | ||
'query', | ||
dir, | ||
{ | ||
expression: this._createExpression(dir, opts?.ignore), | ||
fields: ['name', 'mode', 'exists', 'new'], | ||
since: clock, | ||
}, | ||
]); | ||
|
||
return (response.files || []).map((file: any) => ({ | ||
path: file.name, | ||
type: file.new ? 'create' : file.exists ? 'update' : 'delete', | ||
})); | ||
} | ||
|
||
_createExpression( | ||
dir: string, | ||
ignore?: Array<FilePath | GlobPattern>, | ||
): WatchmanArgs { | ||
const ignores = [ | ||
// Ignore the watchman cookie | ||
['match', '.watchman-cookie-*'], | ||
// Ignore directory changes as they are just noise | ||
['type', 'd'], | ||
]; | ||
|
||
if (ignore) { | ||
const customIgnores = ignore?.map(filePathOrGlob => { | ||
const relative = path.relative(dir, filePathOrGlob); | ||
|
||
if (isGlob(filePathOrGlob)) { | ||
return ['match', relative, 'wholename']; | ||
} | ||
|
||
// If pattern is not a glob, then assume it's a directory. | ||
// Ignoring single files is not currently supported | ||
return ['dirname', relative]; | ||
}); | ||
|
||
ignores.push(...customIgnores); | ||
} | ||
|
||
return ['not', ['anyof', ...ignores]]; | ||
} | ||
|
||
async subscribe( | ||
dir: string, | ||
fn: SubscribeCallback, | ||
opts?: Options, | ||
): Promise<AsyncSubscription> { | ||
const {subscriptionName} = this; | ||
const {clock} = await this.commandAsync(['clock', dir]); | ||
|
||
await this.commandAsync([ | ||
'subscribe', | ||
dir, | ||
subscriptionName, | ||
{ | ||
// `defer` can be used here if you want to pause the | ||
// notification stream until something has finished. | ||
// | ||
// https://facebook.github.io/watchman/docs/cmd/subscribe#defer | ||
// defer: ['my-company-example'], | ||
expression: this._createExpression(dir, opts?.ignore), | ||
fields: ['name', 'mode', 'exists', 'new'], | ||
since: clock, | ||
}, | ||
]); | ||
|
||
this.client.on('subscription', function (resp) { | ||
if (!resp.files || resp.subscription !== subscriptionName) { | ||
return; | ||
} | ||
|
||
fn( | ||
null /* err */, | ||
resp.files.map((file: any) => { | ||
return { | ||
path: path.join(dir, file.name), | ||
type: file.new ? 'create' : file.exists ? 'update' : 'delete', | ||
}; | ||
}), | ||
); | ||
}); | ||
|
||
const unsubscribe = async () => { | ||
await this.commandAsync(['unsubscribe', dir, subscriptionName]); | ||
}; | ||
|
||
return { | ||
unsubscribe, | ||
}; | ||
} | ||
|
||
async unsubscribe(dir: string): Promise<void> { | ||
await this.commandAsync(['unsubscribe', dir, this.subscriptionName]); | ||
} | ||
} |
Oops, something went wrong.