mirror of
https://github.com/wassname/talk.git
synced 2026-06-30 04:04:04 +08:00
Merge branch 'next' into next-passport
This commit is contained in:
Vendored
+2
@@ -11,6 +11,8 @@
|
||||
"cwd": "${workspaceFolder}",
|
||||
"program": "${workspaceFolder}/node_modules/.bin/ts-node",
|
||||
"args": [
|
||||
"--project",
|
||||
"${workspaceFolder}/src/tsconfig.json",
|
||||
"-r",
|
||||
"tsconfig-paths/register",
|
||||
"${workspaceFolder}/src/index.ts"
|
||||
|
||||
Vendored
+3
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"files.associations": {
|
||||
"*.css": "postcss"
|
||||
},
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import path from "path";
|
||||
import {
|
||||
CommandExecutor,
|
||||
Config,
|
||||
LongRunningExecutor,
|
||||
} from "../scripts/watcher";
|
||||
|
||||
const config: Config = {
|
||||
rootDir: path.resolve(__dirname, "../src"),
|
||||
watchers: {
|
||||
compileRelayStream: {
|
||||
paths: [
|
||||
"core/client/stream/**/*.ts",
|
||||
"core/client/stream/**/*.tsx",
|
||||
"core/client/stream/**/*.graphql",
|
||||
"core/client/server/**/*.graphql",
|
||||
],
|
||||
ignore: ["core/**/*.d.ts"],
|
||||
executor: new CommandExecutor("npm run compile:relay-stream", {
|
||||
runOnInit: true,
|
||||
}),
|
||||
},
|
||||
compileCSSTypes: {
|
||||
paths: ["**/*.css"],
|
||||
executor: new CommandExecutor("npm run compile:css-types", {
|
||||
runOnInit: true,
|
||||
}),
|
||||
},
|
||||
runServer: {
|
||||
paths: ["core/**/*.ts", "core/locales/**/*.ftl"],
|
||||
ignore: ["core/client/**/*"],
|
||||
executor: new LongRunningExecutor("npm run start:development"),
|
||||
},
|
||||
runWebpackDevServer: {
|
||||
paths: [],
|
||||
executor: new LongRunningExecutor("npm run start:webpackDevServer"),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
Generated
+97
-15
@@ -1437,12 +1437,31 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/chokidar": {
|
||||
"version": "1.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/chokidar/-/chokidar-1.7.5.tgz",
|
||||
"integrity": "sha512-PDkSRY7KltW3M60hSBlerxI8SFPXsO3AL/aRVsO4Kh9IHRW74Ih75gUuTd/aE4LSSFqypb10UIX3QzOJwBQMGQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/events": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/classnames": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.4.tgz",
|
||||
"integrity": "sha512-UWUmNYhaIGDx8Kv0NSqFRwP6HWnBMXam4nBacOrjIiPBKKCdWMCe77+Nbn6rI9+Us9c+BhE26u84xeYQv2bKeA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/commander": {
|
||||
"version": "2.12.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/commander/-/commander-2.12.2.tgz",
|
||||
"integrity": "sha512-0QEFiR8ljcHp9bAbWxecjVRuAMr16ivPiGOw6KFQBVrVd0RQIcM3xKdRisH2EDWgVWujiYtHwhSkSUoAAGzH7Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "*"
|
||||
}
|
||||
},
|
||||
"@types/connect": {
|
||||
"version": "3.4.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz",
|
||||
@@ -1457,6 +1476,15 @@
|
||||
"integrity": "sha512-p+gNRe4RPjpl1lTBUomFJ42P8ymArH/P93DFJ0iY873BJ4ZmogcKc6TbHgZQmtQMsy3jxcAo0HcTjidXwo8uKg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/cross-spawn": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.0.tgz",
|
||||
"integrity": "sha512-evp2ZGsFw9YKprDbg8ySgC9NA15g3YgiI8ANkGmKKvvi0P2aDGYLPxQIC5qfeKNUOe3TjABVGuah6omPRpIYhg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/dotenv": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-4.0.3.tgz",
|
||||
@@ -1531,9 +1559,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/joi": {
|
||||
"version": "13.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/joi/-/joi-13.0.8.tgz",
|
||||
"integrity": "sha512-GXYdIVpwBP5ZBOlHitSYfQdH+vWXVahhkeQwalX0LkoX7Mx0D3L3tg4vXXhr6nYHkEpWlAzWuEjgWEBtcp5NZA==",
|
||||
"version": "13.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/joi/-/joi-13.3.0.tgz",
|
||||
"integrity": "sha512-nOnsbHvoo5DsQEh8VGlbQlfg9+/iFSxE5RQKLNkAODIqyupdEkBCZf6RCNxR+9X0egMIkJ43NnwkEJKxLogsIA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/jsonwebtoken": {
|
||||
@@ -4988,23 +5016,24 @@
|
||||
"dev": true
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz",
|
||||
"integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==",
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
|
||||
"integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"anymatch": "^2.0.0",
|
||||
"async-each": "^1.0.0",
|
||||
"braces": "^2.3.0",
|
||||
"fsevents": "^1.1.2",
|
||||
"fsevents": "^1.2.2",
|
||||
"glob-parent": "^3.1.0",
|
||||
"inherits": "^2.0.1",
|
||||
"is-binary-path": "^1.0.0",
|
||||
"is-glob": "^4.0.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"normalize-path": "^2.1.1",
|
||||
"path-is-absolute": "^1.0.0",
|
||||
"readdirp": "^2.0.0",
|
||||
"upath": "^1.0.0"
|
||||
"upath": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
@@ -5156,6 +5185,19 @@
|
||||
"p-finally": "^1.0.0",
|
||||
"signal-exit": "^3.0.0",
|
||||
"strip-eof": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"cross-spawn": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
|
||||
"integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^4.0.1",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5349,9 +5391,9 @@
|
||||
}
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
|
||||
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
|
||||
"version": "2.16.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz",
|
||||
"integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==",
|
||||
"dev": true
|
||||
},
|
||||
"commondir": {
|
||||
@@ -5840,12 +5882,14 @@
|
||||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
|
||||
"integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
||||
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^4.0.1",
|
||||
"nice-try": "^1.0.4",
|
||||
"path-key": "^2.0.1",
|
||||
"semver": "^5.5.0",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
}
|
||||
@@ -6973,6 +7017,17 @@
|
||||
"strip-ansi": "^3.0.0",
|
||||
"supports-color": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
|
||||
"integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^4.0.1",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7510,6 +7565,19 @@
|
||||
"p-finally": "^1.0.0",
|
||||
"signal-exit": "^3.0.0",
|
||||
"strip-eof": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"cross-spawn": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
|
||||
"integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^4.0.1",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"exit": {
|
||||
@@ -9586,6 +9654,14 @@
|
||||
"param-case": "2.1.x",
|
||||
"relateurl": "0.2.x",
|
||||
"uglify-js": "3.4.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
|
||||
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"html-webpack-plugin": {
|
||||
@@ -19704,6 +19780,12 @@
|
||||
"source-map": "~0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
|
||||
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
|
||||
+17
-16
@@ -5,26 +5,21 @@
|
||||
"scripts": {
|
||||
"start": "node dist/index.js",
|
||||
"test": "node scripts/test.js --env=jsdom",
|
||||
"build": "npm-run-all --parallel compile:* && npm-run-all --parallel build:*",
|
||||
"build": "npm-run-all --parallel compile:* --parallel build:*",
|
||||
"build:client": "node ./scripts/build.js",
|
||||
"build:server": "tsc",
|
||||
"watch": "NODE_ENV=development npm-run-all compile:* --parallel watch:*",
|
||||
"watch:client": "node ./scripts/start.js",
|
||||
"watch:css-types": "tcm src/core/client/ --watch",
|
||||
"watch:relay-stream": "nodemon --config ./config/nodemon/relay-stream.json",
|
||||
"watch:server": "nodemon --config ./config/nodemon/server.json",
|
||||
"watch:types": "nodemon --config ./config/nodemon/types.json",
|
||||
"build:server": "tsc -p ./src/tsconfig.json",
|
||||
"watch": "NODE_ENV=development ts-node ./scripts/watcher/bin/watcher.ts ./config/watcher.ts",
|
||||
"compile:css-types": "tcm src/core/client/",
|
||||
"compile:relay-stream": "relay-compiler --src ./src/core/client/stream --schema ./src/core/server/graph/tenant/schema/schema.graphql --language typescript --artifactDirectory ./src/core/client/stream/__generated__ --no-watchman",
|
||||
"compile:server:types": "node ./scripts/types.js",
|
||||
"start:development": "NODE_ENV=development ts-node -r tsconfig-paths/register src/index.ts",
|
||||
"watch:types": "nodemon --config ./config/nodemon/types.json",
|
||||
"compile:relay-stream": "relay-compiler --src ./src/core/client/stream --schema $(ts-node ./scripts/schemaPath.ts tenant) --language typescript --artifactDirectory ./src/core/client/stream/__generated__ --no-watchman",
|
||||
"start:development": "NODE_ENV=development ts-node --project ./src/tsconfig.json -r tsconfig-paths/register ./src/index.ts",
|
||||
"start:webpackDevServer": "node ./scripts/start.js",
|
||||
"lint-fix": "npm run lint:server -- --fix && npm run lint:client -- --fix && npm run lint:scripts -- --fix",
|
||||
"lint": "npm-run-all --parallel lint:*",
|
||||
"lint:server": "tslint --project ./tsconfig.json",
|
||||
"lint:server": "tslint --project ./src/tsconfig.json",
|
||||
"lint:client": "tslint --project ./src/core/client/tsconfig.json",
|
||||
"docz:watch": "docz dev",
|
||||
"postinstall": "node ./scripts/types.js",
|
||||
"lint:scripts": "tslint ./config/**/*.js ./scripts/**/*.js ./doczrc.js ./src/**/.*.js"
|
||||
"lint:scripts": "tslint --project ./tsconfig.json",
|
||||
"docz:watch": "docz dev"
|
||||
},
|
||||
"author": "",
|
||||
"license": "Apache-2.0",
|
||||
@@ -64,8 +59,11 @@
|
||||
"@babel/preset-env": "7.0.0-beta.49",
|
||||
"@babel/preset-react": "7.0.0-beta.49",
|
||||
"@types/bunyan": "^1.8.4",
|
||||
"@types/chokidar": "^1.7.5",
|
||||
"@types/classnames": "^2.2.4",
|
||||
"@types/commander": "^2.12.2",
|
||||
"@types/convict": "^4.2.0",
|
||||
"@types/cross-spawn": "^6.0.0",
|
||||
"@types/dotenv": "^4.0.3",
|
||||
"@types/express": "^4.16.0",
|
||||
"@types/graphql": "^0.13.1",
|
||||
@@ -93,8 +91,11 @@
|
||||
"babel-preset-react-optimize": "^1.0.1",
|
||||
"case-sensitive-paths-webpack-plugin": "^2.1.2",
|
||||
"chalk": "^2.4.1",
|
||||
"chokidar": "^2.0.4",
|
||||
"classnames": "^2.2.5",
|
||||
"commander": "^2.16.0",
|
||||
"copy-webpack-plugin": "^4.5.1",
|
||||
"cross-spawn": "^6.0.5",
|
||||
"css-loader": "^0.28.11",
|
||||
"docz": "^0.2.6",
|
||||
"docz-theme-default": "^0.2.10",
|
||||
@@ -149,4 +150,4 @@
|
||||
"webpack-hot-client": "^4.0.3",
|
||||
"webpack-manifest-plugin": "^2.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env ts-node
|
||||
|
||||
import program from "commander";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
const config = JSON.parse(
|
||||
fs.readFileSync(path.resolve(__dirname, "../.graphqlconfig"), "utf8")
|
||||
);
|
||||
|
||||
program
|
||||
.version("0.1.0")
|
||||
.usage("<project>")
|
||||
.arguments("<project>")
|
||||
.description(
|
||||
"Returns the schema graph in `.graphqlconfig` based on <project>"
|
||||
)
|
||||
.action(project => {
|
||||
if (!config.projects) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error("Missing projects key in .graphqconfig");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!config.projects[project]) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(`Project ${project} not found in .graphqconfig`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (!config.projects[project].schemaPath) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(
|
||||
`SchemaPath for project ${project} not found in .graphqconfig`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(config.projects[project].schemaPath);
|
||||
})
|
||||
.parse(process.argv);
|
||||
@@ -0,0 +1,81 @@
|
||||
import chokidar from "chokidar";
|
||||
import { Watcher, WatchOptions } from "./types";
|
||||
|
||||
export default class ChokidarWatcher implements Watcher {
|
||||
public watch(
|
||||
paths: ReadonlyArray<string>,
|
||||
options: WatchOptions = {}
|
||||
): AsyncIterable<string> {
|
||||
const client = chokidar.watch(paths as string[], {
|
||||
ignored: options.ignore,
|
||||
});
|
||||
|
||||
// An array to hold all changes, that has not yet been yield.
|
||||
const queue: string[] = [];
|
||||
let firstError: Error | null = null;
|
||||
|
||||
// If this is set, a pending promise is waiting for the next result.
|
||||
let pending:
|
||||
| ({ resolve: (result: string) => void; reject: (error: Error) => void })
|
||||
| null = null;
|
||||
|
||||
// Listen for errors
|
||||
client.on("error", (error: Error) => {
|
||||
// Resolve pending request.
|
||||
if (pending) {
|
||||
pending.reject(error);
|
||||
pending = null;
|
||||
return;
|
||||
}
|
||||
if (!firstError) {
|
||||
firstError = error;
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for changes
|
||||
client.on("change", (pathFile: string) => {
|
||||
// Resolve pending request.
|
||||
if (pending) {
|
||||
pending.resolve(pathFile);
|
||||
pending = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// There is no pending request, save it into the queue.
|
||||
queue.unshift(pathFile);
|
||||
});
|
||||
return {
|
||||
[Symbol.asyncIterator]() {
|
||||
return {
|
||||
next: () =>
|
||||
new Promise<IteratorResult<string>>((resolve, reject) => {
|
||||
const wrapped = {
|
||||
resolve: (pathFile: string) =>
|
||||
resolve({
|
||||
done: false,
|
||||
value: pathFile,
|
||||
}),
|
||||
reject: (error: Error) =>
|
||||
reject({
|
||||
done: true,
|
||||
value: error,
|
||||
}),
|
||||
};
|
||||
|
||||
// We already have a change to return
|
||||
if (firstError) {
|
||||
wrapped.reject(firstError);
|
||||
return;
|
||||
}
|
||||
if (queue.length) {
|
||||
wrapped.resolve(queue.pop()!);
|
||||
return;
|
||||
}
|
||||
// We need to wait for the next change event.
|
||||
pending = wrapped;
|
||||
}),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import spawn from "cross-spawn";
|
||||
import { Cancelable, debounce } from "lodash";
|
||||
|
||||
import { Executor } from "./types";
|
||||
|
||||
interface CommandExecutorOptions {
|
||||
args?: ReadonlyArray<string>;
|
||||
// If true, allow spawning multiple processes.
|
||||
spawnMutiple?: boolean;
|
||||
|
||||
// Specify the period in which the process is started at max once.
|
||||
debounce?: number | false;
|
||||
|
||||
// If true, will run command upon initialization.
|
||||
runOnInit?: boolean;
|
||||
}
|
||||
|
||||
export default class CommandExecutor implements Executor {
|
||||
private cmd: string;
|
||||
private args?: ReadonlyArray<string>;
|
||||
private spawnMultiple: boolean;
|
||||
private runOnInit: boolean;
|
||||
private isRunning: boolean = false;
|
||||
private shouldRespawn: boolean = false;
|
||||
private spawnProcessDebounced?: (() => void) & Cancelable;
|
||||
|
||||
constructor(cmd: string, opts: CommandExecutorOptions = {}) {
|
||||
this.cmd = cmd;
|
||||
this.args = opts.args;
|
||||
this.spawnMultiple = opts.spawnMutiple || false;
|
||||
this.runOnInit = opts.runOnInit || false;
|
||||
|
||||
const wait = opts.debounce === undefined ? 500 : opts.debounce;
|
||||
if (wait) {
|
||||
this.spawnProcessDebounced = debounce(() => this.spawnProcess(), wait);
|
||||
}
|
||||
}
|
||||
|
||||
// This is called before watching starts.
|
||||
public onInit(): void {
|
||||
if (this.runOnInit) {
|
||||
this.spawnProcessPotentiallyDebounced();
|
||||
}
|
||||
}
|
||||
|
||||
private spawnProcessPotentiallyDebounced() {
|
||||
if (this.spawnProcessDebounced) {
|
||||
this.spawnProcessDebounced();
|
||||
return;
|
||||
}
|
||||
this.spawnProcess();
|
||||
}
|
||||
|
||||
private spawnProcess() {
|
||||
if (this.isRunning && !this.spawnMultiple) {
|
||||
this.shouldRespawn = true;
|
||||
return;
|
||||
}
|
||||
this.isRunning = true;
|
||||
this.shouldRespawn = false;
|
||||
const child = spawn(this.cmd, this.args as string[], {
|
||||
stdio: "inherit",
|
||||
shell: !this.args,
|
||||
});
|
||||
|
||||
child.on("close", (code: number) => {
|
||||
this.isRunning = false;
|
||||
if (code !== 0) {
|
||||
// tslint:disable-next-line: no-console
|
||||
console.log(`We had an error building ${code}`);
|
||||
}
|
||||
if (this.shouldRespawn) {
|
||||
this.spawnProcessPotentiallyDebounced();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public execute(filePath: string) {
|
||||
this.spawnProcessPotentiallyDebounced();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { ChildProcess } from "child_process";
|
||||
import spawn from "cross-spawn";
|
||||
import { Cancelable, debounce } from "lodash";
|
||||
import { Executor } from "./types";
|
||||
|
||||
interface LongRunningExecutorOptions {
|
||||
args?: ReadonlyArray<string>;
|
||||
|
||||
// Specify the period in which the process is restarted at max once.
|
||||
debounce?: number;
|
||||
}
|
||||
|
||||
export default class LongRunningExecutor implements Executor {
|
||||
private cmd: string;
|
||||
private args?: ReadonlyArray<string>;
|
||||
private process: ChildProcess | null = null;
|
||||
private isRunning: boolean = false;
|
||||
private shouldRestart: boolean = false;
|
||||
private restartDebounced: (() => void) & Cancelable;
|
||||
|
||||
constructor(cmd: string, opts: LongRunningExecutorOptions = {}) {
|
||||
this.cmd = cmd;
|
||||
this.args = opts.args;
|
||||
this.restartDebounced = debounce(
|
||||
() => this.restart(),
|
||||
opts.debounce || 500
|
||||
);
|
||||
}
|
||||
|
||||
private spawnProcess() {
|
||||
this.isRunning = true;
|
||||
this.process = spawn(this.cmd, this.args as string[], {
|
||||
stdio: "inherit",
|
||||
// Have all child processes in their own group.
|
||||
// See `process.kill` below.
|
||||
detached: true,
|
||||
shell: !this.args,
|
||||
});
|
||||
|
||||
this.process!.on("exit", (code: number) => {
|
||||
this.isRunning = false;
|
||||
|
||||
if (code !== 0 && code !== null) {
|
||||
// tslint:disable-next-line: no-console
|
||||
console.error(`Exit code returned ${code}`);
|
||||
return;
|
||||
}
|
||||
if (this.shouldRestart) {
|
||||
this.spawnProcess();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private restart(): void {
|
||||
this.shouldRestart = true;
|
||||
// Using the `-` will kill all child procceses in the group.
|
||||
// See: https://azimi.me/2014/12/31/kill-child_process-node-js.html
|
||||
process.kill(-this.process!.pid, "SIGTERM");
|
||||
}
|
||||
|
||||
private kill(): void {
|
||||
this.shouldRestart = false;
|
||||
// Using the `-` will kill all child procceses in the group.
|
||||
// See: https://azimi.me/2014/12/31/kill-child_process-node-js.html
|
||||
process.kill(-this.process!.pid, "SIGTERM");
|
||||
}
|
||||
|
||||
// This is called before watching starts.
|
||||
public onInit(): void {
|
||||
this.spawnProcess();
|
||||
}
|
||||
|
||||
// This is called before exiting.
|
||||
public onCleanup() {
|
||||
this.restartDebounced.cancel();
|
||||
if (this.isRunning) {
|
||||
this.kill();
|
||||
}
|
||||
}
|
||||
|
||||
public execute(filePath: string) {
|
||||
if (this.isRunning) {
|
||||
this.restartDebounced();
|
||||
return;
|
||||
}
|
||||
this.spawnProcess();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env ts-node
|
||||
|
||||
import program from "commander";
|
||||
import path from "path";
|
||||
import watch from "../";
|
||||
|
||||
program
|
||||
.version("0.1.0")
|
||||
.usage("<configFile>")
|
||||
.arguments("<configFile>")
|
||||
.description("Run watchers defined in <configFile>")
|
||||
.action(configFile => {
|
||||
let config: any = require(path.resolve(configFile));
|
||||
if (config.__esModule) {
|
||||
config = config.default;
|
||||
}
|
||||
watch(config);
|
||||
})
|
||||
.parse(process.argv);
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default as ChokidarWatcher } from "./ChokidarWatcher";
|
||||
export { default as CommandExecutor } from "./CommandExecutor";
|
||||
export { default as LongRunningExecutor } from "./LongRunningExecutor";
|
||||
export { default } from "./watch";
|
||||
export * from "./types";
|
||||
@@ -0,0 +1,50 @@
|
||||
import Joi from "joi";
|
||||
|
||||
export interface WatchOptions {
|
||||
ignore?: ReadonlyArray<string>;
|
||||
}
|
||||
|
||||
export interface Watcher {
|
||||
watch(
|
||||
paths: ReadonlyArray<string>,
|
||||
options?: WatchOptions
|
||||
): AsyncIterable<string>;
|
||||
}
|
||||
|
||||
export interface Executor {
|
||||
onInit?(): void;
|
||||
onCleanup?(): void;
|
||||
execute(filePath: string): void;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
rootDir?: string;
|
||||
backend?: Watcher;
|
||||
watchers: {
|
||||
[key: string]: WatchConfig;
|
||||
};
|
||||
}
|
||||
|
||||
export interface WatchConfig {
|
||||
paths: ReadonlyArray<string>;
|
||||
ignore?: ReadonlyArray<string>;
|
||||
executor: Executor;
|
||||
}
|
||||
|
||||
export const configSchema = Joi.object({
|
||||
rootDir: Joi.string().optional(),
|
||||
backend: Joi.object().optional(),
|
||||
watchers: Joi.object().pattern(
|
||||
/.*/,
|
||||
Joi.object({
|
||||
paths: Joi.array()
|
||||
.items(Joi.string())
|
||||
.unique(),
|
||||
ignore: Joi.array()
|
||||
.items(Joi.string())
|
||||
.unique()
|
||||
.optional(),
|
||||
executor: Joi.object(),
|
||||
})
|
||||
),
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import Joi from "joi";
|
||||
import path from "path";
|
||||
|
||||
import ChokidarWatcher from "./ChokidarWatcher";
|
||||
import { Config, configSchema, WatchConfig, Watcher } from "./types";
|
||||
|
||||
// Polyfill the asyncIterator symbol.
|
||||
if (Symbol.asyncIterator === undefined) {
|
||||
(Symbol as any).asyncIterator = Symbol.for("asyncIterator");
|
||||
}
|
||||
|
||||
async function beginWatch(watcher: Watcher, key: string, config: WatchConfig) {
|
||||
const { paths, ignore, executor } = config;
|
||||
if (executor.onInit) {
|
||||
executor.onInit();
|
||||
}
|
||||
for await (const filePath of watcher.watch(paths, { ignore })) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(`Execute "${key}"`);
|
||||
executor.execute(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
function prependRootDir(prepend: string, cfg: WatchConfig): WatchConfig {
|
||||
const prependFunc = (p: string) => path.resolve(prepend, p);
|
||||
return {
|
||||
...cfg,
|
||||
paths: cfg.paths.map(prependFunc),
|
||||
ignore: cfg.ignore ? cfg.ignore.map(prependFunc) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function setupCleanup(config: Config) {
|
||||
["SIGINT", "SIGTERM"].forEach(signal =>
|
||||
process.once(signal as any, () => {
|
||||
for (const key of Object.keys(config.watchers)) {
|
||||
if (config.watchers[key].executor.onCleanup) {
|
||||
config.watchers[key].executor.onCleanup!();
|
||||
}
|
||||
}
|
||||
process.exit(0);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export default async function watch(config: Config) {
|
||||
Joi.assert(config, configSchema);
|
||||
const watcher = config.backend || new ChokidarWatcher();
|
||||
setupCleanup(config);
|
||||
for (const key of Object.keys(config.watchers)) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(`Start watcher "${key}"`);
|
||||
const watcherConfig = config.rootDir
|
||||
? prependRootDir(config.rootDir, config.watchers[key])
|
||||
: config.watchers[key];
|
||||
beginWatch(watcher, key, watcherConfig);
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,7 @@
|
||||
"target": "es2015",
|
||||
"module": "esnext",
|
||||
"jsx": "preserve",
|
||||
"noEmit": true,
|
||||
"strictNullChecks": true,
|
||||
"allowJs": false,
|
||||
"lib": [
|
||||
"dom",
|
||||
"es7",
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": false,
|
||||
"sourceMap": true,
|
||||
"pretty": false,
|
||||
"removeComments": true,
|
||||
"noEmit": false,
|
||||
"outDir": "../dist",
|
||||
// See https://github.com/prismagraphql/graphql-request/issues/26 for why we
|
||||
// have to include "dom" here.
|
||||
"lib": [
|
||||
"es6",
|
||||
"esnext.asynciterable",
|
||||
"dom"
|
||||
],
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"talk-server/*": [
|
||||
"./core/server/*"
|
||||
],
|
||||
"talk-common/*": [
|
||||
"./core/common/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"./**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"./core/client"
|
||||
]
|
||||
}
|
||||
+8
-21
@@ -3,6 +3,7 @@
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"allowJs": true,
|
||||
"noEmit": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
@@ -12,32 +13,18 @@
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"noErrorTruncation": true,
|
||||
"sourceMap": true,
|
||||
"pretty": false,
|
||||
"removeComments": true,
|
||||
"outDir": "dist",
|
||||
// See https://github.com/prismagraphql/graphql-request/issues/26 for why we
|
||||
// have to include "dom" here.
|
||||
"lib": [
|
||||
"es6",
|
||||
"esnext.asynciterable",
|
||||
"dom"
|
||||
],
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"talk-server/*": [
|
||||
"./src/core/server/*"
|
||||
],
|
||||
"talk-common/*": [
|
||||
"./src/core/common/*"
|
||||
]
|
||||
}
|
||||
"esnext.asynciterable"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
"./src/**/.*.js",
|
||||
"./scripts/**/*",
|
||||
"./config/**/*",
|
||||
"*.js"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"./src/core/client"
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user