-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
23 changed files
with
1,377 additions
and
3 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,2 @@ | ||
.DS_Store | ||
tsconfig.tsbuildinfo |
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,49 @@ | ||
{ | ||
"name": "@cordisjs/cli", | ||
"description": "CLI for cordis loader", | ||
"type": "module", | ||
"version": "0.3.4", | ||
"main": "lib/index.js", | ||
"types": "lib/index.d.ts", | ||
"bin": { | ||
"cordis": "lib/cli.js" | ||
}, | ||
"exports": { | ||
".": "./lib/index.js", | ||
"./worker": "./lib/worker/index.js", | ||
"./worker/main": "./lib/worker/main.js", | ||
"./src/*": "./src/*", | ||
"./package.json": "./package.json" | ||
}, | ||
"files": [ | ||
"lib", | ||
"src" | ||
], | ||
"engines": { | ||
"node": "^18.0.0 || >=20.0.0" | ||
}, | ||
"author": "Shigma <[email protected]>", | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/cordisjs/cordis.git", | ||
"directory": "packages/cli" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/cordisjs/cordis/issues" | ||
}, | ||
"homepage": "https://github.com/cordisjs/cordis", | ||
"keywords": [ | ||
"cordis", | ||
"loader", | ||
"cli" | ||
], | ||
"dependencies": { | ||
"@cordisjs/loader": "0.3.3", | ||
"@cordisjs/logger": "^0.1.4", | ||
"cac": "^6.7.14", | ||
"cordis": "^3.7.1", | ||
"cosmokit": "^1.5.2", | ||
"kleur": "^4.1.5" | ||
} | ||
} |
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,72 @@ | ||
#!/usr/bin/env node | ||
|
||
import { cac } from 'cac' | ||
import kleur from 'kleur' | ||
import { start } from './index.js' | ||
import { Dict, hyphenate } from 'cosmokit' | ||
import { version } from '../package.json' | ||
|
||
export function isInteger(source: any) { | ||
return typeof source === 'number' && Math.floor(source) === source | ||
} | ||
|
||
const cli = cac('cordis').help().version(version) | ||
|
||
function toArg(key: string) { | ||
return key.length === 1 ? `-${key}` : `--${hyphenate(key)}` | ||
} | ||
|
||
function addToArray(args: string[], arg: string) { | ||
if (!args.includes(arg)) args.push(arg) | ||
} | ||
|
||
function unparse(argv: Dict) { | ||
const execArgv = Object.entries(argv).flatMap<string>(([key, value]) => { | ||
if (key === '--') return [] | ||
key = toArg(key) | ||
if (value === true) { | ||
return [key] | ||
} else if (value === false) { | ||
return ['--no-' + key.slice(2)] | ||
} else if (Array.isArray(value)) { | ||
return value.flatMap(value => [key, value]) | ||
} else { | ||
return [key, value] | ||
} | ||
}) | ||
execArgv.push(...argv['--']) | ||
addToArray(execArgv, '--expose-internals') | ||
return execArgv | ||
} | ||
|
||
cli.command('start [file]', 'start a cordis application') | ||
.alias('run') | ||
.allowUnknownOptions() | ||
.option('--debug [namespace]', 'specify debug namespace') | ||
.option('--log-level [level]', 'specify log level (default: 2)') | ||
.option('--log-time [format]', 'show timestamp in logs') | ||
.action((file, options) => { | ||
const { logLevel, debug, logTime, ...rest } = options | ||
if (logLevel !== undefined && (!isInteger(logLevel) || logLevel < 0)) { | ||
console.warn(`${kleur.red('error')} log level should be a positive integer.`) | ||
process.exit(1) | ||
} | ||
process.env.CORDIS_LOG_LEVEL = logLevel || '' | ||
process.env.CORDIS_LOG_DEBUG = debug || '' | ||
process.env.CORDIS_LOADER_ENTRY = file || '' | ||
start({ | ||
name: 'cordis', | ||
daemon: { | ||
execArgv: unparse(rest), | ||
}, | ||
logger: { | ||
showTime: logTime, | ||
}, | ||
}) | ||
}) | ||
|
||
const argv = cli.parse() | ||
|
||
if (!cli.matchedCommand && !argv.options.help) { | ||
cli.outputHelp() | ||
} |
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,104 @@ | ||
import { ChildProcess, fork } from 'child_process' | ||
import { extname, resolve } from 'path' | ||
import kleur from 'kleur' | ||
import type * as worker from './worker/index.js' | ||
import { fileURLToPath } from 'url' | ||
|
||
type Event = Event.Start | Event.Env | Event.Heartbeat | ||
|
||
namespace Event { | ||
export interface Start { | ||
type: 'start' | ||
} | ||
|
||
export interface Env { | ||
type: 'shared' | ||
body: string | ||
} | ||
|
||
export interface Heartbeat { | ||
type: 'heartbeat' | ||
} | ||
} | ||
|
||
let child: ChildProcess | ||
|
||
process.env.CORDIS_SHARED = JSON.stringify({ | ||
startTime: Date.now(), | ||
}) | ||
|
||
function createWorker(options: worker.Options) { | ||
let timer: 0 | NodeJS.Timeout | undefined | ||
let started = false | ||
|
||
const filename = fileURLToPath(import.meta.url) | ||
child = fork(resolve(filename, `../worker/main${extname(filename)}`), [], { | ||
execArgv: [ | ||
...process.execArgv, | ||
...options.daemon?.execArgv || [], | ||
], | ||
env: { | ||
...process.env, | ||
CORDIS_LOADER_OPTIONS: JSON.stringify(options), | ||
}, | ||
}) | ||
|
||
child.on('message', (message: Event) => { | ||
if (message.type === 'start') { | ||
started = true | ||
timer = options.daemon?.heartbeatTimeout && setTimeout(() => { | ||
console.log(kleur.red('daemon: heartbeat timeout')) | ||
child.kill('SIGKILL') | ||
}, options.daemon?.heartbeatTimeout) | ||
} else if (message.type === 'shared') { | ||
process.env.CORDIS_SHARED = message.body | ||
} else if (message.type === 'heartbeat') { | ||
if (timer) timer.refresh() | ||
} | ||
}) | ||
|
||
// https://nodejs.org/api/process.html#signal-events | ||
// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/signal | ||
const signals: NodeJS.Signals[] = [ | ||
'SIGABRT', | ||
'SIGBREAK', | ||
'SIGBUS', | ||
'SIGFPE', | ||
'SIGHUP', | ||
'SIGILL', | ||
'SIGINT', | ||
'SIGKILL', | ||
'SIGSEGV', | ||
'SIGSTOP', | ||
'SIGTERM', | ||
] | ||
|
||
function shouldExit(code: number, signal: NodeJS.Signals) { | ||
// start failed | ||
if (!started) return true | ||
|
||
// exit manually | ||
if (code === 0) return true | ||
if (signals.includes(signal)) return true | ||
|
||
// restart manually | ||
if (code === 51) return false | ||
if (code === 52) return true | ||
|
||
// fallback to autoRestart | ||
return !options.daemon?.autoRestart | ||
} | ||
|
||
child.on('exit', (code, signal) => { | ||
if (shouldExit(code!, signal!)) { | ||
process.exit(code!) | ||
} | ||
createWorker(options) | ||
}) | ||
} | ||
|
||
export async function start(options: worker.Options) { | ||
if (options.daemon) return createWorker(options) | ||
const worker = await import('./worker/index.js') | ||
worker.start(options) | ||
} |
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,31 @@ | ||
import { Context } from 'cordis' | ||
|
||
export interface Config { | ||
execArgv?: string[] | ||
autoRestart?: boolean | ||
heartbeatInterval?: number | ||
heartbeatTimeout?: number | ||
} | ||
|
||
export const name = 'daemon' | ||
|
||
export function apply(ctx: Context, config: Config = {}) { | ||
function handleSignal(signal: NodeJS.Signals) { | ||
// prevent restarting when child process is exiting | ||
if (config.autoRestart) { | ||
process.send!({ type: 'exit' }) | ||
} | ||
ctx.logger('app').info(`terminated by ${signal}`) | ||
ctx.parallel('exit', signal).finally(() => process.exit()) | ||
} | ||
|
||
ctx.on('ready', () => { | ||
process.send!({ type: 'start', body: config }) | ||
process.on('SIGINT', handleSignal) | ||
process.on('SIGTERM', handleSignal) | ||
|
||
config.heartbeatInterval && setInterval(() => { | ||
process.send!({ type: 'heartbeat' }) | ||
}, config.heartbeatInterval) | ||
}) | ||
} |
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,30 @@ | ||
import Loader from '@cordisjs/loader' | ||
import * as daemon from './daemon.js' | ||
import * as logger from './logger.js' | ||
import { ModuleLoader } from './internal.js' | ||
|
||
export type * from './internal.js' | ||
|
||
declare module '@cordisjs/loader' { | ||
interface Loader { | ||
internal?: ModuleLoader | ||
} | ||
} | ||
|
||
export interface Options extends Loader.Options { | ||
logger?: logger.Config | ||
daemon?: daemon.Config | ||
} | ||
|
||
export async function start(options: Options) { | ||
const loader = new Loader(options) | ||
if (process.execArgv.includes('--expose-internals')) { | ||
const { internal } = await import('./internal.js') | ||
loader.internal = internal | ||
} | ||
await loader.init(process.env.CORDIS_LOADER_ENTRY) | ||
if (options.logger) loader.app.plugin(logger) | ||
if (options.daemon) loader.app.plugin(daemon) | ||
await loader.readConfig() | ||
await loader.start() | ||
} |
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,52 @@ | ||
import { createRequire, LoadHookContext } from 'module' | ||
import { Dict } from 'cosmokit' | ||
|
||
const require = createRequire(import.meta.url) | ||
|
||
type ModuleFormat = 'builtin' | 'commonjs' | 'json' | 'module' | 'wasm' | ||
type ModuleSource = string | ArrayBuffer | ||
|
||
interface ResolveResult { | ||
format: ModuleFormat | ||
url: string | ||
} | ||
|
||
interface LoadResult { | ||
format: ModuleFormat | ||
source?: ModuleSource | ||
} | ||
|
||
interface LoadCache extends Omit<Map<string, Dict<ModuleJob | Function>>, 'get' | 'set' | 'has'> { | ||
get(url: string, type?: string): ModuleJob | Function | undefined | ||
set(url: string, type?: string, job?: ModuleJob | Function): this | ||
has(url: string, type?: string): boolean | ||
} | ||
|
||
export interface ModuleWrap { | ||
url: string | ||
getNamespace(): any | ||
} | ||
|
||
export interface ModuleJob { | ||
url: string | ||
loader: ModuleLoader | ||
module?: ModuleWrap | ||
importAttributes: ImportAttributes | ||
linked: Promise<ModuleJob[]> | ||
instantiate(): Promise<void> | ||
run(): Promise<{ module: ModuleWrap }> | ||
} | ||
|
||
export interface ModuleLoader { | ||
loadCache: LoadCache | ||
import(specifier: string, parentURL: string, importAttributes: ImportAttributes): Promise<any> | ||
register(specifier: string | URL, parentURL?: string | URL, data?: any, transferList?: any[]): void | ||
getModuleJob(specifier: string, parentURL: string, importAttributes: ImportAttributes): Promise<ModuleJob> | ||
getModuleJobSync(specifier: string, parentURL: string, importAttributes: ImportAttributes): ModuleJob | ||
resolve(originalSpecifier: string, parentURL: string, importAttributes: ImportAttributes): Promise<ResolveResult> | ||
resolveSync(originalSpecifier: string, parentURL: string, importAttributes: ImportAttributes): ResolveResult | ||
load(specifier: string, context: Pick<LoadHookContext, 'format' | 'importAttributes'>): Promise<LoadResult> | ||
loadSync(specifier: string, context: Pick<LoadHookContext, 'format' | 'importAttributes'>): LoadResult | ||
} | ||
|
||
export const internal: ModuleLoader = require('internal/process/esm_loader').esmLoader |
Oops, something went wrong.