@astroscope/boot 0.2.3 → 0.3.1

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,123 @@ 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";
93
205
  function getServerDefaults(config) {
94
206
  return {
95
207
  host: typeof config?.server?.host === "string" ? config.server.host : config?.server?.host === true ? "0.0.0.0" : "localhost",
@@ -103,8 +215,7 @@ function boot(options = {}) {
103
215
  let isSSR = false;
104
216
  let bootChunkRef = null;
105
217
  let astroConfig = null;
106
- let pageModules = [];
107
- let middlewarePath = null;
218
+ let warmupModules = null;
108
219
  return {
109
220
  name: "@astroscope/boot",
110
221
  hooks: {
@@ -117,7 +228,7 @@ function boot(options = {}) {
117
228
  {
118
229
  name: "@astroscope/boot",
119
230
  enforce: "pre",
120
- configureServer(server) {
231
+ async configureServer(server) {
121
232
  if (isBuild) return;
122
233
  const getBootContext = () => {
123
234
  const addr = server.httpServer?.address();
@@ -125,72 +236,27 @@ function boot(options = {}) {
125
236
  const host2 = addr.address === "::" || addr.address === "0.0.0.0" ? "localhost" : addr.address;
126
237
  return { dev: true, host: host2, port: addr.port };
127
238
  }
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;
239
+ const { host, port } = getServerDefaults(astroConfig);
131
240
  return { dev: true, host, port };
132
241
  };
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
- });
242
+ try {
243
+ const bootContext = getBootContext();
244
+ const module2 = await server.ssrLoadModule(`/${entry}`);
245
+ await runStartup(module2, bootContext);
246
+ } catch (error) {
247
+ logger.error(`Error running startup script: ${error}`);
248
+ }
142
249
  server.httpServer?.once("close", async () => {
143
250
  try {
144
251
  const bootContext = getBootContext();
145
252
  const module2 = await server.ssrLoadModule(`/${entry}`);
146
- await module2.onShutdown?.(bootContext);
253
+ await runShutdown(module2, bootContext);
147
254
  } catch (error) {
148
255
  logger.error(`Error running shutdown script: ${error}`);
149
256
  }
150
257
  });
151
258
  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
- });
259
+ setupBootHmr(server, entry, logger, getBootContext);
194
260
  }
195
261
  },
196
262
  configResolved(config2) {
@@ -215,39 +281,12 @@ function boot(options = {}) {
215
281
  logger.warn("entry.mjs not found - boot injection skipped");
216
282
  return;
217
283
  }
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
- }
284
+ warmupModules = collectWarmupModules(bundle);
229
285
  const { host, port } = getServerDefaults(astroConfig);
230
286
  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
- }
287
+ import * as __astroscope_boot from './${bootChunkName}';
288
+ import { setup as __astroscope_bootSetup } from '@astroscope/boot/setup';
289
+ await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port })});
251
290
  `;
252
291
  const s = new import_magic_string.default(entryChunk.code);
253
292
  s.prepend(bootImport);
@@ -258,19 +297,10 @@ if (__boot.onShutdown) {
258
297
  logger.info(`injected ${bootChunkName} into entry.mjs`);
259
298
  },
260
299
  writeBundle(outputOptions) {
261
- if (!isSSR) return;
300
+ if (!isSSR || !warmupModules) return;
262
301
  const outDir = outputOptions.dir;
263
302
  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`);
303
+ writeWarmupManifest(outDir, warmupModules, logger);
274
304
  }
275
305
  }
276
306
  ]
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,90 @@ 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";
56
141
  function getServerDefaults(config) {
57
142
  return {
58
143
  host: typeof config?.server?.host === "string" ? config.server.host : config?.server?.host === true ? "0.0.0.0" : "localhost",
@@ -66,8 +151,7 @@ function boot(options = {}) {
66
151
  let isSSR = false;
67
152
  let bootChunkRef = null;
68
153
  let astroConfig = null;
69
- let pageModules = [];
70
- let middlewarePath = null;
154
+ let warmupModules = null;
71
155
  return {
72
156
  name: "@astroscope/boot",
73
157
  hooks: {
@@ -80,7 +164,7 @@ function boot(options = {}) {
80
164
  {
81
165
  name: "@astroscope/boot",
82
166
  enforce: "pre",
83
- configureServer(server) {
167
+ async configureServer(server) {
84
168
  if (isBuild) return;
85
169
  const getBootContext = () => {
86
170
  const addr = server.httpServer?.address();
@@ -88,72 +172,27 @@ function boot(options = {}) {
88
172
  const host2 = addr.address === "::" || addr.address === "0.0.0.0" ? "localhost" : addr.address;
89
173
  return { dev: true, host: host2, port: addr.port };
90
174
  }
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;
175
+ const { host, port } = getServerDefaults(astroConfig);
94
176
  return { dev: true, host, port };
95
177
  };
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
- });
178
+ try {
179
+ const bootContext = getBootContext();
180
+ const module = await server.ssrLoadModule(`/${entry}`);
181
+ await runStartup(module, bootContext);
182
+ } catch (error) {
183
+ logger.error(`Error running startup script: ${error}`);
184
+ }
105
185
  server.httpServer?.once("close", async () => {
106
186
  try {
107
187
  const bootContext = getBootContext();
108
188
  const module = await server.ssrLoadModule(`/${entry}`);
109
- await module.onShutdown?.(bootContext);
189
+ await runShutdown(module, bootContext);
110
190
  } catch (error) {
111
191
  logger.error(`Error running shutdown script: ${error}`);
112
192
  }
113
193
  });
114
194
  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
- });
195
+ setupBootHmr(server, entry, logger, getBootContext);
157
196
  }
158
197
  },
159
198
  configResolved(config2) {
@@ -178,39 +217,12 @@ function boot(options = {}) {
178
217
  logger.warn("entry.mjs not found - boot injection skipped");
179
218
  return;
180
219
  }
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
- }
220
+ warmupModules = collectWarmupModules(bundle);
192
221
  const { host, port } = getServerDefaults(astroConfig);
193
222
  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
- }
223
+ import * as __astroscope_boot from './${bootChunkName}';
224
+ import { setup as __astroscope_bootSetup } from '@astroscope/boot/setup';
225
+ await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port })});
214
226
  `;
215
227
  const s = new MagicString(entryChunk.code);
216
228
  s.prepend(bootImport);
@@ -221,19 +233,10 @@ if (__boot.onShutdown) {
221
233
  logger.info(`injected ${bootChunkName} into entry.mjs`);
222
234
  },
223
235
  writeBundle(outputOptions) {
224
- if (!isSSR) return;
236
+ if (!isSSR || !warmupModules) return;
225
237
  const outDir = outputOptions.dir;
226
238
  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`);
