@shaferllc/keel 0.36.0 → 0.58.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 +14 -2
- package/dist/core/application.d.ts +58 -2
- package/dist/core/application.js +99 -3
- package/dist/core/authorization.d.ts +52 -0
- package/dist/core/authorization.js +97 -0
- package/dist/core/broadcasting.d.ts +49 -0
- package/dist/core/broadcasting.js +84 -0
- package/dist/core/broker.d.ts +398 -0
- package/dist/core/broker.js +602 -0
- package/dist/core/crypto.d.ts +1 -1
- package/dist/core/crypto.js +12 -3
- package/dist/core/database.d.ts +26 -1
- package/dist/core/database.js +65 -2
- package/dist/core/decorators.d.ts +39 -0
- package/dist/core/decorators.js +72 -0
- package/dist/core/exceptions.d.ts +77 -6
- package/dist/core/exceptions.js +168 -10
- package/dist/core/helpers.d.ts +6 -0
- package/dist/core/helpers.js +12 -0
- package/dist/core/http/kernel.js +14 -2
- package/dist/core/http/router.d.ts +14 -0
- package/dist/core/http/router.js +30 -1
- package/dist/core/index.d.ts +28 -6
- package/dist/core/index.js +15 -3
- package/dist/core/logger.d.ts +5 -0
- package/dist/core/logger.js +24 -2
- package/dist/core/migrations.js +3 -3
- package/dist/core/model.d.ts +19 -1
- package/dist/core/model.js +72 -4
- package/dist/core/provider.d.ts +13 -4
- package/dist/core/provider.js +12 -2
- package/dist/core/redis.d.ts +78 -0
- package/dist/core/redis.js +176 -0
- package/dist/core/request-logger.d.ts +26 -0
- package/dist/core/request-logger.js +48 -0
- package/dist/core/request.d.ts +17 -1
- package/dist/core/request.js +27 -1
- package/dist/core/scheduler.d.ts +60 -0
- package/dist/core/scheduler.js +166 -0
- package/dist/core/session.js +17 -2
- package/dist/core/storage.d.ts +57 -0
- package/dist/core/storage.js +98 -0
- package/dist/core/template.d.ts +50 -0
- package/dist/core/template.js +753 -0
- package/dist/core/testing.d.ts +54 -0
- package/dist/core/testing.js +141 -0
- package/dist/core/transformer.d.ts +89 -0
- package/dist/core/transformer.js +152 -0
- package/dist/core/validation.d.ts +20 -0
- package/dist/core/validation.js +52 -1
- package/dist/core/vite.d.ts +117 -0
- package/dist/core/vite.js +258 -0
- package/dist/vite/index.d.ts +40 -0
- package/dist/vite/index.js +146 -0
- package/package.json +16 -1
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite integration. Wire a modern frontend build — bundling, hashed filenames,
|
|
3
|
+
* hot module reload — to Keel's server-rendered HTML, the way modern full-stack
|
|
4
|
+
* frameworks do. `Vite` is the *server* half: it emits the `<script>`/`<link>`
|
|
5
|
+
* tags for your entrypoints and resolves individual asset URLs, switching
|
|
6
|
+
* automatically between two modes:
|
|
7
|
+
*
|
|
8
|
+
* • Dev — a `public/hot` file (written by the `keelVite()` plugin when the
|
|
9
|
+
* Vite dev server starts) points at `http://localhost:5173`. Tags load
|
|
10
|
+
* straight from that server, with HMR.
|
|
11
|
+
* • Prod — no hot file, so it reads `public/assets/.vite/manifest.json` and
|
|
12
|
+
* emits the hashed, split, preloaded production tags.
|
|
13
|
+
*
|
|
14
|
+
* // in a service provider
|
|
15
|
+
* const vite = await new Vite({ entrypoints: ["resources/js/app.ts"] }).loadFromDisk();
|
|
16
|
+
* singleton(Vite, () => vite);
|
|
17
|
+
*
|
|
18
|
+
* // in a JSX layout <head>
|
|
19
|
+
* {viteReactRefresh()}
|
|
20
|
+
* {viteTags("resources/js/app.ts")}
|
|
21
|
+
*
|
|
22
|
+
* Tag generation is pure string work over an in-memory manifest, so it's
|
|
23
|
+
* edge-safe: on Workers, skip `loadFromDisk` and hand the bundled manifest in
|
|
24
|
+
* with `useManifest(...)`. Only reading from disk touches `node:fs`, and that's
|
|
25
|
+
* imported dynamically (like the static server), so the core still loads on the
|
|
26
|
+
* edge.
|
|
27
|
+
*/
|
|
28
|
+
import { raw } from "hono/html";
|
|
29
|
+
import { bound, make } from "./helpers.js";
|
|
30
|
+
const STYLE_EXTENSIONS = /\.(css|less|sass|scss|styl|stylus|pcss|postcss)(\?|$)/;
|
|
31
|
+
export class Vite {
|
|
32
|
+
entrypoints;
|
|
33
|
+
hotFile;
|
|
34
|
+
manifestFile;
|
|
35
|
+
assetsUrl;
|
|
36
|
+
scriptAttributes;
|
|
37
|
+
styleAttributes;
|
|
38
|
+
hotUrl = null;
|
|
39
|
+
parsed = null;
|
|
40
|
+
fs = null;
|
|
41
|
+
constructor(options = {}) {
|
|
42
|
+
this.entrypoints = normalizeList(options.entrypoints);
|
|
43
|
+
this.hotFile = options.hotFile ?? "public/hot";
|
|
44
|
+
const buildDirectory = trimSlashes(options.buildDirectory ?? "public/assets");
|
|
45
|
+
this.manifestFile = options.manifestFile ?? `${buildDirectory}/.vite/manifest.json`;
|
|
46
|
+
this.assetsUrl = (options.assetsUrl ?? "/assets").replace(/\/+$/, "");
|
|
47
|
+
this.scriptAttributes = options.scriptAttributes ?? {};
|
|
48
|
+
this.styleAttributes = options.styleAttributes ?? {};
|
|
49
|
+
}
|
|
50
|
+
/* ------------------------------- loading -------------------------------- */
|
|
51
|
+
/**
|
|
52
|
+
* Read the hot file (dev) or the build manifest (prod) from disk. Call once at
|
|
53
|
+
* boot from a service provider. Node only — uses `node:fs`.
|
|
54
|
+
*/
|
|
55
|
+
async loadFromDisk() {
|
|
56
|
+
this.fs = await import("node:fs");
|
|
57
|
+
this.refreshHot();
|
|
58
|
+
if (!this.hotUrl) {
|
|
59
|
+
// No dev server — try the production manifest. Leave it null if there's no
|
|
60
|
+
// build yet; tag generation raises a clear error when it's actually used.
|
|
61
|
+
try {
|
|
62
|
+
this.parsed = JSON.parse(this.fs.readFileSync(this.manifestFile, "utf8"));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
this.parsed = null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
/** Inject a manifest directly — for the edge, where there's no filesystem. */
|
|
71
|
+
useManifest(manifest) {
|
|
72
|
+
this.parsed = manifest;
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
/** Force the dev-server URL (or `null` for prod). Bypasses the hot file. */
|
|
76
|
+
useHotUrl(url) {
|
|
77
|
+
this.hotUrl = url ? url.replace(/\/+$/, "") : null;
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
/** Re-read the hot file if we're on Node and no manifest has claimed prod mode. */
|
|
81
|
+
refreshHot() {
|
|
82
|
+
if (!this.fs)
|
|
83
|
+
return;
|
|
84
|
+
try {
|
|
85
|
+
const contents = this.fs.readFileSync(this.hotFile, "utf8").trim();
|
|
86
|
+
this.hotUrl = contents ? contents.replace(/\/+$/, "") : null;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
this.hotUrl = null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** The dev-server URL when running Vite, else `null` (production). */
|
|
93
|
+
hot() {
|
|
94
|
+
// A loaded manifest means we built for production — never dev.
|
|
95
|
+
if (this.parsed)
|
|
96
|
+
return null;
|
|
97
|
+
this.refreshHot();
|
|
98
|
+
return this.hotUrl;
|
|
99
|
+
}
|
|
100
|
+
/** The parsed production manifest. Throws if it hasn't been loaded. */
|
|
101
|
+
manifest() {
|
|
102
|
+
if (!this.parsed) {
|
|
103
|
+
throw new Error(`Vite manifest not found at "${this.manifestFile}". Run the client build ` +
|
|
104
|
+
`(e.g. \`vite build\`) or start the dev server.`);
|
|
105
|
+
}
|
|
106
|
+
return this.parsed;
|
|
107
|
+
}
|
|
108
|
+
/* ---------------------------- tag generation ---------------------------- */
|
|
109
|
+
/**
|
|
110
|
+
* The `<script>`/`<link>` tags for one or more entrypoints — dev-server URLs
|
|
111
|
+
* with HMR while developing, hashed + preloaded tags in production. Falls back
|
|
112
|
+
* to the entrypoints passed to the constructor.
|
|
113
|
+
*/
|
|
114
|
+
generateEntryPointsTags(entrypoints) {
|
|
115
|
+
const entries = entrypoints !== undefined ? normalizeList(entrypoints) : this.entrypoints;
|
|
116
|
+
const hot = this.hot();
|
|
117
|
+
const tags = hot ? this.devTags(hot, entries) : this.productionTags(entries);
|
|
118
|
+
return raw(tags.join("\n"));
|
|
119
|
+
}
|
|
120
|
+
/** The public URL for a single asset (an image, a font) — dev or hashed prod. */
|
|
121
|
+
assetPath(asset) {
|
|
122
|
+
const hot = this.hot();
|
|
123
|
+
if (hot)
|
|
124
|
+
return `${hot}/${trimSlashes(asset)}`;
|
|
125
|
+
const chunk = this.manifest()[asset];
|
|
126
|
+
if (!chunk) {
|
|
127
|
+
throw new Error(`Unable to locate "${asset}" in the Vite manifest.`);
|
|
128
|
+
}
|
|
129
|
+
return `${this.assetsUrl}/${chunk.file}`;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The React Fast Refresh preamble — required before your entry script when
|
|
133
|
+
* using `@vitejs/plugin-react`. Empty in production.
|
|
134
|
+
*/
|
|
135
|
+
reactHMR() {
|
|
136
|
+
const hot = this.hot();
|
|
137
|
+
if (!hot)
|
|
138
|
+
return raw("");
|
|
139
|
+
return raw(`<script type="module">\n` +
|
|
140
|
+
`import RefreshRuntime from "${hot}/@react-refresh"\n` +
|
|
141
|
+
`RefreshRuntime.injectIntoGlobalHook(window)\n` +
|
|
142
|
+
`window.$RefreshReg$ = () => {}\n` +
|
|
143
|
+
`window.$RefreshSig$ = () => (type) => type\n` +
|
|
144
|
+
`window.__vite_plugin_react_preamble_installed__ = true\n` +
|
|
145
|
+
`</script>`);
|
|
146
|
+
}
|
|
147
|
+
/* ------------------------------ internals ------------------------------- */
|
|
148
|
+
devTags(hot, entries) {
|
|
149
|
+
// The Vite client (its own module) drives HMR — always first.
|
|
150
|
+
const tags = [`<script type="module" src="${hot}/@vite/client"></script>`];
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
const url = `${hot}/${trimSlashes(entry)}`;
|
|
153
|
+
tags.push(this.tagFor(entry, url));
|
|
154
|
+
}
|
|
155
|
+
return tags;
|
|
156
|
+
}
|
|
157
|
+
productionTags(entries) {
|
|
158
|
+
const manifest = this.manifest();
|
|
159
|
+
const styles = [];
|
|
160
|
+
const preloads = [];
|
|
161
|
+
const scripts = [];
|
|
162
|
+
const seen = new Set();
|
|
163
|
+
const walk = (name, isEntry) => {
|
|
164
|
+
const chunk = manifest[name];
|
|
165
|
+
if (!chunk || seen.has(name))
|
|
166
|
+
return;
|
|
167
|
+
seen.add(name);
|
|
168
|
+
for (const imported of chunk.imports ?? [])
|
|
169
|
+
walk(imported, false);
|
|
170
|
+
for (const css of chunk.css ?? []) {
|
|
171
|
+
const url = `${this.assetsUrl}/${css}`;
|
|
172
|
+
pushUnique(styles, this.styleTag(css, url));
|
|
173
|
+
}
|
|
174
|
+
const url = `${this.assetsUrl}/${chunk.file}`;
|
|
175
|
+
if (isEntry)
|
|
176
|
+
pushUnique(scripts, this.scriptTag(name, url));
|
|
177
|
+
else
|
|
178
|
+
pushUnique(preloads, `<link rel="modulepreload" href="${url}">`);
|
|
179
|
+
};
|
|
180
|
+
for (const entry of entries) {
|
|
181
|
+
const chunk = manifest[entry];
|
|
182
|
+
if (!chunk) {
|
|
183
|
+
throw new Error(`Entrypoint "${entry}" is not in the Vite manifest.`);
|
|
184
|
+
}
|
|
185
|
+
walk(entry, true);
|
|
186
|
+
}
|
|
187
|
+
// Styles, then preloads, then the entry scripts.
|
|
188
|
+
return [...styles, ...preloads, ...scripts];
|
|
189
|
+
}
|
|
190
|
+
/** In dev, a style entrypoint is a `<link>`; everything else a module script. */
|
|
191
|
+
tagFor(entry, url) {
|
|
192
|
+
return STYLE_EXTENSIONS.test(entry) ? this.styleTag(entry, url) : this.scriptTag(entry, url);
|
|
193
|
+
}
|
|
194
|
+
scriptTag(src, url) {
|
|
195
|
+
const attrs = renderAttributes({ type: "module", src: url, ...resolveAttributes(this.scriptAttributes, src, url) });
|
|
196
|
+
return `<script${attrs}></script>`;
|
|
197
|
+
}
|
|
198
|
+
styleTag(src, url) {
|
|
199
|
+
const attrs = renderAttributes({ rel: "stylesheet", href: url, ...resolveAttributes(this.styleAttributes, src, url) });
|
|
200
|
+
return `<link${attrs}>`;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/* -------------------------------- helpers --------------------------------- */
|
|
204
|
+
function resolveAttributes(attributes, src, url) {
|
|
205
|
+
const resolved = typeof attributes === "function" ? attributes({ src, url }) : attributes;
|
|
206
|
+
return resolved ?? {};
|
|
207
|
+
}
|
|
208
|
+
function renderAttributes(attributes) {
|
|
209
|
+
const parts = [];
|
|
210
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
211
|
+
if (value === false || value == null)
|
|
212
|
+
continue;
|
|
213
|
+
if (value === true)
|
|
214
|
+
parts.push(key);
|
|
215
|
+
else
|
|
216
|
+
parts.push(`${key}="${escapeAttribute(String(value))}"`);
|
|
217
|
+
}
|
|
218
|
+
return parts.length ? ` ${parts.join(" ")}` : "";
|
|
219
|
+
}
|
|
220
|
+
function escapeAttribute(value) {
|
|
221
|
+
return value
|
|
222
|
+
.replace(/&/g, "&")
|
|
223
|
+
.replace(/"/g, """)
|
|
224
|
+
.replace(/</g, "<")
|
|
225
|
+
.replace(/>/g, ">");
|
|
226
|
+
}
|
|
227
|
+
function normalizeList(value) {
|
|
228
|
+
if (value == null)
|
|
229
|
+
return [];
|
|
230
|
+
return Array.isArray(value) ? value : [value];
|
|
231
|
+
}
|
|
232
|
+
function trimSlashes(value) {
|
|
233
|
+
return value.replace(/^\/+|\/+$/g, "");
|
|
234
|
+
}
|
|
235
|
+
function pushUnique(list, value) {
|
|
236
|
+
if (!list.includes(value))
|
|
237
|
+
list.push(value);
|
|
238
|
+
}
|
|
239
|
+
/* -------------------------------- globals --------------------------------- */
|
|
240
|
+
function resolveVite() {
|
|
241
|
+
if (!bound(Vite)) {
|
|
242
|
+
throw new Error("Vite is not configured. Bind it in a provider: " +
|
|
243
|
+
'singleton(Vite, () => new Vite({ entrypoints: ["resources/js/app.ts"] })).');
|
|
244
|
+
}
|
|
245
|
+
return make(Vite);
|
|
246
|
+
}
|
|
247
|
+
/** The entrypoint tags for the current app's Vite instance. Use in a JSX `<head>`. */
|
|
248
|
+
export function viteTags(entrypoints) {
|
|
249
|
+
return resolveVite().generateEntryPointsTags(entrypoints);
|
|
250
|
+
}
|
|
251
|
+
/** The URL for a single asset through the current app's Vite instance. */
|
|
252
|
+
export function viteAsset(asset) {
|
|
253
|
+
return resolveVite().assetPath(asset);
|
|
254
|
+
}
|
|
255
|
+
/** The React Fast Refresh preamble (dev only) for the current app's Vite instance. */
|
|
256
|
+
export function viteReactRefresh() {
|
|
257
|
+
return resolveVite().reactHMR();
|
|
258
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Keel Vite plugin — the build-time half of Keel's Vite integration. Drop it
|
|
3
|
+
* into `vite.config.ts` and it wires Vite to the `Vite` server service in
|
|
4
|
+
* `@shaferllc/keel/core`:
|
|
5
|
+
*
|
|
6
|
+
* import { defineConfig } from "vite";
|
|
7
|
+
* import { keelVite } from "@shaferllc/keel/vite";
|
|
8
|
+
*
|
|
9
|
+
* export default defineConfig({
|
|
10
|
+
* plugins: [keelVite({ entrypoints: ["resources/js/app.ts"] })],
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* It does three things:
|
|
14
|
+
* • Configures the build — manifest on, output to `public/assets`, your
|
|
15
|
+
* entrypoints as the Rollup inputs, and `base` set to `assetsUrl` so hashed
|
|
16
|
+
* asset URLs resolve in production.
|
|
17
|
+
* • Writes a `public/hot` file with the dev-server URL while `vite` runs (and
|
|
18
|
+
* removes it on exit) — the marker the `Vite` service reads to switch into
|
|
19
|
+
* dev mode with HMR.
|
|
20
|
+
* • Optionally triggers a full browser reload when files matching `reload`
|
|
21
|
+
* globs change (e.g. server-rendered views Vite doesn't otherwise watch).
|
|
22
|
+
*
|
|
23
|
+
* This module runs only in the Node build environment (never on the edge), so it
|
|
24
|
+
* imports `node:fs`/`node:path` directly.
|
|
25
|
+
*/
|
|
26
|
+
import type { Plugin } from "vite";
|
|
27
|
+
export interface KeelViteOptions {
|
|
28
|
+
/** Entry files, each producing its own bundle. Required. */
|
|
29
|
+
entrypoints: string | string[];
|
|
30
|
+
/** Build output directory (must match the server's `buildDirectory`). Default: `public/assets`. */
|
|
31
|
+
buildDirectory?: string;
|
|
32
|
+
/** The dev-server marker file the `Vite` service reads. Default: `public/hot`. */
|
|
33
|
+
hotFile?: string;
|
|
34
|
+
/** Public URL / CDN base for built assets. Default: `/assets`. */
|
|
35
|
+
assetsUrl?: string;
|
|
36
|
+
/** Glob patterns that trigger a full page reload when changed (e.g. views). */
|
|
37
|
+
reload?: string[];
|
|
38
|
+
}
|
|
39
|
+
/** The Keel Vite plugin(s). Spread-safe: returns an array to add to `plugins`. */
|
|
40
|
+
export declare function keelVite(options: KeelViteOptions): Plugin[];
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Keel Vite plugin — the build-time half of Keel's Vite integration. Drop it
|
|
3
|
+
* into `vite.config.ts` and it wires Vite to the `Vite` server service in
|
|
4
|
+
* `@shaferllc/keel/core`:
|
|
5
|
+
*
|
|
6
|
+
* import { defineConfig } from "vite";
|
|
7
|
+
* import { keelVite } from "@shaferllc/keel/vite";
|
|
8
|
+
*
|
|
9
|
+
* export default defineConfig({
|
|
10
|
+
* plugins: [keelVite({ entrypoints: ["resources/js/app.ts"] })],
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* It does three things:
|
|
14
|
+
* • Configures the build — manifest on, output to `public/assets`, your
|
|
15
|
+
* entrypoints as the Rollup inputs, and `base` set to `assetsUrl` so hashed
|
|
16
|
+
* asset URLs resolve in production.
|
|
17
|
+
* • Writes a `public/hot` file with the dev-server URL while `vite` runs (and
|
|
18
|
+
* removes it on exit) — the marker the `Vite` service reads to switch into
|
|
19
|
+
* dev mode with HMR.
|
|
20
|
+
* • Optionally triggers a full browser reload when files matching `reload`
|
|
21
|
+
* globs change (e.g. server-rendered views Vite doesn't otherwise watch).
|
|
22
|
+
*
|
|
23
|
+
* This module runs only in the Node build environment (never on the edge), so it
|
|
24
|
+
* imports `node:fs`/`node:path` directly.
|
|
25
|
+
*/
|
|
26
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
27
|
+
import { dirname } from "node:path";
|
|
28
|
+
/** The Keel Vite plugin(s). Spread-safe: returns an array to add to `plugins`. */
|
|
29
|
+
export function keelVite(options) {
|
|
30
|
+
const entrypoints = toArray(options.entrypoints);
|
|
31
|
+
if (entrypoints.length === 0) {
|
|
32
|
+
throw new Error("keelVite: `entrypoints` must list at least one entry file.");
|
|
33
|
+
}
|
|
34
|
+
const buildDirectory = trimTrailingSlash(options.buildDirectory ?? "public/assets");
|
|
35
|
+
const hotFile = options.hotFile ?? "public/hot";
|
|
36
|
+
const assetsUrl = options.assetsUrl ?? "/assets";
|
|
37
|
+
const plugins = [
|
|
38
|
+
{
|
|
39
|
+
name: "keel:vite",
|
|
40
|
+
enforce: "post",
|
|
41
|
+
config(user, env) {
|
|
42
|
+
const build = env.command === "build";
|
|
43
|
+
return {
|
|
44
|
+
// Dev serves from the root; the build prefixes hashed assets with assetsUrl.
|
|
45
|
+
base: build ? withTrailingSlash(assetsUrl) : "/",
|
|
46
|
+
// Keel serves `public/` itself — don't let Vite copy it into the bundle.
|
|
47
|
+
publicDir: user.publicDir ?? false,
|
|
48
|
+
build: {
|
|
49
|
+
manifest: user.build?.manifest ?? true,
|
|
50
|
+
outDir: user.build?.outDir ?? buildDirectory,
|
|
51
|
+
// Flat output: assetsUrl already namespaces the files, so don't nest
|
|
52
|
+
// them under another `assets/` folder.
|
|
53
|
+
assetsDir: user.build?.assetsDir ?? "",
|
|
54
|
+
emptyOutDir: user.build?.emptyOutDir ?? true,
|
|
55
|
+
rollupOptions: {
|
|
56
|
+
...user.build?.rollupOptions,
|
|
57
|
+
input: user.build?.rollupOptions?.input ?? entrypoints,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
configureServer(server) {
|
|
63
|
+
const write = () => {
|
|
64
|
+
mkdirSync(dirname(hotFile), { recursive: true });
|
|
65
|
+
writeFileSync(hotFile, devServerUrl(server));
|
|
66
|
+
};
|
|
67
|
+
const clean = () => {
|
|
68
|
+
try {
|
|
69
|
+
rmSync(hotFile);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// already gone — nothing to do
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
server.httpServer?.once("listening", write);
|
|
76
|
+
server.httpServer?.once("close", clean);
|
|
77
|
+
process.once("exit", clean);
|
|
78
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
79
|
+
process.once(signal, () => {
|
|
80
|
+
clean();
|
|
81
|
+
process.exit();
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
if (options.reload && options.reload.length > 0) {
|
|
88
|
+
const matchers = options.reload.map(globToRegExp);
|
|
89
|
+
plugins.push({
|
|
90
|
+
name: "keel:vite-reload",
|
|
91
|
+
handleHotUpdate({ file, server }) {
|
|
92
|
+
if (matchers.some((re) => re.test(file))) {
|
|
93
|
+
server.ws.send({ type: "full-reload", path: "*" });
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return plugins;
|
|
99
|
+
}
|
|
100
|
+
/* -------------------------------- helpers --------------------------------- */
|
|
101
|
+
function devServerUrl(server) {
|
|
102
|
+
const address = server.httpServer?.address();
|
|
103
|
+
const port = address && typeof address === "object" ? address.port : 5173;
|
|
104
|
+
const https = Boolean(server.config.server.https);
|
|
105
|
+
const protocol = https ? "https" : "http";
|
|
106
|
+
const configured = server.config.server.host;
|
|
107
|
+
const host = typeof configured === "string" ? configured : "localhost";
|
|
108
|
+
return `${protocol}://${host}:${port}`;
|
|
109
|
+
}
|
|
110
|
+
function toArray(value) {
|
|
111
|
+
return Array.isArray(value) ? value : [value];
|
|
112
|
+
}
|
|
113
|
+
function trimTrailingSlash(value) {
|
|
114
|
+
return value.replace(/\/+$/, "");
|
|
115
|
+
}
|
|
116
|
+
function withTrailingSlash(value) {
|
|
117
|
+
return value.endsWith("/") ? value : `${value}/`;
|
|
118
|
+
}
|
|
119
|
+
/** A small glob → RegExp for `reload` patterns: supports `**`, `*`, and `?`. */
|
|
120
|
+
function globToRegExp(glob) {
|
|
121
|
+
let re = "";
|
|
122
|
+
for (let i = 0; i < glob.length; i++) {
|
|
123
|
+
const char = glob[i];
|
|
124
|
+
if (char === "*") {
|
|
125
|
+
if (glob[i + 1] === "*") {
|
|
126
|
+
re += ".*";
|
|
127
|
+
i++;
|
|
128
|
+
if (glob[i + 1] === "/")
|
|
129
|
+
i++; // collapse `**/`
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
re += "[^/]*";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else if (char === "?") {
|
|
136
|
+
re += "[^/]";
|
|
137
|
+
}
|
|
138
|
+
else if ("\\^$.|+()[]{}".includes(char)) {
|
|
139
|
+
re += `\\${char}`;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
re += char;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return new RegExp(`${re}$`);
|
|
146
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shaferllc/keel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The house framework for Node.js — a service container, providers, routing, JSX views, and a code-generating console.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"./core": {
|
|
25
25
|
"types": "./dist/core/index.d.ts",
|
|
26
26
|
"import": "./dist/core/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./vite": {
|
|
29
|
+
"types": "./dist/vite/index.d.ts",
|
|
30
|
+
"import": "./dist/vite/index.js"
|
|
27
31
|
}
|
|
28
32
|
},
|
|
29
33
|
"files": [
|
|
@@ -41,18 +45,29 @@
|
|
|
41
45
|
"test": "node --import tsx --test tests/*.test.ts",
|
|
42
46
|
"test:coverage": "node --import tsx --test --experimental-test-coverage --test-coverage-exclude='tests/**' tests/*.test.ts",
|
|
43
47
|
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
48
|
+
"dev:client": "vite",
|
|
49
|
+
"build:client": "vite build",
|
|
44
50
|
"prepare": "npm run build"
|
|
45
51
|
},
|
|
46
52
|
"dependencies": {
|
|
47
53
|
"dotenv": "^16.4.7",
|
|
48
54
|
"hono": "^4.6.14"
|
|
49
55
|
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"vite": "^5 || ^6 || ^7"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"vite": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
50
64
|
"devDependencies": {
|
|
51
65
|
"@hono/node-server": "^1.13.7",
|
|
52
66
|
"@types/node": "^26.1.1",
|
|
53
67
|
"commander": "^12.1.0",
|
|
54
68
|
"tsx": "^4.19.2",
|
|
55
69
|
"typescript": "^5.7.2",
|
|
70
|
+
"vite": "^6.4.3",
|
|
56
71
|
"zod": "^4.4.3"
|
|
57
72
|
}
|
|
58
73
|
}
|