@cordisjs/cli-cordis 0.0.0 → 2.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +105 -0
- package/lib/worker/daemon.d.ts +10 -0
- package/lib/worker/index.d.ts +10 -0
- package/lib/worker/index.js +103 -0
- package/lib/worker/main.d.ts +1 -0
- package/lib/worker/main.js +5 -0
- package/package.json +59 -1
- package/src/index.ts +112 -0
- package/src/worker/daemon.ts +72 -0
- package/src/worker/index.ts +51 -0
- package/src/worker/main.ts +6 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# @cordisjs/cli-cordis
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
import { fork } from "node:child_process";
|
|
6
|
+
import { extname, resolve } from "node:path";
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import kleur from "kleur";
|
|
10
|
+
var name = "cli-cordis";
|
|
11
|
+
var inject = ["cli"];
|
|
12
|
+
function apply(ctx, config) {
|
|
13
|
+
ctx.cli.command("", "Meta-framework of spatiotemporal composability").option("-v, --version", "Show version").action(({ options }) => {
|
|
14
|
+
if (options.version) {
|
|
15
|
+
const require2 = createRequire(import.meta.url);
|
|
16
|
+
const { version } = require2("cordis/package.json");
|
|
17
|
+
return `cordis ${version}`;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
ctx.cli.command("run [url]", "Start a Cordis application").option("-d, --daemon", "Run as daemon", { default: config.daemon.enabled }).action(async ({ args, options }) => {
|
|
21
|
+
const workerConfig = { ...config };
|
|
22
|
+
workerConfig.baseUrl ??= ctx.baseUrl;
|
|
23
|
+
if (args[0]) {
|
|
24
|
+
workerConfig.path = args[0];
|
|
25
|
+
}
|
|
26
|
+
if (options.daemon) {
|
|
27
|
+
createWorker(workerConfig);
|
|
28
|
+
} else {
|
|
29
|
+
const { start } = await import("./worker/index.js");
|
|
30
|
+
await start(workerConfig);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
__name(apply, "apply");
|
|
35
|
+
var child;
|
|
36
|
+
function createWorker(config) {
|
|
37
|
+
let timer;
|
|
38
|
+
let started = false;
|
|
39
|
+
let exiting = false;
|
|
40
|
+
process.env.CORDIS_SHARED = JSON.stringify({
|
|
41
|
+
startTime: Date.now()
|
|
42
|
+
});
|
|
43
|
+
const filename = fileURLToPath(import.meta.url);
|
|
44
|
+
child = fork(resolve(filename, `../worker/main${extname(filename)}`), [], {
|
|
45
|
+
execArgv: [
|
|
46
|
+
...process.execArgv,
|
|
47
|
+
...config.execArgv || [],
|
|
48
|
+
"--expose-internals"
|
|
49
|
+
],
|
|
50
|
+
env: {
|
|
51
|
+
...process.env,
|
|
52
|
+
CORDIS_LOADER_OPTIONS: JSON.stringify(config)
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
child.on("message", (message) => {
|
|
56
|
+
if (message.type === "start") {
|
|
57
|
+
started = true;
|
|
58
|
+
timer = config.daemon?.heartbeatTimeout && setTimeout(() => {
|
|
59
|
+
console.log(kleur.red("daemon: heartbeat timeout"));
|
|
60
|
+
child.kill("SIGKILL");
|
|
61
|
+
}, config.daemon?.heartbeatTimeout);
|
|
62
|
+
} else if (message.type === "shared") {
|
|
63
|
+
process.env.CORDIS_SHARED = message.body;
|
|
64
|
+
} else if (message.type === "heartbeat") {
|
|
65
|
+
if (timer) timer.refresh();
|
|
66
|
+
} else if (message.type === "exit") {
|
|
67
|
+
exiting = true;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
const signals = [
|
|
71
|
+
"SIGABRT",
|
|
72
|
+
"SIGBREAK",
|
|
73
|
+
"SIGBUS",
|
|
74
|
+
"SIGFPE",
|
|
75
|
+
"SIGHUP",
|
|
76
|
+
"SIGILL",
|
|
77
|
+
"SIGINT",
|
|
78
|
+
"SIGKILL",
|
|
79
|
+
"SIGSEGV",
|
|
80
|
+
"SIGSTOP",
|
|
81
|
+
"SIGTERM"
|
|
82
|
+
];
|
|
83
|
+
function shouldExit(code, signal) {
|
|
84
|
+
if (!started) return true;
|
|
85
|
+
if (exiting) return true;
|
|
86
|
+
if (code === 0) return true;
|
|
87
|
+
if (signals.includes(signal)) return true;
|
|
88
|
+
if (code === 51) return false;
|
|
89
|
+
if (code === 52) return true;
|
|
90
|
+
return !config.daemon?.autoRestart;
|
|
91
|
+
}
|
|
92
|
+
__name(shouldExit, "shouldExit");
|
|
93
|
+
child.on("exit", (code, signal) => {
|
|
94
|
+
if (shouldExit(code, signal)) {
|
|
95
|
+
process.exit(code);
|
|
96
|
+
}
|
|
97
|
+
createWorker(config);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
__name(createWorker, "createWorker");
|
|
101
|
+
export {
|
|
102
|
+
apply,
|
|
103
|
+
inject,
|
|
104
|
+
name
|
|
105
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Context } from 'cordis';
|
|
2
|
+
export interface Config {
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
autoRestart?: boolean;
|
|
5
|
+
heartbeatInterval?: number;
|
|
6
|
+
heartbeatTimeout?: number;
|
|
7
|
+
exitTimeout?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare const name = "daemon";
|
|
10
|
+
export declare function apply(ctx: Context, config?: Config): Generator<() => void, void, unknown>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { EntryOptions } from '@cordisjs/loader';
|
|
2
|
+
import * as daemon from './daemon.ts';
|
|
3
|
+
export interface Config {
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
execArgv?: string[];
|
|
6
|
+
path: string;
|
|
7
|
+
daemon: daemon.Config;
|
|
8
|
+
prelude?: EntryOptions[];
|
|
9
|
+
}
|
|
10
|
+
export declare function start(config: Config): Promise<void>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name2 in all)
|
|
5
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// src/worker/index.ts
|
|
9
|
+
import { Context } from "cordis";
|
|
10
|
+
import { noop } from "cosmokit";
|
|
11
|
+
import Fs from "@cordisjs/fs";
|
|
12
|
+
import NodeFs from "@cordisjs/fs-node";
|
|
13
|
+
import { Loader } from "@cordisjs/loader";
|
|
14
|
+
|
|
15
|
+
// src/worker/daemon.ts
|
|
16
|
+
var daemon_exports = {};
|
|
17
|
+
__export(daemon_exports, {
|
|
18
|
+
apply: () => apply,
|
|
19
|
+
name: () => name
|
|
20
|
+
});
|
|
21
|
+
var name = "daemon";
|
|
22
|
+
var closedErrors = ["EPIPE", "ECONNRESET", "ERR_IPC_CHANNEL_CLOSED"];
|
|
23
|
+
function* apply(ctx, config = {}) {
|
|
24
|
+
let exiting = false;
|
|
25
|
+
function send(message) {
|
|
26
|
+
process.send?.(message, (error) => {
|
|
27
|
+
if (!error || closedErrors.includes(error.code)) return;
|
|
28
|
+
ctx.logger.warn(error);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
__name(send, "send");
|
|
32
|
+
function exit(signal, reason) {
|
|
33
|
+
if (exiting) return;
|
|
34
|
+
exiting = true;
|
|
35
|
+
setTimeout(() => process.exit(1), config.exitTimeout ?? 1e4).unref();
|
|
36
|
+
ctx.logger.info(reason);
|
|
37
|
+
ctx.parallel("exit", signal).finally(() => process.exit());
|
|
38
|
+
}
|
|
39
|
+
__name(exit, "exit");
|
|
40
|
+
function handleSignal(signal) {
|
|
41
|
+
process.stderr.write("\x1B[2K\r");
|
|
42
|
+
send({ type: "exit" });
|
|
43
|
+
exit(signal, `terminated by ${signal}`);
|
|
44
|
+
}
|
|
45
|
+
__name(handleSignal, "handleSignal");
|
|
46
|
+
function handleDisconnect() {
|
|
47
|
+
exit("SIGHUP", "parent process disconnected");
|
|
48
|
+
}
|
|
49
|
+
__name(handleDisconnect, "handleDisconnect");
|
|
50
|
+
process.on("SIGINT", handleSignal);
|
|
51
|
+
yield () => process.off("SIGINT", handleSignal);
|
|
52
|
+
process.on("SIGTERM", handleSignal);
|
|
53
|
+
yield () => process.off("SIGTERM", handleSignal);
|
|
54
|
+
process.on("disconnect", handleDisconnect);
|
|
55
|
+
yield () => process.off("disconnect", handleDisconnect);
|
|
56
|
+
if (process.send && !process.connected) {
|
|
57
|
+
handleDisconnect();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
send({ type: "start", body: config });
|
|
61
|
+
if (config.heartbeatInterval) {
|
|
62
|
+
const timer = setInterval(() => {
|
|
63
|
+
send({ type: "heartbeat" });
|
|
64
|
+
}, config.heartbeatInterval);
|
|
65
|
+
yield () => clearInterval(timer);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
__name(apply, "apply");
|
|
69
|
+
|
|
70
|
+
// src/worker/index.ts
|
|
71
|
+
async function start(config) {
|
|
72
|
+
const ctx = new Context();
|
|
73
|
+
process.stdout.on("error", noop);
|
|
74
|
+
process.stderr.on("error", noop);
|
|
75
|
+
process.on("uncaughtException", (error) => {
|
|
76
|
+
ctx.logger.error(error);
|
|
77
|
+
process.exitCode = 1;
|
|
78
|
+
});
|
|
79
|
+
process.on("unhandledRejection", (error) => {
|
|
80
|
+
ctx.logger.warn(error);
|
|
81
|
+
});
|
|
82
|
+
ctx.baseUrl = config.baseUrl;
|
|
83
|
+
if (config.daemon.enabled) {
|
|
84
|
+
await ctx.use(daemon_exports, config.daemon);
|
|
85
|
+
}
|
|
86
|
+
await ctx.use(Fs);
|
|
87
|
+
await ctx.use(NodeFs);
|
|
88
|
+
await ctx.use(Loader);
|
|
89
|
+
for (const options of config.prelude ?? []) {
|
|
90
|
+
await ctx.loader.create(options);
|
|
91
|
+
}
|
|
92
|
+
await ctx.loader.create({
|
|
93
|
+
name: "@cordisjs/include",
|
|
94
|
+
config: {
|
|
95
|
+
path: config.path,
|
|
96
|
+
enableLogs: true
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
__name(start, "start");
|
|
101
|
+
export {
|
|
102
|
+
start
|
|
103
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1 +1,59 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"name": "@cordisjs/cli-cordis",
|
|
3
|
+
"version": "2.0.0-rc.0",
|
|
4
|
+
"description": "Commands for running Cordis applications",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"sideEffects": [
|
|
8
|
+
"./lib/worker/main.js",
|
|
9
|
+
"./src/worker/main.ts"
|
|
10
|
+
],
|
|
11
|
+
"type": "module",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./lib/index.d.ts",
|
|
15
|
+
"default": "./lib/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./worker": {
|
|
18
|
+
"types": "./lib/worker/index.d.ts",
|
|
19
|
+
"default": "./lib/worker/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./main": "./lib/worker/main.js",
|
|
22
|
+
"./src/*": "./src/*",
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"lib",
|
|
27
|
+
"src"
|
|
28
|
+
],
|
|
29
|
+
"author": "Shigma <shigma10826@gmail.com>",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/cordiverse/cordis.git",
|
|
34
|
+
"directory": "packages/cli-cordis"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/cordiverse/cordis/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/cordiverse/cordis",
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@cordisjs/cli": "^4.0.0",
|
|
42
|
+
"@cordisjs/fs": "^2.0.0-rc.0",
|
|
43
|
+
"@cordisjs/fs-node": "^0.1.0",
|
|
44
|
+
"@cordisjs/loader": "^1.0.0-rc.10",
|
|
45
|
+
"cordis": "^4.0.0-rc.13"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@cordisjs/cli": "^4.0.0",
|
|
49
|
+
"@cordisjs/fs": "^2.0.0-rc.0",
|
|
50
|
+
"@cordisjs/fs-node": "^0.1.0",
|
|
51
|
+
"@cordisjs/loader": "^1.0.0-rc.10",
|
|
52
|
+
"@cordisjs/logger-console": "^1.0.1",
|
|
53
|
+
"cordis": "^4.0.0-rc.13"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"cosmokit": "^1.8.1",
|
|
57
|
+
"kleur": "^4.1.5"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { ChildProcess, fork } from 'node:child_process'
|
|
2
|
+
import { extname, resolve } from 'node:path'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { Context } from 'cordis'
|
|
6
|
+
import type {} from '@cordisjs/cli'
|
|
7
|
+
import type { Config } from './worker/index.ts'
|
|
8
|
+
import kleur from 'kleur'
|
|
9
|
+
|
|
10
|
+
export const name = 'cli-cordis'
|
|
11
|
+
export const inject = ['cli']
|
|
12
|
+
|
|
13
|
+
interface Event {
|
|
14
|
+
type: 'start' | 'shared' | 'heartbeat' | 'exit'
|
|
15
|
+
body?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function apply(ctx: Context, config: Config) {
|
|
19
|
+
ctx.cli
|
|
20
|
+
.command('', 'Meta-framework of spatiotemporal composability')
|
|
21
|
+
.option('-v, --version', 'Show version')
|
|
22
|
+
.action(({ options }) => {
|
|
23
|
+
if (options.version) {
|
|
24
|
+
const require = createRequire(import.meta.url)
|
|
25
|
+
const { version } = require('cordis/package.json')
|
|
26
|
+
return `cordis ${version}`
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
ctx.cli
|
|
31
|
+
.command('run [url]', 'Start a Cordis application')
|
|
32
|
+
.option('-d, --daemon', 'Run as daemon', { default: config.daemon.enabled })
|
|
33
|
+
.action(async ({ args, options }) => {
|
|
34
|
+
const workerConfig: Config = { ...config }
|
|
35
|
+
workerConfig.baseUrl ??= ctx.baseUrl
|
|
36
|
+
if (args[0]) {
|
|
37
|
+
workerConfig.path = args[0]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (options.daemon) {
|
|
41
|
+
createWorker(workerConfig)
|
|
42
|
+
} else {
|
|
43
|
+
// Direct mode: load in the same process
|
|
44
|
+
const { start } = await import('./worker/index.ts')
|
|
45
|
+
await start(workerConfig)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let child: ChildProcess
|
|
51
|
+
|
|
52
|
+
function createWorker(config: Config) {
|
|
53
|
+
let timer: 0 | NodeJS.Timeout | undefined
|
|
54
|
+
let started = false
|
|
55
|
+
let exiting = false
|
|
56
|
+
|
|
57
|
+
process.env.CORDIS_SHARED = JSON.stringify({
|
|
58
|
+
startTime: Date.now(),
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const filename = fileURLToPath(import.meta.url)
|
|
62
|
+
child = fork(resolve(filename, `../worker/main${extname(filename)}`), [], {
|
|
63
|
+
execArgv: [
|
|
64
|
+
...process.execArgv,
|
|
65
|
+
...config.execArgv || [],
|
|
66
|
+
'--expose-internals',
|
|
67
|
+
],
|
|
68
|
+
env: {
|
|
69
|
+
...process.env,
|
|
70
|
+
CORDIS_LOADER_OPTIONS: JSON.stringify(config),
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
child.on('message', (message: Event) => {
|
|
75
|
+
if (message.type === 'start') {
|
|
76
|
+
started = true
|
|
77
|
+
timer = config.daemon?.heartbeatTimeout && setTimeout(() => {
|
|
78
|
+
// eslint-disable-next-line no-console
|
|
79
|
+
console.log(kleur.red('daemon: heartbeat timeout'))
|
|
80
|
+
child.kill('SIGKILL')
|
|
81
|
+
}, config.daemon?.heartbeatTimeout)
|
|
82
|
+
} else if (message.type === 'shared') {
|
|
83
|
+
process.env.CORDIS_SHARED = message.body
|
|
84
|
+
} else if (message.type === 'heartbeat') {
|
|
85
|
+
if (timer) timer.refresh()
|
|
86
|
+
} else if (message.type === 'exit') {
|
|
87
|
+
exiting = true
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const signals: NodeJS.Signals[] = [
|
|
92
|
+
'SIGABRT', 'SIGBREAK', 'SIGBUS', 'SIGFPE', 'SIGHUP',
|
|
93
|
+
'SIGILL', 'SIGINT', 'SIGKILL', 'SIGSEGV', 'SIGSTOP', 'SIGTERM',
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
function shouldExit(code: number, signal: NodeJS.Signals) {
|
|
97
|
+
if (!started) return true
|
|
98
|
+
if (exiting) return true
|
|
99
|
+
if (code === 0) return true
|
|
100
|
+
if (signals.includes(signal)) return true
|
|
101
|
+
if (code === 51) return false
|
|
102
|
+
if (code === 52) return true
|
|
103
|
+
return !config.daemon?.autoRestart
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
child.on('exit', (code, signal) => {
|
|
107
|
+
if (shouldExit(code!, signal!)) {
|
|
108
|
+
process.exit(code!)
|
|
109
|
+
}
|
|
110
|
+
createWorker(config)
|
|
111
|
+
})
|
|
112
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Context } from 'cordis'
|
|
2
|
+
|
|
3
|
+
export interface Config {
|
|
4
|
+
enabled?: boolean
|
|
5
|
+
autoRestart?: boolean
|
|
6
|
+
heartbeatInterval?: number
|
|
7
|
+
heartbeatTimeout?: number
|
|
8
|
+
exitTimeout?: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const name = 'daemon'
|
|
12
|
+
|
|
13
|
+
// errors raised by writing to an IPC channel whose peer has gone
|
|
14
|
+
const closedErrors = ['EPIPE', 'ECONNRESET', 'ERR_IPC_CHANNEL_CLOSED']
|
|
15
|
+
|
|
16
|
+
export function* apply(ctx: Context, config: Config = {}) {
|
|
17
|
+
let exiting = false
|
|
18
|
+
|
|
19
|
+
// `process.send` is undefined when the worker is not forked with an IPC channel
|
|
20
|
+
function send(message: any) {
|
|
21
|
+
// A callback receives write errors that would otherwise be emitted on `process`.
|
|
22
|
+
// `process.connected` may still be true when the peer has already closed.
|
|
23
|
+
process.send?.(message, (error: any) => {
|
|
24
|
+
if (!error || closedErrors.includes(error.code)) return
|
|
25
|
+
ctx.logger.warn(error)
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function exit(signal: NodeJS.Signals, reason: string) {
|
|
30
|
+
if (exiting) return
|
|
31
|
+
exiting = true
|
|
32
|
+
// an `exit` listener that never settles must not keep the worker alive
|
|
33
|
+
setTimeout(() => process.exit(1), config.exitTimeout ?? 10000).unref()
|
|
34
|
+
ctx.logger.info(reason)
|
|
35
|
+
ctx.parallel('exit', signal).finally(() => process.exit())
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function handleSignal(signal: NodeJS.Signals) {
|
|
39
|
+
process.stderr.write('\x1b[2K\r')
|
|
40
|
+
// the parent process must not restart a worker that is exiting on request,
|
|
41
|
+
// regardless of the exit code produced by the cleanup
|
|
42
|
+
send({ type: 'exit' })
|
|
43
|
+
exit(signal, `terminated by ${signal}`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// the parent process has exited, so no one supervises or restarts this worker
|
|
47
|
+
function handleDisconnect() {
|
|
48
|
+
exit('SIGHUP', 'parent process disconnected')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
process.on('SIGINT', handleSignal)
|
|
52
|
+
yield () => process.off('SIGINT', handleSignal)
|
|
53
|
+
process.on('SIGTERM', handleSignal)
|
|
54
|
+
yield () => process.off('SIGTERM', handleSignal)
|
|
55
|
+
process.on('disconnect', handleDisconnect)
|
|
56
|
+
yield () => process.off('disconnect', handleDisconnect)
|
|
57
|
+
|
|
58
|
+
// `disconnect` is not emitted again for a channel that closed before the
|
|
59
|
+
// listener was registered, such as during module loading or preloading
|
|
60
|
+
if (process.send && !process.connected) {
|
|
61
|
+
handleDisconnect()
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
send({ type: 'start', body: config })
|
|
66
|
+
if (config.heartbeatInterval) {
|
|
67
|
+
const timer = setInterval(() => {
|
|
68
|
+
send({ type: 'heartbeat' })
|
|
69
|
+
}, config.heartbeatInterval)
|
|
70
|
+
yield () => clearInterval(timer)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Context } from 'cordis'
|
|
2
|
+
import { noop } from 'cosmokit'
|
|
3
|
+
import Fs from '@cordisjs/fs'
|
|
4
|
+
import NodeFs from '@cordisjs/fs-node'
|
|
5
|
+
import { EntryOptions, Loader } from '@cordisjs/loader'
|
|
6
|
+
import * as daemon from './daemon.ts'
|
|
7
|
+
|
|
8
|
+
export interface Config {
|
|
9
|
+
baseUrl?: string
|
|
10
|
+
execArgv?: string[]
|
|
11
|
+
path: string
|
|
12
|
+
daemon: daemon.Config
|
|
13
|
+
prelude?: EntryOptions[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function start(config: Config) {
|
|
17
|
+
const ctx = new Context()
|
|
18
|
+
|
|
19
|
+
// Output to stdio is best-effort. A write error, such as EPIPE after the
|
|
20
|
+
// reader has gone, would otherwise become an uncaught exception, whose log
|
|
21
|
+
// entry is written to the same stream and raises the next one.
|
|
22
|
+
process.stdout.on('error', noop)
|
|
23
|
+
process.stderr.on('error', noop)
|
|
24
|
+
|
|
25
|
+
process.on('uncaughtException', (error) => {
|
|
26
|
+
ctx.logger.error(error)
|
|
27
|
+
process.exitCode = 1
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
process.on('unhandledRejection', (error) => {
|
|
31
|
+
ctx.logger.warn(error)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
ctx.baseUrl = config.baseUrl
|
|
35
|
+
if (config.daemon.enabled) {
|
|
36
|
+
await ctx.use(daemon, config.daemon)
|
|
37
|
+
}
|
|
38
|
+
await ctx.use(Fs)
|
|
39
|
+
await ctx.use(NodeFs)
|
|
40
|
+
await ctx.use(Loader)
|
|
41
|
+
for (const options of config.prelude ?? []) {
|
|
42
|
+
await ctx.loader.create(options)
|
|
43
|
+
}
|
|
44
|
+
await ctx.loader.create({
|
|
45
|
+
name: '@cordisjs/include',
|
|
46
|
+
config: {
|
|
47
|
+
path: config.path,
|
|
48
|
+
enableLogs: true,
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
}
|