@astroscope/boot 0.1.0 → 0.1.2
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 +3 -3
- package/dist/index.cjs +94 -19
- package/dist/index.d.cts +31 -2
- package/dist/index.d.ts +31 -2
- package/dist/index.js +94 -19
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @astroscope/boot
|
|
2
2
|
|
|
3
|
-
Run initialization and cleanup code
|
|
3
|
+
Startup and graceful shutdown hooks for Astro SSR. Run initialization code before the server starts and cleanup code when it shuts down.
|
|
4
4
|
|
|
5
5
|
## Examples
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ See the [demo/boot](../../demo/boot) directory for a working example.
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
|
|
12
|
+
npm install @astroscope/boot
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## Usage
|
|
@@ -83,7 +83,7 @@ boot({ entry: "src/startup.ts" });
|
|
|
83
83
|
|
|
84
84
|
### `hmr`
|
|
85
85
|
|
|
86
|
-
Re-run `onStartup` when the boot file changes during development. This is disabled by default to avoid side effects, because `onStartup` may perform operations that should only run once (e.g., database connections).
|
|
86
|
+
Re-run `onStartup` when the boot file changes during development. This is disabled by default to avoid side effects, because `onStartup` may perform operations that should only run once (e.g., database connections). Please ensure your `onShutdown` function destroys any resources created by `onStartup` to prevent leaks / unexpected behavior.
|
|
87
87
|
|
|
88
88
|
- **Type**: `boolean`
|
|
89
89
|
- **Default**: `false`
|
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,51 @@ __export(index_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(index_exports);
|
|
36
36
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
37
37
|
var import_node_path = __toESM(require("path"), 1);
|
|
38
|
+
|
|
39
|
+
// src/ignored.ts
|
|
40
|
+
var ignoredSuffixes = [
|
|
41
|
+
// type definitions
|
|
42
|
+
".d.ts",
|
|
43
|
+
".d.mts",
|
|
44
|
+
".d.cts",
|
|
45
|
+
// images
|
|
46
|
+
".png",
|
|
47
|
+
".jpg",
|
|
48
|
+
".jpeg",
|
|
49
|
+
".gif",
|
|
50
|
+
".svg",
|
|
51
|
+
".webp",
|
|
52
|
+
".avif",
|
|
53
|
+
".ico",
|
|
54
|
+
// fonts
|
|
55
|
+
".woff",
|
|
56
|
+
".woff2",
|
|
57
|
+
".ttf",
|
|
58
|
+
".otf",
|
|
59
|
+
".eot",
|
|
60
|
+
// other static assets
|
|
61
|
+
".pdf",
|
|
62
|
+
".mp3",
|
|
63
|
+
".mp4",
|
|
64
|
+
".webm",
|
|
65
|
+
".ogg",
|
|
66
|
+
".wav",
|
|
67
|
+
// data/config that boot typically doesn't import
|
|
68
|
+
".json",
|
|
69
|
+
".yaml",
|
|
70
|
+
".yml",
|
|
71
|
+
".toml",
|
|
72
|
+
".md",
|
|
73
|
+
".mdx",
|
|
74
|
+
".txt",
|
|
75
|
+
// styles (handled by Vite's CSS HMR)
|
|
76
|
+
".css",
|
|
77
|
+
".scss",
|
|
78
|
+
".sass",
|
|
79
|
+
".less"
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
// src/index.ts
|
|
38
83
|
function resolveEntry(entry) {
|
|
39
84
|
if (entry) return entry;
|
|
40
85
|
if (import_node_fs.default.existsSync("src/boot.ts")) return "src/boot.ts";
|
|
@@ -61,9 +106,7 @@ function boot(options = {}) {
|
|
|
61
106
|
if (isBuild) return;
|
|
62
107
|
server.httpServer?.once("listening", async () => {
|
|
63
108
|
try {
|
|
64
|
-
const module2 = await server.ssrLoadModule(
|
|
65
|
-
`/${entry}`
|
|
66
|
-
);
|
|
109
|
+
const module2 = await server.ssrLoadModule(`/${entry}`);
|
|
67
110
|
if (module2.onStartup) {
|
|
68
111
|
await module2.onStartup();
|
|
69
112
|
}
|
|
@@ -73,9 +116,7 @@ function boot(options = {}) {
|
|
|
73
116
|
});
|
|
74
117
|
server.httpServer?.once("close", async () => {
|
|
75
118
|
try {
|
|
76
|
-
const module2 = await server.ssrLoadModule(
|
|
77
|
-
`/${entry}`
|
|
78
|
-
);
|
|
119
|
+
const module2 = await server.ssrLoadModule(`/${entry}`);
|
|
79
120
|
if (module2.onShutdown) {
|
|
80
121
|
await module2.onShutdown();
|
|
81
122
|
}
|
|
@@ -84,19 +125,49 @@ function boot(options = {}) {
|
|
|
84
125
|
}
|
|
85
126
|
});
|
|
86
127
|
if (hmr) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
128
|
+
const bootModuleId = `/${entry}`;
|
|
129
|
+
const bootFilePath = import_node_path.default.resolve(server.config.root, entry);
|
|
130
|
+
const getBootDependencies = () => {
|
|
131
|
+
const deps = /* @__PURE__ */ new Set();
|
|
132
|
+
const bootModules = server.moduleGraph.getModulesByFile(bootFilePath);
|
|
133
|
+
const bootModule = bootModules ? [...bootModules][0] : void 0;
|
|
134
|
+
if (!bootModule) return deps;
|
|
135
|
+
const collectDeps = (mod, visited = /* @__PURE__ */ new Set()) => {
|
|
136
|
+
if (!mod?.file || visited.has(mod.file)) return;
|
|
137
|
+
visited.add(mod.file);
|
|
138
|
+
deps.add(mod.file);
|
|
139
|
+
for (const imported of mod.importedModules) {
|
|
140
|
+
collectDeps(imported, visited);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
collectDeps(bootModule);
|
|
144
|
+
return deps;
|
|
145
|
+
};
|
|
146
|
+
const rerunBoot = async (changedFile) => {
|
|
147
|
+
logger.info(`boot dependency changed: ${changedFile}, rerunning hooks...`);
|
|
90
148
|
try {
|
|
149
|
+
const oldModule = await server.ssrLoadModule(bootModuleId);
|
|
150
|
+
if (oldModule.onShutdown) {
|
|
151
|
+
await oldModule.onShutdown();
|
|
152
|
+
}
|
|
91
153
|
server.moduleGraph.invalidateAll();
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (module2.onStartup) {
|
|
96
|
-
await module2.onStartup();
|
|
154
|
+
const newModule = await server.ssrLoadModule(bootModuleId);
|
|
155
|
+
if (newModule.onStartup) {
|
|
156
|
+
await newModule.onStartup();
|
|
97
157
|
}
|
|
98
158
|
} catch (error) {
|
|
99
|
-
logger.error(`Error
|
|
159
|
+
logger.error(`Error during boot HMR: ${error}`);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
const shouldIgnore = (filePath) => {
|
|
163
|
+
const path2 = filePath.toLowerCase();
|
|
164
|
+
return ignoredSuffixes.some((suffix) => path2.endsWith(suffix));
|
|
165
|
+
};
|
|
166
|
+
server.watcher.on("change", async (changedPath) => {
|
|
167
|
+
if (shouldIgnore(changedPath)) return;
|
|
168
|
+
const bootDeps = getBootDependencies();
|
|
169
|
+
if (bootDeps.has(changedPath)) {
|
|
170
|
+
await rerunBoot(changedPath);
|
|
100
171
|
}
|
|
101
172
|
});
|
|
102
173
|
}
|
|
@@ -116,10 +187,14 @@ function boot(options = {}) {
|
|
|
116
187
|
}
|
|
117
188
|
},
|
|
118
189
|
writeBundle(outputOptions) {
|
|
190
|
+
if (!isSSR) return;
|
|
119
191
|
const outDir = outputOptions.dir;
|
|
120
192
|
if (!outDir || !bootChunkRef) return;
|
|
121
193
|
const entryPath = import_node_path.default.join(outDir, "entry.mjs");
|
|
122
|
-
if (!import_node_fs.default.existsSync(entryPath))
|
|
194
|
+
if (!import_node_fs.default.existsSync(entryPath)) {
|
|
195
|
+
logger.warn("entry.mjs not found - boot injection skipped");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
123
198
|
const bootChunkName = this.getFileName(bootChunkRef);
|
|
124
199
|
if (!bootChunkName) {
|
|
125
200
|
logger.warn("boot chunk not found");
|
|
@@ -132,9 +207,9 @@ function boot(options = {}) {
|
|
|
132
207
|
);
|
|
133
208
|
}
|
|
134
209
|
let content = import_node_fs.default.readFileSync(entryPath, "utf-8");
|
|
135
|
-
const bootImport = `import
|
|
136
|
-
await onStartup?.();
|
|
137
|
-
if (onShutdown) process.on('SIGTERM', async () => { await onShutdown(); process.exit(0); });
|
|
210
|
+
const bootImport = `import * as __boot from './${bootChunkName}';
|
|
211
|
+
await __boot.onStartup?.();
|
|
212
|
+
if (__boot.onShutdown) process.on('SIGTERM', async () => { await __boot.onShutdown(); process.exit(0); });
|
|
138
213
|
`;
|
|
139
214
|
content = bootImport + content;
|
|
140
215
|
import_node_fs.default.writeFileSync(entryPath, content);
|
package/dist/index.d.cts
CHANGED
|
@@ -5,13 +5,42 @@ interface BootOptions {
|
|
|
5
5
|
* Path to the boot file relative to the project root.
|
|
6
6
|
* @default "src/boot.ts"
|
|
7
7
|
*/
|
|
8
|
-
entry?: string;
|
|
8
|
+
entry?: string | undefined;
|
|
9
9
|
/**
|
|
10
10
|
* Enable HMR for the boot file. When true, `onStartup` will re-run when the boot file changes.
|
|
11
11
|
* @default false
|
|
12
12
|
*/
|
|
13
|
-
hmr?: boolean;
|
|
13
|
+
hmr?: boolean | undefined;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Astro integration for application lifecycle hooks.
|
|
17
|
+
*
|
|
18
|
+
* Runs `onStartup` and `onShutdown` functions exported from your boot file
|
|
19
|
+
* during server startup and shutdown.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* // astro.config.ts
|
|
24
|
+
* import { defineConfig } from "astro/config";
|
|
25
|
+
* import boot from "@astroscope/boot";
|
|
26
|
+
*
|
|
27
|
+
* export default defineConfig({
|
|
28
|
+
* integrations: [boot()],
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* // src/boot.ts
|
|
35
|
+
* export async function onStartup() {
|
|
36
|
+
* console.log("Server starting...");
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* export async function onShutdown() {
|
|
40
|
+
* console.log("Server shutting down...");
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
15
44
|
declare function boot(options?: BootOptions): AstroIntegration;
|
|
16
45
|
|
|
17
46
|
export { type BootOptions, boot as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,13 +5,42 @@ interface BootOptions {
|
|
|
5
5
|
* Path to the boot file relative to the project root.
|
|
6
6
|
* @default "src/boot.ts"
|
|
7
7
|
*/
|
|
8
|
-
entry?: string;
|
|
8
|
+
entry?: string | undefined;
|
|
9
9
|
/**
|
|
10
10
|
* Enable HMR for the boot file. When true, `onStartup` will re-run when the boot file changes.
|
|
11
11
|
* @default false
|
|
12
12
|
*/
|
|
13
|
-
hmr?: boolean;
|
|
13
|
+
hmr?: boolean | undefined;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Astro integration for application lifecycle hooks.
|
|
17
|
+
*
|
|
18
|
+
* Runs `onStartup` and `onShutdown` functions exported from your boot file
|
|
19
|
+
* during server startup and shutdown.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* // astro.config.ts
|
|
24
|
+
* import { defineConfig } from "astro/config";
|
|
25
|
+
* import boot from "@astroscope/boot";
|
|
26
|
+
*
|
|
27
|
+
* export default defineConfig({
|
|
28
|
+
* integrations: [boot()],
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* // src/boot.ts
|
|
35
|
+
* export async function onStartup() {
|
|
36
|
+
* console.log("Server starting...");
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* export async function onShutdown() {
|
|
40
|
+
* console.log("Server shutting down...");
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
15
44
|
declare function boot(options?: BootOptions): AstroIntegration;
|
|
16
45
|
|
|
17
46
|
export { type BootOptions, boot as default };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
+
|
|
5
|
+
// src/ignored.ts
|
|
6
|
+
var ignoredSuffixes = [
|
|
7
|
+
// type definitions
|
|
8
|
+
".d.ts",
|
|
9
|
+
".d.mts",
|
|
10
|
+
".d.cts",
|
|
11
|
+
// images
|
|
12
|
+
".png",
|
|
13
|
+
".jpg",
|
|
14
|
+
".jpeg",
|
|
15
|
+
".gif",
|
|
16
|
+
".svg",
|
|
17
|
+
".webp",
|
|
18
|
+
".avif",
|
|
19
|
+
".ico",
|
|
20
|
+
// fonts
|
|
21
|
+
".woff",
|
|
22
|
+
".woff2",
|
|
23
|
+
".ttf",
|
|
24
|
+
".otf",
|
|
25
|
+
".eot",
|
|
26
|
+
// other static assets
|
|
27
|
+
".pdf",
|
|
28
|
+
".mp3",
|
|
29
|
+
".mp4",
|
|
30
|
+
".webm",
|
|
31
|
+
".ogg",
|
|
32
|
+
".wav",
|
|
33
|
+
// data/config that boot typically doesn't import
|
|
34
|
+
".json",
|
|
35
|
+
".yaml",
|
|
36
|
+
".yml",
|
|
37
|
+
".toml",
|
|
38
|
+
".md",
|
|
39
|
+
".mdx",
|
|
40
|
+
".txt",
|
|
41
|
+
// styles (handled by Vite's CSS HMR)
|
|
42
|
+
".css",
|
|
43
|
+
".scss",
|
|
44
|
+
".sass",
|
|
45
|
+
".less"
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// src/index.ts
|
|
4
49
|
function resolveEntry(entry) {
|
|
5
50
|
if (entry) return entry;
|
|
6
51
|
if (fs.existsSync("src/boot.ts")) return "src/boot.ts";
|
|
@@ -27,9 +72,7 @@ function boot(options = {}) {
|
|
|
27
72
|
if (isBuild) return;
|
|
28
73
|
server.httpServer?.once("listening", async () => {
|
|
29
74
|
try {
|
|
30
|
-
const module = await server.ssrLoadModule(
|
|
31
|
-
`/${entry}`
|
|
32
|
-
);
|
|
75
|
+
const module = await server.ssrLoadModule(`/${entry}`);
|
|
33
76
|
if (module.onStartup) {
|
|
34
77
|
await module.onStartup();
|
|
35
78
|
}
|
|
@@ -39,9 +82,7 @@ function boot(options = {}) {
|
|
|
39
82
|
});
|
|
40
83
|
server.httpServer?.once("close", async () => {
|
|
41
84
|
try {
|
|
42
|
-
const module = await server.ssrLoadModule(
|
|
43
|
-
`/${entry}`
|
|
44
|
-
);
|
|
85
|
+
const module = await server.ssrLoadModule(`/${entry}`);
|
|
45
86
|
if (module.onShutdown) {
|
|
46
87
|
await module.onShutdown();
|
|
47
88
|
}
|
|
@@ -50,19 +91,49 @@ function boot(options = {}) {
|
|
|
50
91
|
}
|
|
51
92
|
});
|
|
52
93
|
if (hmr) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
94
|
+
const bootModuleId = `/${entry}`;
|
|
95
|
+
const bootFilePath = path.resolve(server.config.root, entry);
|
|
96
|
+
const getBootDependencies = () => {
|
|
97
|
+
const deps = /* @__PURE__ */ new Set();
|
|
98
|
+
const bootModules = server.moduleGraph.getModulesByFile(bootFilePath);
|
|
99
|
+
const bootModule = bootModules ? [...bootModules][0] : void 0;
|
|
100
|
+
if (!bootModule) return deps;
|
|
101
|
+
const collectDeps = (mod, visited = /* @__PURE__ */ new Set()) => {
|
|
102
|
+
if (!mod?.file || visited.has(mod.file)) return;
|
|
103
|
+
visited.add(mod.file);
|
|
104
|
+
deps.add(mod.file);
|
|
105
|
+
for (const imported of mod.importedModules) {
|
|
106
|
+
collectDeps(imported, visited);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
collectDeps(bootModule);
|
|
110
|
+
return deps;
|
|
111
|
+
};
|
|
112
|
+
const rerunBoot = async (changedFile) => {
|
|
113
|
+
logger.info(`boot dependency changed: ${changedFile}, rerunning hooks...`);
|
|
56
114
|
try {
|
|
115
|
+
const oldModule = await server.ssrLoadModule(bootModuleId);
|
|
116
|
+
if (oldModule.onShutdown) {
|
|
117
|
+
await oldModule.onShutdown();
|
|
118
|
+
}
|
|
57
119
|
server.moduleGraph.invalidateAll();
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (module.onStartup) {
|
|
62
|
-
await module.onStartup();
|
|
120
|
+
const newModule = await server.ssrLoadModule(bootModuleId);
|
|
121
|
+
if (newModule.onStartup) {
|
|
122
|
+
await newModule.onStartup();
|
|
63
123
|
}
|
|
64
124
|
} catch (error) {
|
|
65
|
-
logger.error(`Error
|
|
125
|
+
logger.error(`Error during boot HMR: ${error}`);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const shouldIgnore = (filePath) => {
|
|
129
|
+
const path2 = filePath.toLowerCase();
|
|
130
|
+
return ignoredSuffixes.some((suffix) => path2.endsWith(suffix));
|
|
131
|
+
};
|
|
132
|
+
server.watcher.on("change", async (changedPath) => {
|
|
133
|
+
if (shouldIgnore(changedPath)) return;
|
|
134
|
+
const bootDeps = getBootDependencies();
|
|
135
|
+
if (bootDeps.has(changedPath)) {
|
|
136
|
+
await rerunBoot(changedPath);
|
|
66
137
|
}
|
|
67
138
|
});
|
|
68
139
|
}
|
|
@@ -82,10 +153,14 @@ function boot(options = {}) {
|
|
|
82
153
|
}
|
|
83
154
|
},
|
|
84
155
|
writeBundle(outputOptions) {
|
|
156
|
+
if (!isSSR) return;
|
|
85
157
|
const outDir = outputOptions.dir;
|
|
86
158
|
if (!outDir || !bootChunkRef) return;
|
|
87
159
|
const entryPath = path.join(outDir, "entry.mjs");
|
|
88
|
-
if (!fs.existsSync(entryPath))
|
|
160
|
+
if (!fs.existsSync(entryPath)) {
|
|
161
|
+
logger.warn("entry.mjs not found - boot injection skipped");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
89
164
|
const bootChunkName = this.getFileName(bootChunkRef);
|
|
90
165
|
if (!bootChunkName) {
|
|
91
166
|
logger.warn("boot chunk not found");
|
|
@@ -98,9 +173,9 @@ function boot(options = {}) {
|
|
|
98
173
|
);
|
|
99
174
|
}
|
|
100
175
|
let content = fs.readFileSync(entryPath, "utf-8");
|
|
101
|
-
const bootImport = `import
|
|
102
|
-
await onStartup?.();
|
|
103
|
-
if (onShutdown) process.on('SIGTERM', async () => { await onShutdown(); process.exit(0); });
|
|
176
|
+
const bootImport = `import * as __boot from './${bootChunkName}';
|
|
177
|
+
await __boot.onStartup?.();
|
|
178
|
+
if (__boot.onShutdown) process.on('SIGTERM', async () => { await __boot.onShutdown(); process.exit(0); });
|
|
104
179
|
`;
|
|
105
180
|
content = bootImport + content;
|
|
106
181
|
fs.writeFileSync(entryPath, content);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astroscope/boot",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Startup and graceful shutdown hooks for Astro SSR",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"lint:fix": "eslint 'src/**/*.{ts,tsx}' --fix"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"astro": "^5.
|
|
46
|
+
"astro": "^5.16.9",
|
|
47
47
|
"tsup": "^8.5.1",
|
|
48
48
|
"typescript": "^5.9.3"
|
|
49
49
|
},
|