Skip to content

Commit 9e6b35a

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 c1672b3 commit 9e6b35a

2 files changed

Lines changed: 106 additions & 89 deletions

File tree

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
@@ -60,103 +61,109 @@ export function* exit(status: number, message?: string): Operation<void> {
6061
* @since 3.0
6162
*/
6263

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

154-
if (result.error) {
155-
console.error(result.error);
156-
}
160+
if (result.error) {
161+
console.error(result.error);
162+
}
157163

158-
hardexit(result.status);
159-
}
164+
hardexit(result.status);
165+
},
166+
}, { at: "min" });
160167

161168
const ExitContext = createContext<(exit: Exit) => Operation<void>>("exit");
162169

0 commit comments

Comments
 (0)