239
+ writeWarmupManifest(outDir, warmupModules, logger);
237
240
  }
238
241
  }
239
242
  ]
@@ -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,89 @@
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
+
27
+ // src/events.ts
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
+ async function emit(event, context) {
37
+ const store = getStore();
38
+ const handlers = store.listeners.get(event);
39
+ if (!handlers) return;
40
+ for (const handler of handlers) {
41
+ await handler(context);
42
+ }
43
+ }
44
+
45
+ // src/lifecycle.ts
46
+ async function runStartup(boot, context) {
47
+ await emit("beforeOnStartup", context);
48
+ await boot.onStartup?.(context);
49
+ await emit("afterOnStartup", context);
50
+ }
51
+ async function runShutdown(boot, context) {
52
+ try {
53
+ await emit("beforeOnShutdown", context);
54
+ await boot.onShutdown?.(context);
55
+ } finally {
56
+ await emit("afterOnShutdown", context);
57
+ }
58
+ }
59
+
60
+ // src/setup.ts
61
+ async function setup(boot, config) {
62
+ const context = {
63
+ dev: false,
64
+ host: process.env["HOST"] ?? config.host,
65
+ port: process.env["PORT"] ? Number(process.env["PORT"]) : config.port
66
+ };
67
+ try {
68
+ await runStartup(boot, context);
69
+ } catch (err) {
70
+ console.error("[boot] startup failed:", err);
71
+ try {
72
+ await runShutdown(boot, context);
73
+ } catch {
74
+ }
75
+ process.exit(1);
76
+ }
77
+ process.on("SIGTERM", async () => {
78
+ try {
79
+ await runShutdown(boot, context);
80
+ } catch (err) {
81
+ console.error("[boot] shutdown failed:", err);
82
+ }
83
+ process.exit(0);
84
+ });
85
+ }
86
+ // Annotate the CommonJS export names for ESM import in node:
87
+ 0 && (module.exports = {
88
+ setup
89
+ });
@@ -0,0 +1,9 @@
1
+ import { BootModule } from './lifecycle.cjs';
2
+ import './types-CxpusND2.cjs';
3
+
4
+ declare function setup(boot: BootModule, config: {
5
+ host: string;
6
+ port: number;
7
+ }): Promise<void>;
8
+
9
+ export { setup };
@@ -0,0 +1,9 @@
1
+ import { BootModule } from './lifecycle.js';
2
+ import './types-CxpusND2.js';
3
+
4
+ declare function setup(boot: BootModule, config: {
5
+ host: string;
6
+ port: number;
7
+ }): Promise<void>;
8
+
9
+ export { setup };
package/dist/setup.js ADDED
@@ -0,0 +1,35 @@
1
+ import {
2
+ runShutdown,
3
+ runStartup
4
+ } from "./chunk-LUFQ5I47.js";
5
+ import "./chunk-I62ZQYTP.js";
6
+
7
+ // src/setup.ts
8
+ async function setup(boot, config) {
9
+ const context = {
10
+ dev: false,
11
+ host: process.env["HOST"] ?? config.host,
12
+ port: process.env["PORT"] ? Number(process.env["PORT"]) : config.port
13
+ };
14
+ try {
15
+ await runStartup(boot, context);
16
+ } catch (err) {
17
+ console.error("[boot] startup failed:", err);
18
+ try {
19
+ await runShutdown(boot, context);
20
+ } catch {
21
+ }
22
+ process.exit(1);
23
+ }
24
+ process.on("SIGTERM", async () => {
25
+ try {
26
+ await runShutdown(boot, context);
27
+ } catch (err) {
28
+ console.error("[boot] shutdown failed:", err);
29
+ }
30
+ process.exit(0);
31
+ });
32
+ }
33
+ export {
34
+ setup
35
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astroscope/boot",
3
- "version": "0.2.3",
3
+ "version": "0.3.1",
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",
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"