@astroscope/boot 0.2.2 → 0.3.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.
@@ -0,0 +1,36 @@
1
+ // src/events.ts
2
+ var STORE_KEY = /* @__PURE__ */ Symbol.for("@astroscope/boot/events");
3
+ function getStore() {
4
+ const existing = globalThis[STORE_KEY];
5
+ if (existing) return existing;
6
+ const store = { listeners: /* @__PURE__ */ new Map() };
7
+ globalThis[STORE_KEY] = store;
8
+ return store;
9
+ }
10
+ function on(event, handler) {
11
+ const store = getStore();
12
+ let handlers = store.listeners.get(event);
13
+ if (!handlers) {
14
+ handlers = /* @__PURE__ */ new Set();
15
+ store.listeners.set(event, handlers);
16
+ }
17
+ handlers.add(handler);
18
+ }
19
+ function off(event, handler) {
20
+ const store = getStore();
21
+ store.listeners.get(event)?.delete(handler);
22
+ }
23
+ async function emit(event, context) {
24
+ const store = getStore();
25
+ const handlers = store.listeners.get(event);
26
+ if (!handlers) return;
27
+ for (const handler of handlers) {
28
+ await handler(context);
29
+ }
30
+ }
31
+
32
+ export {
33
+ on,
34
+ off,
35
+ emit
36
+ };
@@ -0,0 +1,23 @@
1
+ import {
2
+ emit
3
+ } from "./chunk-I62ZQYTP.js";
4
+
5
+ // src/lifecycle.ts
6
+ async function runStartup(boot, context) {
7
+ await emit("beforeOnStartup", context);
8
+ await boot.onStartup?.(context);
9
+ await emit("afterOnStartup", context);
10
+ }
11
+ async function runShutdown(boot, context) {
12
+ try {
13
+ await emit("beforeOnShutdown", context);
14
+ await boot.onShutdown?.(context);
15
+ } finally {
16
+ await emit("afterOnShutdown", context);
17
+ }
18
+ }
19
+
20
+ export {
21
+ runStartup,
22
+ runShutdown
23
+ };
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/events.ts
21
+ var events_exports = {};
22
+ __export(events_exports, {
23
+ emit: () => emit,
24
+ off: () => off,
25
+ on: () => on
26
+ });
27
+ module.exports = __toCommonJS(events_exports);
28
+ var STORE_KEY = /* @__PURE__ */ Symbol.for("@astroscope/boot/events");
29
+ function getStore() {
30
+ const existing = globalThis[STORE_KEY];
31
+ if (existing) return existing;
32
+ const store = { listeners: /* @__PURE__ */ new Map() };
33
+ globalThis[STORE_KEY] = store;
34
+ return store;
35
+ }
36
+ function on(event, handler) {
37
+ const store = getStore();
38
+ let handlers = store.listeners.get(event);
39
+ if (!handlers) {
40
+ handlers = /* @__PURE__ */ new Set();
41
+ store.listeners.set(event, handlers);
42
+ }
43
+ handlers.add(handler);
44
+ }
45
+ function off(event, handler) {
46
+ const store = getStore();
47
+ store.listeners.get(event)?.delete(handler);
48
+ }
49
+ async function emit(event, context) {
50
+ const store = getStore();
51
+ const handlers = store.listeners.get(event);
52
+ if (!handlers) return;
53
+ for (const handler of handlers) {
54
+ await handler(context);
55
+ }
56
+ }
57
+ // Annotate the CommonJS export names for ESM import in node:
58
+ 0 && (module.exports = {
59
+ emit,
60
+ off,
61
+ on
62
+ });
@@ -0,0 +1,18 @@
1
+ import { B as BootContext } from './types-CxpusND2.cjs';
2
+
3
+ type BootEventName = 'beforeOnStartup' | 'afterOnStartup' | 'beforeOnShutdown' | 'afterOnShutdown';
4
+ type BootEventHandler = (context: BootContext) => Promise<void> | void;
5
+ /**
6
+ * Register a handler for a boot lifecycle event.
7
+ */
8
+ declare function on(event: BootEventName, handler: BootEventHandler): void;
9
+ /**
10
+ * Remove a previously registered handler.
11
+ */
12
+ declare function off(event: BootEventName, handler: BootEventHandler): void;
13
+ /**
14
+ * Emit a boot lifecycle event, running all registered handlers sequentially.
15
+ */
16
+ declare function emit(event: BootEventName, context: BootContext): Promise<void>;
17
+
18
+ export { type BootEventHandler, type BootEventName, emit, off, on };
@@ -0,0 +1,18 @@
1
+ import { B as BootContext } from './types-CxpusND2.js';
2
+
3
+ type BootEventName = 'beforeOnStartup' | 'afterOnStartup' | 'beforeOnShutdown' | 'afterOnShutdown';
4
+ type BootEventHandler = (context: BootContext) => Promise<void> | void;
5
+ /**
6
+ * Register a handler for a boot lifecycle event.
7
+ */
8
+ declare function on(event: BootEventName, handler: BootEventHandler): void;
9
+ /**
10
+ * Remove a previously registered handler.
11
+ */
12
+ declare function off(event: BootEventName, handler: BootEventHandler): void;
13
+ /**
14
+ * Emit a boot lifecycle event, running all registered handlers sequentially.
15
+ */
16
+ declare function emit(event: BootEventName, context: BootContext): Promise<void>;
17
+
18
+ export { type BootEventHandler, type BootEventName, emit, off, on };
package/dist/events.js ADDED
@@ -0,0 +1,10 @@
1
+ import {
2
+ emit,
3
+ off,
4
+ on
5
+ } from "./chunk-I62ZQYTP.js";
6
+ export {
7
+ emit,
8
+ off,
9
+ on
10
+ };
package/dist/index.cjs CHANGED
@@ -36,10 +36,12 @@ __export(index_exports, {
36
36
  module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/integration.ts
39
- var import_node_fs = __toESM(require("fs"), 1);
40
- var import_node_path = __toESM(require("path"), 1);
39
+ var import_node_fs2 = __toESM(require("fs"), 1);
41
40
  var import_magic_string = __toESM(require("magic-string"), 1);
42
41
 
42
+ // src/hmr.ts
43
+ var import_node_path = __toESM(require("path"), 1);
44
+
43
45
  // src/ignored.ts
44
46
  var ignoredSuffixes = [
45
47
  // type definitions
@@ -83,13 +85,125 @@ var ignoredSuffixes = [
83
85
  ".less"
84
86
  ];
85
87
 
88
+ // src/events.ts
89
+ var STORE_KEY = /* @__PURE__ */ Symbol.for("@astroscope/boot/events");
90
+ function getStore() {
91
+ const existing = globalThis[STORE_KEY];
92
+ if (existing) return existing;
93
+ const store = { listeners: /* @__PURE__ */ new Map() };
94
+ globalThis[STORE_KEY] = store;
95
+ return store;
96
+ }
97
+ async function emit(event, context) {
98
+ const store = getStore();
99
+ const handlers = store.listeners.get(event);
100
+ if (!handlers) return;
101
+ for (const handler of handlers) {
102
+ await handler(context);
103
+ }
104
+ }
105
+
106
+ // src/lifecycle.ts
107
+ async function runStartup(boot2, context) {
108
+ await emit("beforeOnStartup", context);
109
+ await boot2.onStartup?.(context);
110
+ await emit("afterOnStartup", context);
111
+ }
112
+ async function runShutdown(boot2, context) {
113
+ try {
114
+ await emit("beforeOnShutdown", context);
115
+ await boot2.onShutdown?.(context);
116
+ } finally {
117
+ await emit("afterOnShutdown", context);
118
+ }
119
+ }
120
+
121
+ // src/hmr.ts
122
+ function setupBootHmr(server, entry, logger, getBootContext) {
123
+ const bootModuleId = `/${entry}`;
124
+ const bootFilePath = import_node_path.default.resolve(server.config.root, entry);
125
+ const getBootDependencies = () => {
126
+ const deps = /* @__PURE__ */ new Set();
127
+ const bootModules = server.moduleGraph.getModulesByFile(bootFilePath);
128
+ const bootModule = bootModules ? [...bootModules][0] : void 0;
129
+ if (!bootModule) return deps;
130
+ const collectDeps = (mod, visited = /* @__PURE__ */ new Set()) => {
131
+ if (!mod?.file || visited.has(mod.file)) return;
132
+ visited.add(mod.file);
133
+ deps.add(mod.file);
134
+ for (const imported of mod.importedModules) {
135
+ collectDeps(imported, visited);
136
+ }
137
+ };
138
+ collectDeps(bootModule);
139
+ return deps;
140
+ };
141
+ const shouldIgnore = (filePath) => {
142
+ const p = filePath.toLowerCase();
143
+ return ignoredSuffixes.some((suffix) => p.endsWith(suffix));
144
+ };
145
+ server.watcher.on("change", async (changedPath) => {
146
+ if (shouldIgnore(changedPath)) return;
147
+ const bootDeps = getBootDependencies();
148
+ if (bootDeps.has(changedPath)) {
149
+ logger.info(`boot dependency changed: ${changedPath}, rerunning hooks...`);
150
+ const bootContext = getBootContext();
151
+ try {
152
+ const oldModule = await server.ssrLoadModule(bootModuleId);
153
+ await runShutdown(oldModule, bootContext);
154
+ } catch (error) {
155
+ logger.error(`Error during boot HMR shutdown: ${error}`);
156
+ }
157
+ server.moduleGraph.invalidateAll();
158
+ try {
159
+ const newModule = await server.ssrLoadModule(bootModuleId);
160
+ await runStartup(newModule, bootContext);
161
+ } catch (error) {
162
+ logger.error(`Error during boot HMR startup: ${error}`);
163
+ }
164
+ }
165
+ });
166
+ }
167
+
168
+ // src/warmup-manifest.ts
169
+ var import_node_fs = __toESM(require("fs"), 1);
170
+ var import_node_path2 = __toESM(require("path"), 1);
171
+ var WARMUP_MANIFEST_FILE = "warmup-manifest.json";
172
+ function collectWarmupModules(bundle) {
173
+ const pageModules = [];
174
+ let middlewarePath = null;
175
+ for (const [fileName, chunk] of Object.entries(bundle)) {
176
+ if (chunk.type !== "chunk") continue;
177
+ if (fileName.startsWith("pages/") && fileName.endsWith(".mjs")) {
178
+ pageModules.push(fileName);
179
+ }
180
+ if (fileName.includes("_astro-internal_middleware") || fileName.includes("_noop-middleware")) {
181
+ middlewarePath = fileName;
182
+ }
183
+ }
184
+ return { pageModules, middlewarePath };
185
+ }
186
+ function writeWarmupManifest(outDir, { pageModules, middlewarePath }, logger) {
187
+ const modules = [];
188
+ if (middlewarePath) {
189
+ modules.push(`./${middlewarePath}`);
190
+ }
191
+ for (const page of pageModules) {
192
+ modules.push(`./${page}`);
193
+ }
194
+ const manifestPath = import_node_path2.default.join(outDir, "chunks", WARMUP_MANIFEST_FILE);
195
+ import_node_fs.default.writeFileSync(manifestPath, JSON.stringify({ modules }));
196
+ logger.info(`generated warmup for ${pageModules.length} pages`);
197
+ }
198
+
86
199
  // src/integration.ts
87
200
  function resolveEntry(entry) {
88
201
  if (entry) return entry;
89
- if (import_node_fs.default.existsSync("src/boot/index.ts")) return "src/boot/index.ts";
202
+ if (import_node_fs2.default.existsSync("src/boot/index.ts")) return "src/boot/index.ts";
90
203
  return "src/boot.ts";
91
204
  }
92
- var WARMUP_MANIFEST_FILE = "warmup-manifest.json";
205
+ var VIRTUAL_MODULE_ID = "virtual:@astroscope/boot/config";
206
+ var RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
93
207
  function getServerDefaults(config) {
94
208
  return {
95
209
  host: typeof config?.server?.host === "string" ? config.server.host : config?.server?.host === true ? "0.0.0.0" : "localhost",
@@ -103,8 +217,7 @@ function boot(options = {}) {
103
217
  let isSSR = false;
104
218
  let bootChunkRef = null;
105
219
  let astroConfig = null;
106
- let pageModules = [];
107
- let middlewarePath = null;
220
+ let warmupModules = null;
108
221
  return {
109
222
  name: "@astroscope/boot",
110
223
  hooks: {
@@ -117,7 +230,16 @@ function boot(options = {}) {
117
230
  {
118
231
  name: "@astroscope/boot",
119
232
  enforce: "pre",
120
- configureServer(server) {
233
+ resolveId(id) {
234
+ if (id === VIRTUAL_MODULE_ID) return RESOLVED_VIRTUAL_MODULE_ID;
235
+ },
236
+ load(id) {
237
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) {
238
+ const { host, port } = getServerDefaults(astroConfig);
239
+ return `export const config = ${JSON.stringify({ host, port })};`;
240
+ }
241
+ },
242
+ async configureServer(server) {
121
243
  if (isBuild) return;
122
244
  const getBootContext = () => {
123
245
  const addr = server.httpServer?.address();
@@ -125,72 +247,27 @@ function boot(options = {}) {
125
247
  const host2 = addr.address === "::" || addr.address === "0.0.0.0" ? "localhost" : addr.address;
126
248
  return { dev: true, host: host2, port: addr.port };
127
249
  }
128
- const defaults = getServerDefaults(astroConfig);
129
- const host = process.env["HOST"] ?? defaults.host;
130
- const port = process.env["PORT"] ? Number(process.env["PORT"]) : defaults.port;
250
+ const { host, port } = getServerDefaults(astroConfig);
131
251
  return { dev: true, host, port };
132
252
  };
133
- server.httpServer?.once("listening", async () => {
134
- try {
135
- const bootContext = getBootContext();
136
- const module2 = await server.ssrLoadModule(`/${entry}`);
137
- await module2.onStartup?.(bootContext);
138
- } catch (error) {
139
- logger.error(`Error running startup script: ${error}`);
140
- }
141
- });
253
+ try {
254
+ const bootContext = getBootContext();
255
+ const module2 = await server.ssrLoadModule(`/${entry}`);
256
+ await runStartup(module2, bootContext);
257
+ } catch (error) {
258
+ logger.error(`Error running startup script: ${error}`);
259
+ }
142
260
  server.httpServer?.once("close", async () => {
143
261
  try {
144
262
  const bootContext = getBootContext();
145
263
  const module2 = await server.ssrLoadModule(`/${entry}`);
146
- await module2.onShutdown?.(bootContext);
264
+ await runShutdown(module2, bootContext);
147
265
  } catch (error) {
148
266
  logger.error(`Error running shutdown script: ${error}`);
149
267
  }
150
268
  });
151
269
  if (hmr) {
152
- const bootModuleId = `/${entry}`;
153
- const bootFilePath = import_node_path.default.resolve(server.config.root, entry);
154
- const getBootDependencies = () => {
155
- const deps = /* @__PURE__ */ new Set();
156
- const bootModules = server.moduleGraph.getModulesByFile(bootFilePath);
157
- const bootModule = bootModules ? [...bootModules][0] : void 0;
158
- if (!bootModule) return deps;
159
- const collectDeps = (mod, visited = /* @__PURE__ */ new Set()) => {
160
- if (!mod?.file || visited.has(mod.file)) return;
161
- visited.add(mod.file);
162
- deps.add(mod.file);
163
- for (const imported of mod.importedModules) {
164
- collectDeps(imported, visited);
165
- }
166
- };
167
- collectDeps(bootModule);
168
- return deps;
169
- };
170
- const rerunBoot = async (changedFile) => {
171
- logger.info(`boot dependency changed: ${changedFile}, rerunning hooks...`);
172
- try {
173
- const bootContext = getBootContext();
174
- const oldModule = await server.ssrLoadModule(bootModuleId);
175
- await oldModule.onShutdown?.(bootContext);
176
- server.moduleGraph.invalidateAll();
177
- const newModule = await server.ssrLoadModule(bootModuleId);
178
- await newModule.onStartup?.(bootContext);
179
- } catch (error) {
180
- logger.error(`Error during boot HMR: ${error}`);
181
- }
182
- };
183
- const shouldIgnore = (filePath) => {
184
- const p = filePath.toLowerCase();
185
- return ignoredSuffixes.some((suffix) => p.endsWith(suffix));
186
- };
187
- server.watcher.on("change", async (changedPath) => {
188
- if (shouldIgnore(changedPath)) return;
189
- const bootDeps = getBootDependencies();
190
- if (bootDeps.has(changedPath)) {
191
- await rerunBoot(changedPath);
192
- }
193
- });
270
+ setupBootHmr(server, entry, logger, getBootContext);
194
271
  }
195
272
  },
196
273
  configResolved(config2) {
@@ -215,39 +292,11 @@ function boot(options = {}) {
215
292
  logger.warn("entry.mjs not found - boot injection skipped");
216
293
  return;
217
294
  }
218
- pageModules = [];
219
- middlewarePath = null;
220
- for (const [fileName, chunk] of Object.entries(bundle)) {
221
- if (chunk.type !== "chunk") continue;
222
- if (fileName.startsWith("pages/") && fileName.endsWith(".mjs")) {
223
- pageModules.push(fileName);
224
- }
225
- if (fileName.includes("_astro-internal_middleware") || fileName.includes("_noop-middleware")) {
226
- middlewarePath = fileName;
227
- }
228
- }
229
- const { host, port } = getServerDefaults(astroConfig);
295
+ warmupModules = collectWarmupModules(bundle);
230
296
  const bootImport = `globalThis.__astroscope_server_url = import.meta.url;
231
- import * as __boot from './${bootChunkName}';
232
- const __bootContext = { dev: false, host: process.env.HOST ?? '${host}', port: process.env.PORT ? Number(process.env.PORT) : ${port} };
233
- try {
234
- await __boot.onStartup?.(__bootContext);
235
- } catch (err) {
236
- console.error('[boot] startup failed:', err);
237
- try { await __boot.onShutdown?.(__bootContext); } catch {}
238
- process.exit(1);
239
- }
240
- if (__boot.onShutdown) {
241
- process.on('SIGTERM', async () => {
242
- try {
243
- await __boot.onShutdown(__bootContext);
244
- process.exit(0);
245
- } catch (err) {
246
- console.error('[boot] shutdown failed:', err);
247
- process.exit(1);
248
- }
249
- });
250
- }
297
+ import * as __astroscope_boot from './${bootChunkName}';
298
+ import { setup as __astroscope_bootSetup } from '@astroscope/boot/setup';
299
+ await __astroscope_bootSetup(__astroscope_boot);
251
300
  `;
252
301
  const s = new import_magic_string.default(entryChunk.code);
253
302
  s.prepend(bootImport);
@@ -258,19 +307,10 @@ if (__boot.onShutdown) {
258
307
  logger.info(`injected ${bootChunkName} into entry.mjs`);
259
308
  },
260
309
  writeBundle(outputOptions) {
261
- if (!isSSR) return;
310
+ if (!isSSR || !warmupModules) return;
262
311
  const outDir = outputOptions.dir;
263
312
  if (!outDir) return;
264
- const modules = [];
265
- if (middlewarePath) {
266
- modules.push(`./${middlewarePath}`);
267
- }
268
- for (const page of pageModules) {
269
- modules.push(`./${page}`);
270
- }
271
- const manifestPath = import_node_path.default.join(outDir, "chunks", WARMUP_MANIFEST_FILE);
272
- import_node_fs.default.writeFileSync(manifestPath, JSON.stringify({ modules }));
273
- logger.info(`generated warmup for ${pageModules.length} pages`);
313
+ writeWarmupManifest(outDir, warmupModules, logger);
274
314
  }
275
315
  }
276
316
  ]
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { AstroIntegration } from 'astro';
2
2
  export { B as BootContext, W as WarmupResult } from './types-CxpusND2.cjs';
3
+ export { BootEventHandler, BootEventName } from './events.cjs';
3
4
 
4
5
  interface BootOptions {
5
6
  /**
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { AstroIntegration } from 'astro';
2
2
  export { B as BootContext, W as WarmupResult } from './types-CxpusND2.js';
3
+ export { BootEventHandler, BootEventName } from './events.js';
3
4
 
4
5
  interface BootOptions {
5
6
  /**
package/dist/index.js CHANGED
@@ -1,8 +1,16 @@
1
+ import {
2
+ runShutdown,
3
+ runStartup
4
+ } from "./chunk-LUFQ5I47.js";
5
+ import "./chunk-I62ZQYTP.js";
6
+
1
7
  // src/integration.ts
2
- import fs from "fs";
3
- import path from "path";
8
+ import fs2 from "fs";
4
9
  import MagicString from "magic-string";
5
10
 
11
+ // src/hmr.ts
12
+ import path from "path";
13
+
6
14
  // src/ignored.ts
7
15
  var ignoredSuffixes = [
8
16
  // type definitions
@@ -46,13 +54,92 @@ var ignoredSuffixes = [
46
54
  ".less"
47
55
  ];
48
56
 
57
+ // src/hmr.ts
58
+ function setupBootHmr(server, entry, logger, getBootContext) {
59
+ const bootModuleId = `/${entry}`;
60
+ const bootFilePath = path.resolve(server.config.root, entry);
61
+ const getBootDependencies = () => {
62
+ const deps = /* @__PURE__ */ new Set();
63
+ const bootModules = server.moduleGraph.getModulesByFile(bootFilePath);
64
+ const bootModule = bootModules ? [...bootModules][0] : void 0;
65
+ if (!bootModule) return deps;
66
+ const collectDeps = (mod, visited = /* @__PURE__ */ new Set()) => {
67
+ if (!mod?.file || visited.has(mod.file)) return;
68
+ visited.add(mod.file);
69
+ deps.add(mod.file);
70
+ for (const imported of mod.importedModules) {
71
+ collectDeps(imported, visited);
72
+ }
73
+ };
74
+ collectDeps(bootModule);
75
+ return deps;
76
+ };
77
+ const shouldIgnore = (filePath) => {
78
+ const p = filePath.toLowerCase();
79
+ return ignoredSuffixes.some((suffix) => p.endsWith(suffix));
80
+ };
81
+ server.watcher.on("change", async (changedPath) => {
82
+ if (shouldIgnore(changedPath)) return;
83
+ const bootDeps = getBootDependencies();
84
+ if (bootDeps.has(changedPath)) {
85
+ logger.info(`boot dependency changed: ${changedPath}, rerunning hooks...`);
86
+ const bootContext = getBootContext();
87
+ try {
88
+ const oldModule = await server.ssrLoadModule(bootModuleId);
89
+ await runShutdown(oldModule, bootContext);
90
+ } catch (error) {
91
+ logger.error(`Error during boot HMR shutdown: ${error}`);
92
+ }
93
+ server.moduleGraph.invalidateAll();
94
+ try {
95
+ const newModule = await server.ssrLoadModule(bootModuleId);
96
+ await runStartup(newModule, bootContext);
97
+ } catch (error) {
98
+ logger.error(`Error during boot HMR startup: ${error}`);
99
+ }
100
+ }
101
+ });
102
+ }
103
+
104
+ // src/warmup-manifest.ts
105
+ import fs from "fs";
106
+ import path2 from "path";
107
+ var WARMUP_MANIFEST_FILE = "warmup-manifest.json";
108
+ function collectWarmupModules(bundle) {
109
+ const pageModules = [];
110
+ let middlewarePath = null;
111
+ for (const [fileName, chunk] of Object.entries(bundle)) {
112
+ if (chunk.type !== "chunk") continue;
113
+ if (fileName.startsWith("pages/") && fileName.endsWith(".mjs")) {
114
+ pageModules.push(fileName);
115
+ }
116
+ if (fileName.includes("_astro-internal_middleware") || fileName.includes("_noop-middleware")) {
117
+ middlewarePath = fileName;
118
+ }
119
+ }
120
+ return { pageModules, middlewarePath };
121
+ }
122
+ function writeWarmupManifest(outDir, { pageModules, middlewarePath }, logger) {
123
+ const modules = [];
124
+ if (middlewarePath) {
125
+ modules.push(`./${middlewarePath}`);
126
+ }
127
+ for (const page of pageModules) {
128
+ modules.push(`./${page}`);
129
+ }
130
+ const manifestPath = path2.join(outDir, "chunks", WARMUP_MANIFEST_FILE);
131
+ fs.writeFileSync(manifestPath, JSON.stringify({ modules }));
132
+ logger.info(`generated warmup for ${pageModules.length} pages`);
133
+ }
134
+
49
135
  // src/integration.ts
50
136
  function resolveEntry(entry) {
51
137
  if (entry) return entry;
52
- if (fs.existsSync("src/boot/index.ts")) return "src/boot/index.ts";
138
+ if (fs2.existsSync("src/boot/index.ts")) return "src/boot/index.ts";
53
139
  return "src/boot.ts";
54
140
  }
55
- var WARMUP_MANIFEST_FILE = "warmup-manifest.json";
141
+ var VIRTUAL_MODULE_ID = "virtual:@astroscope/boot/config";
142
+ var RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
56
143
  function getServerDefaults(config) {
57
144
  return {
58
145
  host: typeof config?.server?.host === "string" ? config.server.host : config?.server?.host === true ? "0.0.0.0" : "localhost",
@@ -66,8 +153,7 @@ function boot(options = {}) {
66
153
  let isSSR = false;
67
154
  let bootChunkRef = null;
68
155
  let astroConfig = null;
69
- let pageModules = [];
70
- let middlewarePath = null;
156
+ let warmupModules = null;
71
157
  return {
72
158
  name: "@astroscope/boot",
73
159
  hooks: {
@@ -80,7 +166,16 @@ function boot(options = {}) {
80
166
  {
81
167
  name: "@astroscope/boot",
82
168
  enforce: "pre",
83
- configureServer(server) {
169
+ resolveId(id) {
170
+ if (id === VIRTUAL_MODULE_ID) return RESOLVED_VIRTUAL_MODULE_ID;
171
+ },
172
+ load(id) {
173
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) {
174
+ const { host, port } = getServerDefaults(astroConfig);
175
+ return `export const config = ${JSON.stringify({ host, port })};`;
176
+ }
177
+ },
178
+ async configureServer(server) {
84
179
  if (isBuild) return;
85
180
  const getBootContext = () => {
86
181
  const addr = server.httpServer?.address();
@@ -88,72 +183,27 @@ function boot(options = {}) {
88
183
  const host2 = addr.address === "::" || addr.address === "0.0.0.0" ? "localhost" : addr.address;
89
184
  return { dev: true, host: host2, port: addr.port };
90
185
  }
91
- const defaults = getServerDefaults(astroConfig);
92
- const host = process.env["HOST"] ?? defaults.host;
93
- const port = process.env["PORT"] ? Number(process.env["PORT"]) : defaults.port;
186
+ const { host, port } = getServerDefaults(astroConfig);
94
187
  return { dev: true, host, port };
95
188
  };
96
- server.httpServer?.once("listening", async () => {
97
- try {
98
- const bootContext = getBootContext();
99
- const module = await server.ssrLoadModule(`/${entry}`);
100
- await module.onStartup?.(bootContext);
101
- } catch (error) {
102
- logger.error(`Error running startup script: ${error}`);
103
- }
104
- });
189
+ try {
190
+ const bootContext = getBootContext();
191
+ const module = await server.ssrLoadModule(`/${entry}`);
192
+ await runStartup(module, bootContext);
193
+ } catch (error) {
194
+ logger.error(`Error running startup script: ${error}`);
195
+ }
105
196
  server.httpServer?.once("close", async () => {
106
197
  try {
107
198
  const bootContext = getBootContext();
108
199
  const module = await server.ssrLoadModule(`/${entry}`);
109
- await module.onShutdown?.(bootContext);
200
+ await runShutdown(module, bootContext);
110
201
  } catch (error) {
111
202
  logger.error(`Error running shutdown script: ${error}`);
112
203
  }
113
204
  });
114
205
  if (hmr) {
115
- const bootModuleId = `/${entry}`;
116
- const bootFilePath = path.resolve(server.config.root, entry);
117
- const getBootDependencies = () => {
118
- const deps = /* @__PURE__ */ new Set();
119
- const bootModules = server.moduleGraph.getModulesByFile(bootFilePath);
120
- const bootModule = bootModules ? [...bootModules][0] : void 0;
121
- if (!bootModule) return deps;
122
- const collectDeps = (mod, visited = /* @__PURE__ */ new Set()) => {
123
- if (!mod?.file || visited.has(mod.file)) return;
124
- visited.add(mod.file);
125
- deps.add(mod.file);
126
- for (const imported of mod.importedModules) {
127
- collectDeps(imported, visited);
128
- }
129
- };
130
- collectDeps(bootModule);
131
- return deps;
132
- };
133
- const rerunBoot = async (changedFile) => {
134
- logger.info(`boot dependency changed: ${changedFile}, rerunning hooks...`);
135
- try {
136
- const bootContext = getBootContext();
137
- const oldModule = await server.ssrLoadModule(bootModuleId);
138
- await oldModule.onShutdown?.(bootContext);
139
- server.moduleGraph.invalidateAll();
140
- const newModule = await server.ssrLoadModule(bootModuleId);
141
- await newModule.onStartup?.(bootContext);
142
- } catch (error) {
143
- logger.error(`Error during boot HMR: ${error}`);
144
- }
145
- };
146
- const shouldIgnore = (filePath) => {
147
- const p = filePath.toLowerCase();
148
- return ignoredSuffixes.some((suffix) => p.endsWith(suffix));
149
- };
150
- server.watcher.on("change", async (changedPath) => {
151
- if (shouldIgnore(changedPath)) return;
152
- const bootDeps = getBootDependencies();
153
- if (bootDeps.has(changedPath)) {
154
- await rerunBoot(changedPath);
155
- }
156
- });
206
+ setupBootHmr(server, entry, logger, getBootContext);
157
207
  }
158
208
  },
159
209
  configResolved(config2) {
@@ -178,39 +228,11 @@ function boot(options = {}) {
178
228
  logger.warn("entry.mjs not found - boot injection skipped");
179
229
  return;
180
230
  }
181
- pageModules = [];
182
- middlewarePath = null;
183
- for (const [fileName, chunk] of Object.entries(bundle)) {
184
- if (chunk.type !== "chunk") continue;
185
- if (fileName.startsWith("pages/") && fileName.endsWith(".mjs")) {
186
- pageModules.push(fileName);
187
- }
188
- if (fileName.includes("_astro-internal_middleware") || fileName.includes("_noop-middleware")) {
189
- middlewarePath = fileName;
190
- }
191
- }
192
- const { host, port } = getServerDefaults(astroConfig);
231
+ warmupModules = collectWarmupModules(bundle);
193
232
  const bootImport = `globalThis.__astroscope_server_url = import.meta.url;
194
- import * as __boot from './${bootChunkName}';
195
- const __bootContext = { dev: false, host: process.env.HOST ?? '${host}', port: process.env.PORT ? Number(process.env.PORT) : ${port} };
196
- try {
197
- await __boot.onStartup?.(__bootContext);
198
- } catch (err) {
199
- console.error('[boot] startup failed:', err);
200
- try { await __boot.onShutdown?.(__bootContext); } catch {}
201
- process.exit(1);
202
- }
203
- if (__boot.onShutdown) {
204
- process.on('SIGTERM', async () => {
205
- try {
206
- await __boot.onShutdown(__bootContext);
207
- process.exit(0);
208
- } catch (err) {
209
- console.error('[boot] shutdown failed:', err);
210
- process.exit(1);
211
- }
212
- });
213
- }
233
+ import * as __astroscope_boot from './${bootChunkName}';
234
+ import { setup as __astroscope_bootSetup } from '@astroscope/boot/setup';
235
+ await __astroscope_bootSetup(__astroscope_boot);
214
236
  `;
215
237
  const s = new MagicString(entryChunk.code);
216
238
  s.prepend(bootImport);
@@ -221,19 +243,10 @@ if (__boot.onShutdown) {
221
243
  logger.info(`injected ${bootChunkName} into entry.mjs`);
222
244
  },
223
245
  writeBundle(outputOptions) {
224
- if (!isSSR) return;
246
+ if (!isSSR || !warmupModules) return;
225
247
  const outDir = outputOptions.dir;
226
248
  if (!outDir) return;
227
- const modules = [];
228
- if (middlewarePath) {
229
- modules.push(`./${middlewarePath}`);
230
- }
231
- for (const page of pageModules) {
232
- modules.push(`./${page}`);
233
- }
234
- const manifestPath = path.join(outDir, "chunks", WARMUP_MANIFEST_FILE);
235
- fs.writeFileSync(manifestPath, JSON.stringify({ modules }));
236
- logger.info(`generated warmup for ${pageModules.length} pages`);
249
+ writeWarmupManifest(outDir, warmupModules, logger);
237
250
  }
238
251
  }
239
252
  ]
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/lifecycle.ts
21
+ var lifecycle_exports = {};
22
+ __export(lifecycle_exports, {
23
+ runShutdown: () => runShutdown,
24
+ runStartup: () => runStartup
25
+ });
26
+ module.exports = __toCommonJS(lifecycle_exports);
27
+
28
+ // src/events.ts
29
+ var STORE_KEY = /* @__PURE__ */ Symbol.for("@astroscope/boot/events");
30
+ function getStore() {
31
+ const existing = globalThis[STORE_KEY];
32
+ if (existing) return existing;
33
+ const store = { listeners: /* @__PURE__ */ new Map() };
34
+ globalThis[STORE_KEY] = store;
35
+ return store;
36
+ }
37
+ async function emit(event, context) {
38
+ const store = getStore();
39
+ const handlers = store.listeners.get(event);
40
+ if (!handlers) return;
41
+ for (const handler of handlers) {
42
+ await handler(context);
43
+ }
44
+ }
45
+
46
+ // src/lifecycle.ts
47
+ async function runStartup(boot, context) {
48
+ await emit("beforeOnStartup", context);
49
+ await boot.onStartup?.(context);
50
+ await emit("afterOnStartup", context);
51
+ }
52
+ async function runShutdown(boot, context) {
53
+ try {
54
+ await emit("beforeOnShutdown", context);
55
+ await boot.onShutdown?.(context);
56
+ } finally {
57
+ await emit("afterOnShutdown", context);
58
+ }
59
+ }
60
+ // Annotate the CommonJS export names for ESM import in node:
61
+ 0 && (module.exports = {
62
+ runShutdown,
63
+ runStartup
64
+ });
@@ -0,0 +1,10 @@
1
+ import { B as BootContext } from './types-CxpusND2.cjs';
2
+
3
+ interface BootModule {
4
+ onStartup?: ((context: BootContext) => Promise<void> | void) | undefined;
5
+ onShutdown?: ((context: BootContext) => Promise<void> | void) | undefined;
6
+ }
7
+ declare function runStartup(boot: BootModule, context: BootContext): Promise<void>;
8
+ declare function runShutdown(boot: BootModule, context: BootContext): Promise<void>;
9
+
10
+ export { type BootModule, runShutdown, runStartup };
@@ -0,0 +1,10 @@
1
+ import { B as BootContext } from './types-CxpusND2.js';
2
+
3
+ interface BootModule {
4
+ onStartup?: ((context: BootContext) => Promise<void> | void) | undefined;
5
+ onShutdown?: ((context: BootContext) => Promise<void> | void) | undefined;
6
+ }
7
+ declare function runStartup(boot: BootModule, context: BootContext): Promise<void>;
8
+ declare function runShutdown(boot: BootModule, context: BootContext): Promise<void>;
9
+
10
+ export { type BootModule, runShutdown, runStartup };
@@ -0,0 +1,9 @@
1
+ import {
2
+ runShutdown,
3
+ runStartup
4
+ } from "./chunk-LUFQ5I47.js";
5
+ import "./chunk-I62ZQYTP.js";
6
+ export {
7
+ runShutdown,
8
+ runStartup
9
+ };
package/dist/setup.cjs ADDED
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/setup.ts
21
+ var setup_exports = {};
22
+ __export(setup_exports, {
23
+ setup: () => setup
24
+ });
25
+ module.exports = __toCommonJS(setup_exports);
26
+ var import_config = require("virtual:@astroscope/boot/config");
27
+
28
+ // src/events.ts
29
+ var STORE_KEY = /* @__PURE__ */ Symbol.for("@astroscope/boot/events");
30
+ function getStore() {
31
+ const existing = globalThis[STORE_KEY];
32
+ if (existing) return existing;
33
+ const store = { listeners: /* @__PURE__ */ new Map() };
34
+ globalThis[STORE_KEY] = store;
35
+ return store;
36
+ }
37
+ async function emit(event, context) {
38
+ const store = getStore();
39
+ const handlers = store.listeners.get(event);
40
+ if (!handlers) return;
41
+ for (const handler of handlers) {
42
+ await handler(context);
43
+ }
44
+ }
45
+
46
+ // src/lifecycle.ts
47
+ async function runStartup(boot, context) {
48
+ await emit("beforeOnStartup", context);
49
+ await boot.onStartup?.(context);
50
+ await emit("afterOnStartup", context);
51
+ }
52
+ async function runShutdown(boot, context) {
53
+ try {
54
+ await emit("beforeOnShutdown", context);
55
+ await boot.onShutdown?.(context);
56
+ } finally {
57
+ await emit("afterOnShutdown", context);
58
+ }
59
+ }
60
+
61
+ // src/setup.ts
62
+ async function setup(boot) {
63
+ const context = {
64
+ dev: false,
65
+ host: process.env["HOST"] ?? import_config.config.host,
66
+ port: process.env["PORT"] ? Number(process.env["PORT"]) : import_config.config.port
67
+ };
68
+ try {
69
+ await runStartup(boot, context);
70
+ } catch (err) {
71
+ console.error("[boot] startup failed:", err);
72
+ try {
73
+ await runShutdown(boot, context);
74
+ } catch {
75
+ }
76
+ process.exit(1);
77
+ }
78
+ process.on("SIGTERM", async () => {
79
+ try {
80
+ await runShutdown(boot, context);
81
+ } catch (err) {
82
+ console.error("[boot] shutdown failed:", err);
83
+ }
84
+ process.exit(0);
85
+ });
86
+ }
87
+ // Annotate the CommonJS export names for ESM import in node:
88
+ 0 && (module.exports = {
89
+ setup
90
+ });
@@ -0,0 +1,6 @@
1
+ import { BootModule } from './lifecycle.cjs';
2
+ import './types-CxpusND2.cjs';
3
+
4
+ declare function setup(boot: BootModule): Promise<void>;
5
+
6
+ export { setup };
@@ -0,0 +1,6 @@
1
+ import { BootModule } from './lifecycle.js';
2
+ import './types-CxpusND2.js';
3
+
4
+ declare function setup(boot: BootModule): Promise<void>;
5
+
6
+ export { setup };
package/dist/setup.js ADDED
@@ -0,0 +1,36 @@
1
+ import {
2
+ runShutdown,
3
+ runStartup
4
+ } from "./chunk-LUFQ5I47.js";
5
+ import "./chunk-I62ZQYTP.js";
6
+
7
+ // src/setup.ts
8
+ import { config } from "virtual:@astroscope/boot/config";
9
+ async function setup(boot) {
10
+ const context = {
11
+ dev: false,
12
+ host: process.env["HOST"] ?? config.host,
13
+ port: process.env["PORT"] ? Number(process.env["PORT"]) : config.port
14
+ };
15
+ try {
16
+ await runStartup(boot, context);
17
+ } catch (err) {
18
+ console.error("[boot] startup failed:", err);
19
+ try {
20
+ await runShutdown(boot, context);
21
+ } catch {
22
+ }
23
+ process.exit(1);
24
+ }
25
+ process.on("SIGTERM", async () => {
26
+ try {
27
+ await runShutdown(boot, context);
28
+ } catch (err) {
29
+ console.error("[boot] shutdown failed:", err);
30
+ }
31
+ process.exit(0);
32
+ });
33
+ }
34
+ export {
35
+ setup
36
+ };
package/dist/warmup.cjs CHANGED
@@ -61,7 +61,10 @@ async function warmup() {
61
61
  const absolutePath = (0, import_node_path.resolve)(serverDir, mod);
62
62
  return (0, import_node_url.pathToFileURL)(absolutePath).href;
63
63
  });
64
- const results = await Promise.allSettled(resolvedModules.map((mod) => import(mod)));
64
+ const results = await Promise.allSettled(resolvedModules.map((mod) => import(
65
+ /* @vite-ignore */
66
+ mod
67
+ )));
65
68
  const success = [];
66
69
  const failed = [];
67
70
  for (let i = 0; i < results.length; i++) {
package/dist/warmup.js CHANGED
@@ -36,7 +36,10 @@ async function warmup() {
36
36
  const absolutePath = resolve(serverDir, mod);
37
37
  return pathToFileURL(absolutePath).href;
38
38
  });
39
- const results = await Promise.allSettled(resolvedModules.map((mod) => import(mod)));
39
+ const results = await Promise.allSettled(resolvedModules.map((mod) => import(
40
+ /* @vite-ignore */
41
+ mod
42
+ )));
40
43
  const success = [];
41
44
  const failed = [];
42
45
  for (let i = 0; i < results.length; i++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astroscope/boot",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Startup and graceful shutdown hooks for Astro SSR",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -14,6 +14,14 @@
14
14
  "./warmup": {
15
15
  "types": "./dist/warmup.d.ts",
16
16
  "import": "./dist/warmup.js"
17
+ },
18
+ "./events": {
19
+ "types": "./dist/events.d.ts",
20
+ "import": "./dist/events.js"
21
+ },
22
+ "./setup": {
23
+ "types": "./dist/setup.d.ts",
24
+ "import": "./dist/setup.js"
17
25
  }
18
26
  },
19
27
  "files": [
@@ -41,7 +49,7 @@
41
49
  },
42
50
  "homepage": "https://github.com/smnbbrv/astroscope/tree/main/packages/boot#readme",
43
51
  "scripts": {
44
- "build": "tsup src/index.ts src/warmup.ts --format esm,cjs --dts",
52
+ "build": "tsup src/index.ts src/warmup.ts src/events.ts src/lifecycle.ts src/setup.ts --format esm,cjs --dts --external virtual:@astroscope/boot/config",
45
53
  "typecheck": "tsc --noEmit",
46
54
  "lint": "eslint 'src/**/*.{ts,tsx}'",
47
55
  "lint:fix": "eslint 'src/**/*.{ts,tsx}' --fix"
@@ -49,7 +57,8 @@
49
57
  "devDependencies": {
50
58
  "astro": "^5.17.1",
51
59
  "tsup": "^8.5.1",
52
- "typescript": "^5.9.3"
60
+ "typescript": "^5.9.3",
61
+ "vite": "^6.4.1"
53
62
  },
54
63
  "peerDependencies": {
55
64
  "astro": "^5.0.0"