Skip to content

Commit 15c5723

Browse files
committed
✨ Add Main api (#1102)
In order to implement the inspector, we need to be able to wrap code around the main function that will run around the main entry point. This lets you do one time setup and one-time teardown.
1 parent 8b00d6e commit 15c5723

File tree

2 files changed

+106
-89
lines changed

2 files changed

+106
-89
lines changed

lib/api.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@ interface ScopeApi {
1414
delete<T>(scope: Scope, context: Context<T>): boolean;
1515
}
1616

17+
interface MainApi {
18+
main(body: (args: string[]) => Operation<void>): Promise<void>;
19+
}
20+
1721
interface Apis {
1822
Scope: Api<ScopeApi>;
23+
Main: Api<MainApi>;
1924
}
2025

2126
export const api: Apis = {
@@ -31,4 +36,9 @@ export const api: Apis = {
3136
return delete (scope as ScopeInternal).contexts[context.name];
3237
},
3338
}),
39+
Main: createApi<MainApi>("Main", {
40+
main() {
41+
throw new TypeError(`missing handler for "main()"`);
42+
},
43+
}),
3444
};

lib/main.ts

Lines changed: 96 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { createContext } from "./context.ts";
22
import type { Operation } from "./types.ts";
33
import { callcc } from "./callcc.ts";
44
import { run } from "./run.ts";
5-
import { useScope } from "./scope.ts";
5+
import { global, useScope } from "./scope.ts";
66
import { call } from "./call.ts";
7+
import { api } from "./api.ts";
78

89
/**
910
* Halt process execution immediately and initiate shutdown. If a message is
@@ -58,103 +59,109 @@ export function* exit(status: number, message?: string): Operation<void> {
5859
* @returns a promise that resolves right after the program exits
5960
*/
6061

