@astroscope/boot 0.1.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.
- package/README.md +106 -0
- package/dist/index.cjs +150 -0
- package/dist/index.d.cts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +119 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# @astroscope/boot
|
|
2
|
+
|
|
3
|
+
Run initialization and cleanup code for your Astro server.
|
|
4
|
+
|
|
5
|
+
## Examples
|
|
6
|
+
|
|
7
|
+
See the [demo/boot](../../demo/boot) directory for a working example.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun add @astroscope/boot
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
1. Create a boot file at `src/boot.ts` (or `src/boot/index.ts`):
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
// src/boot.ts
|
|
21
|
+
export async function onStartup() {
|
|
22
|
+
console.log("Starting up...");
|
|
23
|
+
|
|
24
|
+
await someAsyncInitialization();
|
|
25
|
+
|
|
26
|
+
console.log("Ready!");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function onShutdown() {
|
|
30
|
+
console.log("Shutting down...");
|
|
31
|
+
|
|
32
|
+
await closeConnections();
|
|
33
|
+
|
|
34
|
+
console.log("Goodbye!");
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
2. Add the integration to your Astro config:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
// astro.config.ts
|
|
42
|
+
import { defineConfig } from "astro/config";
|
|
43
|
+
import boot from "@astroscope/boot";
|
|
44
|
+
|
|
45
|
+
export default defineConfig({
|
|
46
|
+
output: "server",
|
|
47
|
+
integrations: [boot()],
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Lifecycle Hooks
|
|
52
|
+
|
|
53
|
+
### `onStartup`
|
|
54
|
+
|
|
55
|
+
Called before the server starts handling requests. Use this for:
|
|
56
|
+
|
|
57
|
+
- Database connection initialization
|
|
58
|
+
- Loading configuration
|
|
59
|
+
- Warming caches
|
|
60
|
+
- Setting up external service clients
|
|
61
|
+
|
|
62
|
+
### `onShutdown`
|
|
63
|
+
|
|
64
|
+
Called when the server is shutting down (SIGTERM in production, server close in development). Use this for:
|
|
65
|
+
|
|
66
|
+
- Closing database connections
|
|
67
|
+
- Flushing buffers
|
|
68
|
+
- Cleaning up resources
|
|
69
|
+
- Graceful shutdown of external services
|
|
70
|
+
|
|
71
|
+
## Options
|
|
72
|
+
|
|
73
|
+
### `entry`
|
|
74
|
+
|
|
75
|
+
Path to the boot file relative to the project root.
|
|
76
|
+
|
|
77
|
+
- **Type**: `string`
|
|
78
|
+
- **Default**: `"src/boot.ts"`
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
boot({ entry: "src/startup.ts" });
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `hmr`
|
|
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).
|
|
87
|
+
|
|
88
|
+
- **Type**: `boolean`
|
|
89
|
+
- **Default**: `false`
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
boot({ hmr: true });
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## How it works
|
|
96
|
+
|
|
97
|
+
- **Development**: The boot file runs _after_ the dev server starts listening (Vite limitation). `onShutdown` is called when the dev server closes.
|
|
98
|
+
- **Production**: `onStartup` runs _before_ the server starts handling requests. `onShutdown` is called on SIGTERM.
|
|
99
|
+
|
|
100
|
+
## Requirements
|
|
101
|
+
|
|
102
|
+
- Only works with SSR output mode (`output: "server"`)
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
default: () => boot
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
var import_node_fs = __toESM(require("fs"), 1);
|
|
37
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
38
|
+
function resolveEntry(entry) {
|
|
39
|
+
if (entry) return entry;
|
|
40
|
+
if (import_node_fs.default.existsSync("src/boot.ts")) return "src/boot.ts";
|
|
41
|
+
if (import_node_fs.default.existsSync("src/boot/index.ts")) return "src/boot/index.ts";
|
|
42
|
+
return "src/boot.ts";
|
|
43
|
+
}
|
|
44
|
+
function boot(options = {}) {
|
|
45
|
+
const entry = resolveEntry(options.entry);
|
|
46
|
+
const hmr = options.hmr ?? false;
|
|
47
|
+
let isBuild = false;
|
|
48
|
+
let isSSR = false;
|
|
49
|
+
let bootChunkRef = null;
|
|
50
|
+
return {
|
|
51
|
+
name: "@astroscope/boot",
|
|
52
|
+
hooks: {
|
|
53
|
+
"astro:config:setup": ({ command, updateConfig, logger }) => {
|
|
54
|
+
isBuild = command === "build";
|
|
55
|
+
updateConfig({
|
|
56
|
+
vite: {
|
|
57
|
+
plugins: [
|
|
58
|
+
{
|
|
59
|
+
name: "@astroscope/boot",
|
|
60
|
+
configureServer(server) {
|
|
61
|
+
if (isBuild) return;
|
|
62
|
+
server.httpServer?.once("listening", async () => {
|
|
63
|
+
try {
|
|
64
|
+
const module2 = await server.ssrLoadModule(
|
|
65
|
+
`/${entry}`
|
|
66
|
+
);
|
|
67
|
+
if (module2.onStartup) {
|
|
68
|
+
await module2.onStartup();
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
logger.error(`Error running startup script: ${error}`);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
server.httpServer?.once("close", async () => {
|
|
75
|
+
try {
|
|
76
|
+
const module2 = await server.ssrLoadModule(
|
|
77
|
+
`/${entry}`
|
|
78
|
+
);
|
|
79
|
+
if (module2.onShutdown) {
|
|
80
|
+
await module2.onShutdown();
|
|
81
|
+
}
|
|
82
|
+
} catch (error) {
|
|
83
|
+
logger.error(`Error running shutdown script: ${error}`);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
if (hmr) {
|
|
87
|
+
server.watcher.on("change", async (changedPath) => {
|
|
88
|
+
if (!changedPath.endsWith(entry)) return;
|
|
89
|
+
logger.info("boot file changed, re-running onStartup...");
|
|
90
|
+
try {
|
|
91
|
+
server.moduleGraph.invalidateAll();
|
|
92
|
+
const module2 = await server.ssrLoadModule(
|
|
93
|
+
`/${entry}`
|
|
94
|
+
);
|
|
95
|
+
if (module2.onStartup) {
|
|
96
|
+
await module2.onStartup();
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
logger.error(`Error running startup script: ${error}`);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
configResolved(config) {
|
|
105
|
+
isSSR = !!config.build?.ssr;
|
|
106
|
+
},
|
|
107
|
+
buildStart() {
|
|
108
|
+
if (!isSSR) return;
|
|
109
|
+
try {
|
|
110
|
+
bootChunkRef = this.emitFile({
|
|
111
|
+
type: "chunk",
|
|
112
|
+
id: entry,
|
|
113
|
+
name: "boot"
|
|
114
|
+
});
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
writeBundle(outputOptions) {
|
|
119
|
+
const outDir = outputOptions.dir;
|
|
120
|
+
if (!outDir || !bootChunkRef) return;
|
|
121
|
+
const entryPath = import_node_path.default.join(outDir, "entry.mjs");
|
|
122
|
+
if (!import_node_fs.default.existsSync(entryPath)) return;
|
|
123
|
+
const bootChunkName = this.getFileName(bootChunkRef);
|
|
124
|
+
if (!bootChunkName) {
|
|
125
|
+
logger.warn("boot chunk not found");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const sourcemapPath = `${entryPath}.map`;
|
|
129
|
+
if (import_node_fs.default.existsSync(sourcemapPath)) {
|
|
130
|
+
logger.warn(
|
|
131
|
+
"sourcemap detected for entry.mjs - line numbers may be off by 2 lines due to boot injection"
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
let content = import_node_fs.default.readFileSync(entryPath, "utf-8");
|
|
135
|
+
const bootImport = `import { onStartup, onShutdown } from './${bootChunkName}';
|
|
136
|
+
await onStartup?.();
|
|
137
|
+
if (onShutdown) process.on('SIGTERM', async () => { await onShutdown(); process.exit(0); });
|
|
138
|
+
`;
|
|
139
|
+
content = bootImport + content;
|
|
140
|
+
import_node_fs.default.writeFileSync(entryPath, content);
|
|
141
|
+
logger.info(`injected ${bootChunkName} into entry.mjs`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AstroIntegration } from 'astro';
|
|
2
|
+
|
|
3
|
+
interface BootOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Path to the boot file relative to the project root.
|
|
6
|
+
* @default "src/boot.ts"
|
|
7
|
+
*/
|
|
8
|
+
entry?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Enable HMR for the boot file. When true, `onStartup` will re-run when the boot file changes.
|
|
11
|
+
* @default false
|
|
12
|
+
*/
|
|
13
|
+
hmr?: boolean;
|
|
14
|
+
}
|
|
15
|
+
declare function boot(options?: BootOptions): AstroIntegration;
|
|
16
|
+
|
|
17
|
+
export { type BootOptions, boot as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AstroIntegration } from 'astro';
|
|
2
|
+
|
|
3
|
+
interface BootOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Path to the boot file relative to the project root.
|
|
6
|
+
* @default "src/boot.ts"
|
|
7
|
+
*/
|
|
8
|
+
entry?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Enable HMR for the boot file. When true, `onStartup` will re-run when the boot file changes.
|
|
11
|
+
* @default false
|
|
12
|
+
*/
|
|
13
|
+
hmr?: boolean;
|
|
14
|
+
}
|
|
15
|
+
declare function boot(options?: BootOptions): AstroIntegration;
|
|
16
|
+
|
|
17
|
+
export { type BootOptions, boot as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
function resolveEntry(entry) {
|
|
5
|
+
if (entry) return entry;
|
|
6
|
+
if (fs.existsSync("src/boot.ts")) return "src/boot.ts";
|
|
7
|
+
if (fs.existsSync("src/boot/index.ts")) return "src/boot/index.ts";
|
|
8
|
+
return "src/boot.ts";
|
|
9
|
+
}
|
|
10
|
+
function boot(options = {}) {
|
|
11
|
+
const entry = resolveEntry(options.entry);
|
|
12
|
+
const hmr = options.hmr ?? false;
|
|
13
|
+
let isBuild = false;
|
|
14
|
+
let isSSR = false;
|
|
15
|
+
let bootChunkRef = null;
|
|
16
|
+
return {
|
|
17
|
+
name: "@astroscope/boot",
|
|
18
|
+
hooks: {
|
|
19
|
+
"astro:config:setup": ({ command, updateConfig, logger }) => {
|
|
20
|
+
isBuild = command === "build";
|
|
21
|
+
updateConfig({
|
|
22
|
+
vite: {
|
|
23
|
+
plugins: [
|
|
24
|
+
{
|
|
25
|
+
name: "@astroscope/boot",
|
|
26
|
+
configureServer(server) {
|
|
27
|
+
if (isBuild) return;
|
|
28
|
+
server.httpServer?.once("listening", async () => {
|
|
29
|
+
try {
|
|
30
|
+
const module = await server.ssrLoadModule(
|
|
31
|
+
`/${entry}`
|
|
32
|
+
);
|
|
33
|
+
if (module.onStartup) {
|
|
34
|
+
await module.onStartup();
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
logger.error(`Error running startup script: ${error}`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
server.httpServer?.once("close", async () => {
|
|
41
|
+
try {
|
|
42
|
+
const module = await server.ssrLoadModule(
|
|
43
|
+
`/${entry}`
|
|
44
|
+
);
|
|
45
|
+
if (module.onShutdown) {
|
|
46
|
+
await module.onShutdown();
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
logger.error(`Error running shutdown script: ${error}`);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
if (hmr) {
|
|
53
|
+
server.watcher.on("change", async (changedPath) => {
|
|
54
|
+
if (!changedPath.endsWith(entry)) return;
|
|
55
|
+
logger.info("boot file changed, re-running onStartup...");
|
|
56
|
+
try {
|
|
57
|
+
server.moduleGraph.invalidateAll();
|
|
58
|
+
const module = await server.ssrLoadModule(
|
|
59
|
+
`/${entry}`
|
|
60
|
+
);
|
|
61
|
+
if (module.onStartup) {
|
|
62
|
+
await module.onStartup();
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
logger.error(`Error running startup script: ${error}`);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
configResolved(config) {
|
|
71
|
+
isSSR = !!config.build?.ssr;
|
|
72
|
+
},
|
|
73
|
+
buildStart() {
|
|
74
|
+
if (!isSSR) return;
|
|
75
|
+
try {
|
|
76
|
+
bootChunkRef = this.emitFile({
|
|
77
|
+
type: "chunk",
|
|
78
|
+
id: entry,
|
|
79
|
+
name: "boot"
|
|
80
|
+
});
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
writeBundle(outputOptions) {
|
|
85
|
+
const outDir = outputOptions.dir;
|
|
86
|
+
if (!outDir || !bootChunkRef) return;
|
|
87
|
+
const entryPath = path.join(outDir, "entry.mjs");
|
|
88
|
+
if (!fs.existsSync(entryPath)) return;
|
|
89
|
+
const bootChunkName = this.getFileName(bootChunkRef);
|
|
90
|
+
if (!bootChunkName) {
|
|
91
|
+
logger.warn("boot chunk not found");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const sourcemapPath = `${entryPath}.map`;
|
|
95
|
+
if (fs.existsSync(sourcemapPath)) {
|
|
96
|
+
logger.warn(
|
|
97
|
+
"sourcemap detected for entry.mjs - line numbers may be off by 2 lines due to boot injection"
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
let content = fs.readFileSync(entryPath, "utf-8");
|
|
101
|
+
const bootImport = `import { onStartup, onShutdown } from './${bootChunkName}';
|
|
102
|
+
await onStartup?.();
|
|
103
|
+
if (onShutdown) process.on('SIGTERM', async () => { await onShutdown(); process.exit(0); });
|
|
104
|
+
`;
|
|
105
|
+
content = bootImport + content;
|
|
106
|
+
fs.writeFileSync(entryPath, content);
|
|
107
|
+
logger.info(`injected ${bootChunkName} into entry.mjs`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export {
|
|
118
|
+
boot as default
|
|
119
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@astroscope/boot",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Boot integration for Astro",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/smnbbrv/astroscope.git",
|
|
25
|
+
"directory": "packages/boot"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"astro",
|
|
29
|
+
"astro-integration",
|
|
30
|
+
"boot",
|
|
31
|
+
"frameworks"
|
|
32
|
+
],
|
|
33
|
+
"author": "smnbbrv",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/smnbbrv/astroscope/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/smnbbrv/astroscope/tree/main/packages/boot#readme",
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
41
|
+
"typecheck": "tsc --noEmit",
|
|
42
|
+
"lint": "eslint 'src/**/*.{ts,tsx}'",
|
|
43
|
+
"lint:fix": "eslint 'src/**/*.{ts,tsx}' --fix"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"astro": "^5.1.0",
|
|
47
|
+
"tsup": "^8.5.1",
|
|
48
|
+
"typescript": "^5.9.3"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"astro": "^5.0.0"
|
|
52
|
+
}
|
|
53
|
+
}
|