@cosmicdrift/kumiko-dev-server 0.387.0 → 0.388.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.
Files changed (42) hide show
  1. package/dist/build-server-bundle.d.ts +1 -1
  2. package/dist/build-server-bundle.js +2 -2
  3. package/dist/create-kumiko-server-client-bundles.d.ts +21 -0
  4. package/dist/create-kumiko-server-client-bundles.js +97 -0
  5. package/dist/create-kumiko-server-html.d.ts +21 -0
  6. package/dist/create-kumiko-server-html.js +132 -0
  7. package/dist/create-kumiko-server-public-files.d.ts +6 -0
  8. package/dist/create-kumiko-server-public-files.js +96 -0
  9. package/dist/create-kumiko-server-reload.d.ts +14 -0
  10. package/dist/create-kumiko-server-reload.js +73 -0
  11. package/dist/create-kumiko-server-routes.d.ts +16 -0
  12. package/dist/create-kumiko-server-routes.js +206 -0
  13. package/dist/create-kumiko-server-shutdown.d.ts +14 -0
  14. package/dist/create-kumiko-server-shutdown.js +72 -0
  15. package/dist/create-kumiko-server-stack.d.ts +12 -0
  16. package/dist/create-kumiko-server-stack.js +101 -0
  17. package/dist/create-kumiko-server-stylesheet.d.ts +10 -0
  18. package/dist/create-kumiko-server-stylesheet.js +137 -0
  19. package/dist/create-kumiko-server-watcher.d.ts +22 -0
  20. package/dist/create-kumiko-server-watcher.js +155 -0
  21. package/dist/create-kumiko-server.d.ts +6 -20
  22. package/dist/create-kumiko-server.js +59 -1046
  23. package/dist/dev-server-runtime.d.ts +5 -0
  24. package/dist/dev-server-runtime.js +11 -0
  25. package/dist/index.d.ts +3 -1
  26. package/dist/index.js +3 -1
  27. package/dist/managed-files/app-jobs.js +1 -1
  28. package/dist/managed-files/check.js +1 -1
  29. package/dist/run-dev-app-auth.d.ts +38 -0
  30. package/dist/run-dev-app-auth.js +155 -0
  31. package/dist/run-dev-app-config.d.ts +26 -0
  32. package/dist/run-dev-app-config.js +185 -0
  33. package/dist/run-dev-app-server-options.d.ts +13 -0
  34. package/dist/run-dev-app-server-options.js +49 -0
  35. package/dist/run-dev-app.d.ts +4 -4
  36. package/dist/run-dev-app.js +86 -459
  37. package/dist/schema-apply.d.ts +1 -1
  38. package/dist/schema-apply.js +4 -4
  39. package/dist/write-server-lockfile.js +23 -16
  40. package/package.json +9 -9
  41. package/src/changes.json +30 -0
  42. package/templates/managed/ci-public.yml.template +4 -0
