@orochibraru/svelte-smol 1.3.1 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -7
- package/index.ts +52 -24
- package/internal.d.ts +5 -1
- package/package.json +4 -3
- package/templates/env.ts +2 -0
- package/templates/handler.ts +18 -1
- package/templates/healthcheck.ts +55 -0
- package/templates/index.ts +28 -3
package/README.md
CHANGED
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
A [SvelteKit](https://svelte.dev/docs/kit) adapter that compiles your app into a
|
|
4
4
|
**single standalone executable** with `bun build --compile`. No `node_modules`,
|
|
5
|
-
no JS files to ship
|
|
5
|
+
no JS files to ship, just one binary plus its static assets.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
bun add -d @orochibraru/svelte-
|
|
10
|
+
bun add -d @orochibraru/svelte-smol
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
15
|
```js
|
|
16
16
|
// svelte.config.js
|
|
17
|
-
import adapter from "@orochibraru/svelte-smol;
|
|
17
|
+
import adapter from "@orochibraru/svelte-smol";
|
|
18
18
|
|
|
19
19
|
export default {
|
|
20
20
|
kit: {
|
|
@@ -23,11 +23,10 @@ export default {
|
|
|
23
23
|
};
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
Bun runtime:
|
|
26
|
+
The compile step runs under the Bun runtime, so build with:
|
|
28
27
|
|
|
29
28
|
```bash
|
|
30
|
-
bun
|
|
29
|
+
bun run vite build
|
|
31
30
|
```
|
|
32
31
|
|
|
33
32
|
## Output
|
|
@@ -40,7 +39,7 @@ build/
|
|
|
40
39
|
```
|
|
41
40
|
|
|
42
41
|
Deploy the whole `build/` directory (or just `server` if a proxy/CDN serves the
|
|
43
|
-
assets
|
|
42
|
+
assets, see `serveAssets`). The executable locates `client/` and `prerendered/`
|
|
44
43
|
relative to its own path, so it can be run from any working directory:
|
|
45
44
|
|
|
46
45
|
```bash
|
|
@@ -58,6 +57,7 @@ adapter({
|
|
|
58
57
|
minify: false, // minify the bundled server code
|
|
59
58
|
sourcemap: false, // embed a source map for server stack traces
|
|
60
59
|
precompress: false, // emit + serve .gz / .br sibling files
|
|
60
|
+
healthcheck: true, // also compile `build/healthcheck` + expose GET /_health
|
|
61
61
|
envPrefix: "", // prefix for the runtime env vars below
|
|
62
62
|
serveAssets: true, // serve client/ and prerendered/ from the binary
|
|
63
63
|
serveOptions: {}, // extra Bun.serve() options (tls, reusePort, …)
|
|
@@ -88,9 +88,27 @@ runtime the first time you use a target.
|
|
|
88
88
|
| `BODY_SIZE_LIMIT` | `512K` | Max request body size (`K`/`M`/`G` suffixes allowed) |
|
|
89
89
|
| `IDLE_TIMEOUT` | `10` | Bun socket idle timeout in seconds (SSE responses opt out) |
|
|
90
90
|
| `SHUTDOWN_TIMEOUT` | `30` | Seconds to wait for in-flight requests on `SIGINT`/`SIGTERM` |
|
|
91
|
+
| `HEALTHCHECK_PATH` | `/_health`| Endpoint the `healthcheck` binary probes (must match the `healthcheck` option) |
|
|
92
|
+
| `HEALTHCHECK_TIMEOUT` | `2000` | `healthcheck` binary request timeout in ms |
|
|
91
93
|
|
|
92
94
|
Set `envPrefix` to namespace these (`envPrefix: "MY_APP_"` → `MY_APP_PORT`).
|
|
93
95
|
|
|
96
|
+
## Health check
|
|
97
|
+
|
|
98
|
+
With `healthcheck` enabled (the default) the build also produces
|
|
99
|
+
`build/healthcheck` — a tiny executable that requests `GET /_health` over
|
|
100
|
+
loopback (or the Unix socket) and exits `0` when the server answers `200`,
|
|
101
|
+
`1` otherwise. `GET /_health` returns `{ "status": "ok", uptime, rss, pid,
|
|
102
|
+
timestamp }`. Drop it straight into Docker:
|
|
103
|
+
|
|
104
|
+
```dockerfile
|
|
105
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
|
|
106
|
+
CMD ["./build/healthcheck"]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
It reads the same `HOST` / `PORT` / `SOCKET_PATH` as the server, so no extra
|
|
110
|
+
wiring is needed.
|
|
111
|
+
|
|
94
112
|
## Notes
|
|
95
113
|
|
|
96
114
|
- The SvelteKit server code is JavaScript emitted by Vite; `--compile` embeds it
|
|
@@ -98,3 +116,14 @@ Set `envPrefix` to namespace these (`envPrefix: "MY_APP_"` → `MY_APP_PORT`).
|
|
|
98
116
|
your dependencies are the one thing that can't be bundled this way.
|
|
99
117
|
- WebSockets, `read()` from `$app/server`, prerendering, and server
|
|
100
118
|
instrumentation are all supported.
|
|
119
|
+
|
|
120
|
+
## Releases
|
|
121
|
+
|
|
122
|
+
Automated by [semantic-release](https://semantic-release.gitbook.io/) from
|
|
123
|
+
[Conventional Commits](https://www.conventionalcommits.org/):
|
|
124
|
+
|
|
125
|
+
- `fix:` / `perf:` → **patch**, `feat:` → **minor**, `feat!:` or a
|
|
126
|
+
`BREAKING CHANGE:` footer → **major**
|
|
127
|
+
- `docs:` `refactor:` `test:` `chore:` `build:` `ci:` `style:` → **no release**
|
|
128
|
+
- `feat:` / `fix:` scoped to `ci`, `build`, `deps`, `dev`, `repo`, `test`,
|
|
129
|
+
`example`, `release` → **no release** (they don't touch the published package)
|
package/index.ts
CHANGED
|
@@ -44,6 +44,15 @@ export interface AdapterOptions {
|
|
|
44
44
|
* @default false
|
|
45
45
|
*/
|
|
46
46
|
precompress?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Compile a second tiny executable, `healthcheck`, alongside the server
|
|
49
|
+
* and expose a matching `GET` endpoint. The binary probes that endpoint
|
|
50
|
+
* over loopback (or the Unix socket) and exits `0` when healthy, `1`
|
|
51
|
+
* otherwise — ready to drop into a Docker `HEALTHCHECK`. Pass an object
|
|
52
|
+
* to change the endpoint path.
|
|
53
|
+
* @default true
|
|
54
|
+
*/
|
|
55
|
+
healthcheck?: boolean | { path?: string };
|
|
47
56
|
/**
|
|
48
57
|
* Prefix for this adapter's own runtime env vars (`PORT`, `HOST`,
|
|
49
58
|
* `ORIGIN`, ...).
|
|
@@ -112,7 +121,7 @@ const templates = fileURLToPath(new URL("./templates", import.meta.url));
|
|
|
112
121
|
* @example
|
|
113
122
|
* ```js
|
|
114
123
|
* // svelte.config.js
|
|
115
|
-
* import adapter from "@orochibraru/svelte-smol;
|
|
124
|
+
* import adapter from "@orochibraru/svelte-smol";
|
|
116
125
|
*
|
|
117
126
|
* export default {
|
|
118
127
|
* kit: {
|
|
@@ -136,13 +145,22 @@ export default function adapter(options: AdapterOptions = {}): Adapter {
|
|
|
136
145
|
minify = false,
|
|
137
146
|
sourcemap = false,
|
|
138
147
|
precompress = false,
|
|
148
|
+
healthcheck = true,
|
|
139
149
|
envPrefix = "",
|
|
140
150
|
serveAssets = true,
|
|
141
151
|
serveOptions = {},
|
|
142
152
|
} = options;
|
|
143
153
|
|
|
154
|
+
const healthcheckConfig =
|
|
155
|
+
healthcheck === false
|
|
156
|
+
? false
|
|
157
|
+
: {
|
|
158
|
+
path:
|
|
159
|
+
(healthcheck === true ? undefined : healthcheck.path) ?? "/_health",
|
|
160
|
+
};
|
|
161
|
+
|
|
144
162
|
return {
|
|
145
|
-
name: "
|
|
163
|
+
name: "@orochibraru/svelte-smol",
|
|
146
164
|
supports: {
|
|
147
165
|
instrumentation: () => true,
|
|
148
166
|
read: () => true,
|
|
@@ -187,7 +205,11 @@ export default function adapter(options: AdapterOptions = {}): Adapter {
|
|
|
187
205
|
builder.log.minor("Copying entrypoint");
|
|
188
206
|
builder.copy(templates, tmp, {
|
|
189
207
|
replace: {
|
|
190
|
-
BUILD_OPTIONS: JSON.stringify({
|
|
208
|
+
BUILD_OPTIONS: JSON.stringify({
|
|
209
|
+
serveAssets,
|
|
210
|
+
precompress,
|
|
211
|
+
healthcheck: healthcheckConfig,
|
|
212
|
+
}),
|
|
191
213
|
ENV: "./env.ts",
|
|
192
214
|
ENV_PREFIX: JSON.stringify(envPrefix),
|
|
193
215
|
HANDLER: "./handler.ts",
|
|
@@ -209,30 +231,36 @@ export default function adapter(options: AdapterOptions = {}): Adapter {
|
|
|
209
231
|
);
|
|
210
232
|
}
|
|
211
233
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
234
|
+
const compile = async (entrypoint: string, outName: string) => {
|
|
235
|
+
builder.log.minor(
|
|
236
|
+
target ? `Compiling ${outName} (${target})` : `Compiling ${outName}`,
|
|
237
|
+
);
|
|
238
|
+
const result = await Bun.build({
|
|
239
|
+
entrypoints: [entrypoint],
|
|
240
|
+
target: "bun",
|
|
241
|
+
minify,
|
|
242
|
+
bytecode,
|
|
243
|
+
sourcemap: sourcemap ? "linked" : "none",
|
|
244
|
+
compile: {
|
|
245
|
+
outfile: `${out}/${outName}`,
|
|
246
|
+
...(target ? { target } : {}),
|
|
247
|
+
},
|
|
248
|
+
});
|
|
227
249
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
250
|
+
if (!result.success) {
|
|
251
|
+
for (const message of result.logs) {
|
|
252
|
+
builder.log.error(String(message));
|
|
253
|
+
}
|
|
254
|
+
throw new Error(`\`bun build --compile\` failed for ${outName}`);
|
|
231
255
|
}
|
|
232
|
-
throw new Error("`bun build --compile` failed");
|
|
233
|
-
}
|
|
234
256
|
|
|
235
|
-
|
|
257
|
+
builder.log.success(`Compiled ${out}/${outName}`);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
await compile(entry, name);
|
|
261
|
+
if (healthcheckConfig) {
|
|
262
|
+
await compile(`${tmp}/healthcheck.ts`, "healthcheck");
|
|
263
|
+
}
|
|
236
264
|
},
|
|
237
265
|
};
|
|
238
266
|
}
|
package/internal.d.ts
CHANGED
|
@@ -34,5 +34,9 @@ declare module "SERVER" {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
declare const ENV_PREFIX: string;
|
|
37
|
-
declare const BUILD_OPTIONS: {
|
|
37
|
+
declare const BUILD_OPTIONS: {
|
|
38
|
+
serveAssets: boolean;
|
|
39
|
+
precompress: boolean;
|
|
40
|
+
healthcheck: false | { path: string };
|
|
41
|
+
};
|
|
38
42
|
declare const SERVE_OPTIONS: Record<string, unknown>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orochibraru/svelte-smol",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.ts",
|
|
6
6
|
"module": "./index.ts",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
22
|
-
"url": "https://github.
|
|
22
|
+
"url": "git+https://github.com/orochibraru/svelte-smol.git"
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|
|
@@ -36,13 +36,14 @@
|
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@biomejs/biome": "^2.5.11",
|
|
39
|
-
"@saithodev/semantic-release-gitea": "^2.1.0",
|
|
40
39
|
"@semantic-release/changelog": "^7.0.0",
|
|
41
40
|
"@semantic-release/git": "^11.0.1",
|
|
41
|
+
"@semantic-release/github": "^12.0.9",
|
|
42
42
|
"@semantic-release/npm": "^13.1.5",
|
|
43
43
|
"@sveltejs/kit": "^2.70.3",
|
|
44
44
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
45
45
|
"@types/bun": "^1.4.0",
|
|
46
|
+
"conventional-changelog-conventionalcommits": "^10.4.0",
|
|
46
47
|
"husky": "^9.1.7",
|
|
47
48
|
"semantic-release": "^25.0.9",
|
|
48
49
|
"svelte": "^5.56.10",
|
package/templates/env.ts
CHANGED
package/templates/handler.ts
CHANGED
|
@@ -11,7 +11,7 @@ const server = new Server(manifest) as SvelteKitServer & {
|
|
|
11
11
|
websocket?: () => Bun.WebSocketHandler<undefined> | undefined;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
const { serveAssets, precompress } = BUILD_OPTIONS;
|
|
14
|
+
const { serveAssets, precompress, healthcheck } = BUILD_OPTIONS;
|
|
15
15
|
|
|
16
16
|
const origin = env("ORIGIN", undefined);
|
|
17
17
|
const xff_depth = Number.parseInt(env("XFF_DEPTH", "1"), 10);
|
|
@@ -141,6 +141,23 @@ function serve_static(url: URL, request: Request): Response | undefined {
|
|
|
141
141
|
const ssr = async (request: Request, bunServer: Bun.Server<undefined>) => {
|
|
142
142
|
const url = new URL(request.url);
|
|
143
143
|
|
|
144
|
+
if (
|
|
145
|
+
healthcheck &&
|
|
146
|
+
request.method === "GET" &&
|
|
147
|
+
url.pathname === healthcheck.path
|
|
148
|
+
) {
|
|
149
|
+
return Response.json(
|
|
150
|
+
{
|
|
151
|
+
status: "ok",
|
|
152
|
+
uptime: Math.round(process.uptime()),
|
|
153
|
+
rss: process.memoryUsage.rss(),
|
|
154
|
+
pid: process.pid,
|
|
155
|
+
timestamp: new Date().toISOString(),
|
|
156
|
+
},
|
|
157
|
+
{ headers: { "cache-control": "no-store" } },
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
144
161
|
const asset = serve_static(url, request);
|
|
145
162
|
if (asset) {
|
|
146
163
|
return asset;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* global BUILD_OPTIONS */
|
|
2
|
+
|
|
3
|
+
import { env } from "ENV";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
|
|
6
|
+
const { healthcheck } = BUILD_OPTIONS;
|
|
7
|
+
|
|
8
|
+
if (!healthcheck) {
|
|
9
|
+
console.error("healthcheck is disabled for this build");
|
|
10
|
+
process.exit(2);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const socket = env("SOCKET_PATH", false);
|
|
14
|
+
const timeout_ms = Number.parseInt(env("HEALTHCHECK_TIMEOUT", "2000"), 10);
|
|
15
|
+
const path = env("HEALTHCHECK_PATH", healthcheck.path);
|
|
16
|
+
|
|
17
|
+
// A server bound to a wildcard address is reached over loopback.
|
|
18
|
+
const raw_host = env("HOST", "0.0.0.0");
|
|
19
|
+
const host =
|
|
20
|
+
raw_host === "0.0.0.0" || raw_host === "::" || raw_host === ""
|
|
21
|
+
? "127.0.0.1"
|
|
22
|
+
: raw_host;
|
|
23
|
+
const port = env("PORT", "3000");
|
|
24
|
+
|
|
25
|
+
const url = socket
|
|
26
|
+
? `http://localhost${path}`
|
|
27
|
+
: `http://${host.includes(":") ? `[${host}]` : host}:${port}${path}`;
|
|
28
|
+
|
|
29
|
+
function fail(reason: string): never {
|
|
30
|
+
console.error(`unhealthy: ${reason}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const response = await fetch(url, {
|
|
36
|
+
headers: { "user-agent": "svelte-smol-healthcheck" },
|
|
37
|
+
signal: AbortSignal.timeout(timeout_ms),
|
|
38
|
+
...(socket ? { unix: socket } : {}),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
fail(`${url} -> ${response.status}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const body = (await response.json().catch(() => null)) as {
|
|
46
|
+
status?: string;
|
|
47
|
+
} | null;
|
|
48
|
+
if (body?.status && body.status !== "ok") {
|
|
49
|
+
fail(`status=${body.status}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
process.exit(0);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
fail(`${url} -> ${error instanceof Error ? error.message : String(error)}`);
|
|
55
|
+
}
|
package/templates/index.ts
CHANGED
|
@@ -25,12 +25,26 @@ const options = {
|
|
|
25
25
|
...(websocket ? { websocket } : {}),
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
const shutdown_timeout_ms =
|
|
29
|
+
Number.parseInt(env("SHUTDOWN_TIMEOUT", "30"), 10) * 1000;
|
|
30
|
+
|
|
28
31
|
const server = Bun.serve(options as Parameters<typeof Bun.serve>[0]);
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
const rows: Array<[string, string]> = [
|
|
34
|
+
["Listening on", path ? `unix:${path}` : `${server.url}`],
|
|
35
|
+
["WebSocket", websocket ? "enabled" : "disabled"],
|
|
36
|
+
["Body limit", format_bytes(body_size_limit)],
|
|
37
|
+
["Idle timeout", `${idle_timeout}s`],
|
|
38
|
+
["Shutdown grace", `${shutdown_timeout_ms / 1000}s`],
|
|
39
|
+
["Runtime", `Bun ${Bun.version} (${process.platform}/${process.arch})`],
|
|
40
|
+
["PID", `${process.pid}`],
|
|
41
|
+
];
|
|
42
|
+
console.log(
|
|
43
|
+
`\n SvelteKit server ready\n\n${rows
|
|
44
|
+
.map(([label, value]) => ` ${`${label}:`.padEnd(16)}${value}`)
|
|
45
|
+
.join("\n")}\n`,
|
|
46
|
+
);
|
|
31
47
|
|
|
32
|
-
const shutdown_timeout_ms =
|
|
33
|
-
Number.parseInt(env("SHUTDOWN_TIMEOUT", "30"), 10) * 1000;
|
|
34
48
|
let shutting_down = false;
|
|
35
49
|
|
|
36
50
|
async function graceful_shutdown(reason: "SIGINT" | "SIGTERM" | "IDLE") {
|
|
@@ -74,3 +88,14 @@ function parse_as_bytes(value: string): number {
|
|
|
74
88
|
}[units ?? "B"] ?? 1;
|
|
75
89
|
return Number(multiplier !== 1 ? value.slice(0, -1) : value) * multiplier;
|
|
76
90
|
}
|
|
91
|
+
|
|
92
|
+
function format_bytes(bytes: number): string {
|
|
93
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
94
|
+
let value = bytes;
|
|
95
|
+
let unit = 0;
|
|
96
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
97
|
+
value /= 1024;
|
|
98
|
+
unit++;
|
|
99
|
+
}
|
|
100
|
+
return `${Number.isInteger(value) ? value : value.toFixed(1)} ${units[unit]}`;
|
|
101
|
+
}
|