61-
export async function main(
62+
export function main(
6263
body: (args: string[]) => Operation<void>,
6364
): Promise<void> {
64-
let hardexit = (_status: number) => {};
65-
66-
let result = await run(() =>
67-
callcc<Exit>(function* (resolve) {
68-
// action will return shutdown immediately upon resolve, so stash
69-
// this function in the exit context so it can be called anywhere.
70-
yield* ExitContext.set(resolve);
71-
72-
// this will hold the event loop and prevent runtimes such as
73-
// Node and Deno from exiting prematurely.
74-
let interval = setInterval(() => {}, Math.pow(2, 30));
75-
76-
let scope = yield* useScope();
77-
78-
try {
79-
let interrupt = {
80-
SIGINT: () =>
81-
scope.run(() => resolve({ status: 130, signal: "SIGINT" })),
82-
SIGTERM: () =>
83-
scope.run(() => resolve({ status: 143, signal: "SIGTERM" })),
84-
};
85-
86-
yield* withHost({
87-
*deno() {
88-
hardexit = (status) => Deno.exit(status);
89-
try {
90-
Deno.addSignalListener("SIGINT", interrupt.SIGINT);
91-
/**
92-
* Windows only supports ctrl-c (SIGINT), ctrl-break (SIGBREAK), and ctrl-close (SIGUP)
93-
*/
94-
if (Deno.build.os !== "windows") {
95-
Deno.addSignalListener("SIGTERM", interrupt.SIGTERM);
96-
}
97-
yield* body(Deno.args.slice());
98-
} finally {
99-
Deno.removeSignalListener("SIGINT", interrupt.SIGINT);
100-
if (Deno.build.os !== "windows") {
101-
Deno.removeSignalListener("SIGTERM", interrupt.SIGTERM);
65+
return api.Main.invoke(global, "main", [body]);
66+
}
67+
68+
global.around(api.Main, {
69+
async main([body]) {
70+
let hardexit = (_status: number) => {};
71+
72+
let result = await run(() =>
73+
callcc<Exit>(function* (resolve) {
74+
// action will return shutdown immediately upon resolve, so stash
75+
// this function in the exit context so it can be called anywhere.
76+
yield* ExitContext.set(resolve);
77+
78+
// this will hold the event loop and prevent runtimes such as
79+
// Node and Deno from exiting prematurely.
80+
let interval = setInterval(() => {}, Math.pow(2, 30));
81+
82+
let scope = yield* useScope();
83+
84+
try {
85+
let interrupt = {
86+
SIGINT: () =>
87+
scope.run(() => resolve({ status: 130, signal: "SIGINT" })),
88+
SIGTERM: () =>
89+
scope.run(() => resolve({ status: 143, signal: "SIGTERM" })),
90+
};
91+
92+
yield* withHost({
93+
*deno() {
94+
hardexit = (status) => Deno.exit(status);
95+
try {
96+
Deno.addSignalListener("SIGINT", interrupt.SIGINT);
97+
/**
98+
* Windows only supports ctrl-c (SIGINT), ctrl-break (SIGBREAK), and ctrl-close (SIGUP)
99+
*/
100+
if (Deno.build.os !== "windows") {
101+
Deno.addSignalListener("SIGTERM", interrupt.SIGTERM);
102+
}
103+
yield* body(Deno.args.slice());
104+
} finally {
105+
Deno.removeSignalListener("SIGINT", interrupt.SIGINT);
106+
if (Deno.build.os !== "windows") {
107+
Deno.removeSignalListener("SIGTERM", interrupt.SIGTERM);
108+
}
102109
}
103-
}
104-
},
105-
*node() {
106-
// Annotate dynamic import so that webpack ignores it.
107-
// See https://webpack.js.org/api/module-methods/#webpackignore
108-
let { default: process } = yield* call(() =>
109-
import(/* webpackIgnore: true */ "node:process")
110-
);
111-
hardexit = (status) => process.exit(status);
112-
try {
113-
process.on("SIGINT", interrupt.SIGINT);
114-
if (process.platform !== "win32") {
115-
process.on("SIGTERM", interrupt.SIGTERM);
110+
},
111+
*node() {
112+
// Annotate dynamic import so that webpack ignores it.
113+
// See https://webpack.js.org/api/module-methods/#webpackignore
114+
let { default: process } = yield* call(() =>
115+
import(/* webpackIgnore: true */ "node:process")
116+
);
117+
hardexit = (status) => process.exit(status);
118+
try {
119+
process.on("SIGINT", interrupt.SIGINT);
120+
if (process.platform !== "win32") {
121+
process.on("SIGTERM", interrupt.SIGTERM);
122+
}
123+
yield* body(process.argv.slice(2));
124+
} finally {
125+
process.off("SIGINT", interrupt.SIGINT);
126+
if (process.platform !== "win32") {
127+
process.off("SIGTERM", interrupt.SIGINT);
128+
}
116129
}
117-
yield* body(process.argv.slice(2));
118-
} finally {
119-
process.off("SIGINT", interrupt.SIGINT);
120-
if (process.platform !== "win32") {
121-
process.off("SIGTERM", interrupt.SIGINT);
130+
},
131+
*browser() {
132+
try {
133+
self.addEventListener("unload", interrupt.SIGINT);
134+
yield* body([]);
135+
} finally {
136+
self.removeEventListener("unload", interrupt.SIGINT);
122137
}
123-
}
124-
},
125-
*browser() {
126-
try {
127-
self.addEventListener("unload", interrupt.SIGINT);
128-
yield* body([]);
129-
} finally {
130-
self.removeEventListener("unload", interrupt.SIGINT);
131-
}
132-
},
133-
});
134-
135-
yield* exit(0);
136-
} catch (error) {
137-
yield* resolve({ status: 1, error: error as Error });
138-
} finally {
139-
clearInterval(interval);
138+
},
139+
});
140+
141+
yield* exit(0);
142+
} catch (error) {
143+
yield* resolve({ status: 1, error: error as Error });
144+
} finally {
145+
clearInterval(interval);
146+
}
147+
})
148+
);
149+
150+
if (result.message) {
151+
if (result.status === 0) {
152+
console.log(result.message);
153+
} else {
154+
console.error(result.message);
140155
}
141-
})
142-
);
143-
144-
if (result.message) {
145-
if (result.status === 0) {
146-
console.log(result.message);
147-
} else {
148-
console.error(result.message);
149156
}
150-
}
151157

152-
if (result.error) {
153-
console.error(result.error);
154-
}
158+
if (result.error) {
159+
console.error(result.error);
160+
}
155161

156-
hardexit(result.status);
157-
}
162+
hardexit(result.status);
163+
},
164+
}, { at: "min" });
158165

159166
const ExitContext = createContext<(exit: Exit) => Operation<void>>("exit");
160167

0 commit comments

Comments
 (0)