@@ -0,0 +1,155 @@
1
+ /// <reference types="bun-types" preserve="true" />
2
+ import { statSync } from "node:fs";
3
+ import { watch } from "node:fs/promises";
4
+ import { resolve } from "node:path";
5
+ import { rebuildClientBundles, } from "./create-kumiko-server-client-bundles.js";
6
+ import { broadcastReload } from "./create-kumiko-server-reload.js";
7
+ import { logInfo } from "./dev-server-runtime.js";
8
+ async function watchDir(dir, onChange, signal, watchEvents) {
9
+ // The server's stop() aborts the signal; without it, the for-await loop
10
+ // would continue until process exit. During test teardown, removing the
11
+ // temporary directory could then be classified as a restart and call
12
+ // process.exit(75), surfacing as an unhandled Vitest error.
13
+ const watcher = watchEvents(dir, { recursive: true, signal });
14
+ try {
15
+ for await (const ev of watcher) {
16
+ if (ev.filename)
17
+ onChange(ev.filename);
18
+ }
19
+ }
20
+ catch (err) {
21
+ // signal.abort() causes the async iterator to throw AbortError during
22
+ // expected teardown; other errors must propagate.
23
+ // skip: AbortSignal fired the abort, this is expected teardown not a real error
24
+ if (typeof err === "object" && err !== null && "name" in err && err.name === "AbortError")
25
+ return;
26
+ throw err;
27
+ }
28
+ }
29
+ const WATCHED_SOURCE_SUFFIXES = [".ts", ".tsx"];
30
+ const TEST_ONLY_SUFFIXES = [
31
+ ".test.ts",
32
+ ".test.tsx",
33
+ ".integration.ts",
34
+ ".integration.test.ts",
35
+ ".e2e.ts",
36
+ ];
37
+ const CLIENT_SUBDIRS = ["web", "admin", "public", "client"];
38
+ const CLIENT_ENTRY_SUFFIXES = ["/client.tsx", "/client.ts"];
39
+ function endsWithAny(filename, suffixes) {
40
+ return suffixes.some((suffix) => filename.endsWith(suffix));
41
+ }
42
+ function isTestOnlyFile(filename) {
43
+ return filename.includes("__tests__") || endsWithAny(filename, TEST_ONLY_SUFFIXES);
44
+ }
45
+ // Platform-independent: checks both POSIX and Windows separators. Matches
46
+ // `<sep><dir><sep>` and a leading `<dir><sep>` for watcher filenames
47
+ // supplied as relative paths.
48
+ function isInClientSubdir(filename) {
49
+ return CLIENT_SUBDIRS.some((dir) => filename.includes(`/${dir}/`) ||
50
+ filename.includes(`\\${dir}\\`) ||
51
+ filename.startsWith(`${dir}/`) ||
52
+ filename.startsWith(`${dir}\\`));
53
+ }
54
+ // Classifies a changed file as `hot-reload` for client-side changes,
55
+ // `restart` for server-side changes whose cached Bun modules must be
56
+ // reloaded, or `ignore` when the server is unaffected.
57
+ //
58
+ // Tests and non-TypeScript files are ignored. TypeScript files under
59
+ // `/web/`, `/admin/`, `/public/`, or `/client/`, as well as the client
60
+ // entry itself, trigger hot reload; other TypeScript files trigger a
61
+ // restart because they may contain schema or feature definitions.
62
+ //
63
+ // The multiple client directories are intentional: samples use different
64
+ // layouts. Restarting for a client-only edit would be slower, drop the
65
+ // ephemeral test database, and reseed it unnecessarily.
66
+ export function classifyChange(filename) {
67
+ if (!endsWithAny(filename, WATCHED_SOURCE_SUFFIXES))
68
+ return "ignore";
69
+ if (isTestOnlyFile(filename))
70
+ return "ignore";
71
+ if (isInClientSubdir(filename) || endsWithAny(filename, CLIENT_ENTRY_SUFFIXES)) {
72
+ return "hot-reload";
73
+ }
74
+ return "restart";
75
+ }
76
+ // The watcher reports paths relative to the watched directory, so an entry arrives as a bare
77
+ // "client.tsx" (or a custom name like "client-admin.tsx") that classifyChange alone reads as a
78
+ // server file and answers with a restart.
79
+ export function classifyWatchedChange(watchedDir, filename, entrySourceFiles) {
80
+ return entrySourceFiles.has(resolve(watchedDir, filename))
81
+ ? "hot-reload"
82
+ : classifyChange(filename);
83
+ }
84
+ // Expands watchDirs patterns into concrete directories. Entries without
85
+ // `*` are resolved as ordinary paths; entries with `*` are glob-expanded
86
+ // and only directory matches are retained, so `"../../../packages/*/src"`
87
+ // replaces a list of hard-coded paths. Expansion runs once at boot,
88
+ // not while the watcher is running.
89
+ function expandWatchPatterns(patterns) {
90
+ const out = [];
91
+ for (const p of patterns) {
92
+ if (!p.includes("*")) {
93
+ out.push(resolve(p));
94
+ continue;
95
+ }
96
+ // Only reachable under Bun (createKumikoServer is Bun-only); without Bun the
97
+ // call stack fails fast in Bun.serve anyway.
98
+ if (typeof Bun === "undefined")
99
+ throw new Error("expandWatchPatterns requires Bun.Glob");
100
+ const glob = new Bun.Glob(p);
101
+ const matches = Array.from(glob.scanSync({ onlyFiles: false, cwd: process.cwd() }));
102
+ for (const m of matches) {
103
+ const abs = resolve(m);
104
+ try {
105
+ if (statSync(abs).isDirectory())
106
+ out.push(abs);
107
+ }
108
+ catch {
109
+ // ignore unreadable matches, typically broken symlinks
110
+ }
111
+ }
112
+ }
113
+ return out;
114
+ }
115
+ // Because Bun caches module imports, a server-side change needs a process
116
+ // restart: exit code 75 (EX_TEMPFAIL) lets the `kumiko-dev` wrapper respawn.
117
+ async function handleWatchedChange(deps, dir, filename, entrySourceFiles) {
118
+ const action = classifyWatchedChange(dir, filename, entrySourceFiles);
119
+ // skip: file change classified as ignore (test/css/json), nothing to rebuild
120
+ if (action === "ignore")
121
+ return;
122
+ if (action === "restart") {
123
+ logInfo(`[kumiko-server] schema change in ${filename} — restarting (Bun caches imports, hot-reload reicht hier nicht). ` +
124
+ "The kumiko-dev wrapper (package.json's `dev` script) respawns automatically " +
125
+ "and re-syncs new r.entity() columns on the new boot; running this entry " +
126
+ "directly (not via kumiko-dev) exits here without restarting.");
127
+ await deps.stop();
128
+ process.exit(75);
129
+ }
130
+ try {
131
+ await rebuildClientBundles(deps.entries, deps.buildBundle, deps.clientBundles);
132
+ logInfo(`[kumiko-server] rebuilt on ${filename}, broadcasting reload`);
133
+ broadcastReload(deps.reloadHub, filename);
134
+ }
135
+ catch {
136
+ // buildClient already logged the failure; keep serving the
137
+ // last good bundle until the next successful rebuild.
138
+ }
139
+ }
140
+ // Watches every entry directory plus the explicit watchDirs. Multi-entry
141
+ // setups often share one src/ directory, so the Set keeps one watcher per
142
+ // directory.
143
+ export function startDevFileWatchers(deps) {
144
+ // skip: without a client entry there is no bundle to rebuild, so watchDirs stay unwatched too
145
+ if (deps.entries.length === 0)
146
+ return;
147
+ const entryDirs = new Set();
148
+ for (const e of deps.entries)
149
+ entryDirs.add(resolve(e.sourceFile, ".."));
150
+ const entrySourceFiles = new Set(deps.entries.map((e) => resolve(e.sourceFile)));
151
+ const dirs = [...entryDirs, ...expandWatchPatterns(deps.watchDirs ?? [])];
152
+ for (const dir of dirs) {
153
+ void watchDir(dir, (filename) => handleWatchedChange(deps, dir, filename, entrySourceFiles), deps.signal, deps.watchDirectory ?? watch);
154
+ }
155
+ }
@@ -1,12 +1,12 @@
1
1
  /// <reference types="bun-types" preserve="true" />
2
- import { type AuthRoutesConfig, type ExtraRouteDefinition } from "@cosmicdrift/kumiko-framework/api";
2
+ import type { AuthRoutesConfig, ExtraRouteDefinition } from "@cosmicdrift/kumiko-framework/api";
3
3
  import { type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
4
4
  import type { ErrorDocsConfig } from "@cosmicdrift/kumiko-framework/errors";
5
- import { type PageHeadResolver, type PageHeadSystemQuery } from "@cosmicdrift/kumiko-renderer/headless/apex";
6
- import { type SystemWireDeps } from "@cosmicdrift/kumiko-server-runtime/extra-routes-deps";
7
- import { type TestStack, type TestStackOptions } from "@cosmicdrift/kumiko-testing/stack";
8
- type BunServer = typeof Bun extends undefined ? unknown : ReturnType<typeof Bun.serve>;
9
- export declare const logInfo: (msg: string) => void;
5
+ import type { PageHeadResolver, PageHeadSystemQuery } from "@cosmicdrift/kumiko-renderer/headless/apex";
6
+ import type { SystemWireDeps } from "@cosmicdrift/kumiko-server-runtime/extra-routes-deps";
7
+ import type { TestStack, TestStackOptions } from "@cosmicdrift/kumiko-testing/stack";
8
+ import { type DirectoryWatchEvents } from "./create-kumiko-server-watcher.ts";
9
+ import { type BunServer } from "./dev-server-runtime.ts";
10
10
  /** Multi-entry mode for apps that ship several separate bundles (e.g.
11
11
  * publicstatus: `admin.<base>` loads the admin UI, otherwise the public
12
12
  * page).
@@ -201,18 +201,4 @@ export type KumikoServerHandle = {
201
201
  * its `bull:<prefix>-*` keys belong to this server alone on a shared Redis. */
202
202
  readonly jobQueueNamePrefix: string;
203
203
  };
204
- export declare const STYLESHEET_WATCH_ENV = "KUMIKO_DEV_STYLESHEET_WATCH";
205
- export declare const PROD_BUNDLES_ENV = "KUMIKO_DEV_PROD_BUNDLES";
206
- export declare const DEFAULT_HTML = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1,interactive-widget=resizes-content\" />\n <title>Kumiko</title>\n </head>\n <body>\n <div id=\"root\"></div>\n <script type=\"module\" src=\"/client.js\"></script>\n </body>\n</html>\n";
207
- type DirectoryWatchEvents = (dir: string, options: {
208
- readonly recursive: true;
209
- readonly signal: AbortSignal;
210
- }) => AsyncIterable<{
211
- readonly filename: string | null;
212
- }>;
213
- export declare function classifyChange(filename: string): "restart" | "hot-reload" | "ignore";
214
- export declare function classifyWatchedChange(watchedDir: string, filename: string, entrySourceFiles: ReadonlySet<string>): ReturnType<typeof classifyChange>;
215
- export declare function resolveStylesheet(options: CreateKumikoServerOptions): string | undefined;
216
- export declare function resolveStylesheetWatch(options: Pick<CreateKumikoServerOptions, "stylesheetWatch">, env?: Readonly<Record<string, string | undefined>>): boolean;
217
204
  export declare function createKumikoServer(options: CreateKumikoServerOptions): Promise<KumikoServerHandle>;
218
- export {};