react-bun-ssr 0.1.0 → 0.1.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.
package/README.md CHANGED
@@ -1,48 +1,45 @@
1
1
  # react-bun-ssr
2
2
 
3
- `react-bun-ssr` is a TypeScript-first SSR React framework built for Bun.
3
+ `react-bun-ssr` is a Bun-native SSR React framework with file-based routing, loaders, actions, middleware, streaming, soft navigation, and first-class markdown routes.
4
4
 
5
- Documentation: https://react-bun-ssr.fly.dev/docs/getting-started/introduction
5
+ - Documentation: https://react-bun-ssr.fly.dev/docs
6
+ - API reference: https://react-bun-ssr.fly.dev/docs/api/overview
7
+ - Blog: https://react-bun-ssr.fly.dev/blog
8
+ - Repository: https://github.com/react-formation/react-bun-ssr
6
9
 
7
- This repository contains both:
8
- - The framework implementation (`framework/**`, `bin/**`).
9
- - The official documentation site built with the framework itself (`app/**`).
10
+ ## Why react-bun-ssr?
10
11
 
11
- ## Project layout
12
+ `react-bun-ssr` exists for teams that want server-rendered React without starting from Node-first assumptions.
12
13
 
13
- - `framework/`: runtime, router, renderer, build tooling, and CLI internals.
14
- - `bin/rbssr.ts`: CLI entrypoint.
15
- - `app/`: docs web app routes, layouts, middleware, and styles.
16
- - `app/routes/docs/**/*.md`: hand-authored markdown docs routes.
17
- - `app/routes/docs/_sidebar.ts`: docs navigation source of truth.
18
- - `app/routes/docs/api/*.md`: generated API docs.
19
- - `app/routes/docs/search-index.json`: generated search index.
20
- - `tests/`: unit + integration tests.
21
- - `e2e/`: Playwright end-to-end tests.
14
+ It is designed around Bun's runtime, server, bundler, markdown support, and file APIs instead of treating Bun as a compatibility layer. The goal is to stay small enough to understand, but complete enough to use seriously for real SSR applications. The documentation site in this repository is built with the framework itself, so the framework is continuously exercised by its own product surface.
22
15
 
23
- ## Requirements
16
+ ## What it includes
24
17
 
25
- - Bun `>= 1.3.9`
26
- - macOS/Linux (or equivalent Bun-supported environment)
18
+ - [File-based routing](/docs/routing/file-based-routing) for pages, APIs, dynamic params, and markdown routes
19
+ - [Layouts, route groups, and middleware](/docs/routing/layouts-and-groups) with a dedicated [middleware pipeline](/docs/routing/middleware)
20
+ - [Loaders and actions](/docs/data/loaders) for explicit data fetching and mutation flow
21
+ - [Streaming SSR and deferred data](/docs/rendering/streaming-deferred)
22
+ - Soft client transitions with [`Link` and `useRouter`](/docs/routing/navigation)
23
+ - [Bun-first runtime, build, and deployment model](/docs/deployment/bun-deployment)
24
+ - [CSS Modules and static asset support](/docs/styling/css-modules)
25
+ - [Response header config and static caching defaults](/docs/deployment/configuration)
27
26
 
28
- ## Runtime API policy
27
+ ## Installation
29
28
 
30
- - Prefer Bun APIs for file content I/O, hashing, build, and runtime operations.
31
- - `node:path` is intentionally retained for robust path resolution behavior.
32
- - `node:fs` is retained only for `watch` usage in dev mode.
29
+ Prerequisites:
33
30
 
34
- ## Run locally
31
+ - Bun `>= 1.3.10`
32
+ - `rbssr` available on PATH in the workflow you use to start a new app
35
33
 
36
- Install dependencies:
34
+ Minimal setup:
37
35
 
38
36
  ```bash
37
+ bun --version
38
+ mkdir my-app
39
+ cd my-app
40
+ rbssr init
39
41
  bun install
40
- ```
41
-
42
- Run docs site in development:
43
-
44
- ```bash
45
- bun run docs:dev
42
+ bun run dev
46
43
  ```
47
44
 
48
45
  Open:
@@ -51,155 +48,142 @@ Open:
51
48
  http://127.0.0.1:3000
52
49
  ```
53
50
 
54
- Build production artifacts:
55
-
56
- ```bash
57
- bun run docs:build
58
- ```
59
-
60
- Start production server:
51
+ For the full setup walkthrough, read the installation guide:
61
52
 
62
- ```bash
63
- bun run docs:preview
64
- ```
53
+ - https://react-bun-ssr.fly.dev/docs/start/installation
65
54
 
66
- ## Useful commands
55
+ ## What `rbssr init` gives you
67
56
 
68
- Framework:
57
+ `rbssr init` scaffolds a small Bun-first SSR app:
69
58
 
70
- ```bash
71
- bun run dev
72
- bun run build
73
- bun run start
74
- bun run typecheck
75
- bun run test
59
+ ```text
60
+ app/
61
+ root.tsx
62
+ middleware.ts
63
+ routes/
64
+ index.tsx
65
+ api/
66
+ health.ts
67
+ rbssr.config.ts
76
68
  ```
77
69
 
78
- Docs:
70
+ - `app/root.tsx`: document shell and top-level layout
71
+ - `app/middleware.ts`: global request pipeline hook
72
+ - `app/routes/index.tsx`: first SSR page route
73
+ - `app/routes/api/health.ts`: first API route
74
+ - `rbssr.config.ts`: runtime configuration entrypoint
79
75
 
80
- ```bash
81
- bun run docs:dev
82
- bun run docs:check
83
- bun run docs:build
84
- bun run docs:preview
85
- ```
76
+ The quickest follow-up is:
86
77
 
87
- ## Generated files
78
+ - https://react-bun-ssr.fly.dev/docs/start/quick-start
88
79
 
89
- Do not hand-edit generated docs artifacts:
90
- - `app/routes/docs/api/*.md`
91
- - `app/routes/docs/search-index.json`
80
+ ## How it works
92
81
 
93
- They are regenerated by:
94
- - `bun run scripts/generate-api-docs.ts`
95
- - `bun run scripts/build-search-index.ts`
96
- - or automatically via `bun run docs:build`
82
+ ### File-based routing
97
83
 
98
- ## Collaboration guide
84
+ Routes live under `app/routes`. Page routes, API routes, dynamic params, and markdown routes all share one route tree. Files like `_layout` and `_middleware` participate in routing and request flow without becoming public URL segments.
99
85
 
100
- ### 1. Create a branch
86
+ Read more:
101
87
 
102
- ```bash
103
- git checkout -b feat/<short-description>
104
- ```
88
+ - https://react-bun-ssr.fly.dev/docs/routing/file-based-routing
105
89
 
106
- ### 2. Make changes
90
+ ### Request pipeline
107
91
 
108
- - Framework code: update `framework/**`.
109
- - Docs content: update `app/routes/docs/**/*.md`.
110
- - Nav changes: update `app/routes/docs/_sidebar.ts`.
111
- - If exports change, regenerate API docs/search index.
92
+ For a page request, the framework resolves the matching route, runs global and nested middleware, executes the matched loader or action, and then renders an HTML response or returns a direct `Response` when the route short-circuits. API routes use the same route tree and middleware model, but return handler responses instead of page HTML.
112
93
 
113
- ### 3. Validate before pushing
94
+ Read more:
114
95
 
115
- ```bash
116
- bun run test
117
- bun run docs:check
118
- bun run docs:build
119
- ```
96
+ - https://react-bun-ssr.fly.dev/docs/routing/middleware
97
+ - https://react-bun-ssr.fly.dev/docs/data/loaders
120
98
 
121
- ### 4. Commit with generated artifacts when needed
99
+ ### Rendering model
122
100
 
123
- If your changes affect API docs or search corpus, commit updated files under `app/routes/docs/api/*.md` and `app/routes/docs/search-index.json`.
101
+ SSR is the default model. HTML responses stream, deferred loader data is supported, and soft client transitions are handled through `Link` and `useRouter`. The docs site in this repository uses the same routing, rendering, markdown, and transition model that framework users get.
124
102
 
125
- ### 5. Open a PR
103
+ Read more:
126
104
 
127
- Include:
128
- - Problem statement.
129
- - Approach and tradeoffs.
130
- - Testing performed.
131
- - Screenshots/GIF for docs UI changes (if relevant).
105
+ - https://react-bun-ssr.fly.dev/docs/rendering/streaming-deferred
106
+ - https://react-bun-ssr.fly.dev/docs/routing/navigation
132
107
 
133
- ## CI contract
108
+ ### Bun-first runtime
134
109
 
135
- CI runs:
136
- - `bun run test:unit`
137
- - `bun run test:integration`
138
- - `bun run docs:check`
139
- - `bun run docs:build`
110
+ Bun provides the runtime, server, bundler, markdown support, and file APIs that the framework is built around. `react-bun-ssr` is designed to use those primitives directly instead of layering itself on top of a Node-first base.
140
111
 
141
- `docs:check` fails when docs metadata, links, or generated artifacts are stale.
112
+ Read more:
142
113
 
143
- E2E (`bun run test:e2e`) runs on `push` to `main` only.
114
+ - https://react-bun-ssr.fly.dev/docs/api/bun-runtime-apis
144
115
 
145
- ## Deploy to Fly.io
116
+ ## Core commands
146
117
 
147
- ### Prerequisites
118
+ Framework commands:
148
119
 
149
- - Install `flyctl`: https://fly.io/docs/hands-on/install-flyctl/
150
- - Login:
120
+ - `rbssr init`: scaffold a new app in the current directory
121
+ - `rbssr dev`: start the Bun dev server
122
+ - `rbssr build`: create production output in `dist/`
123
+ - `rbssr start`: run the built app in production mode
151
124
 
152
- ```bash
153
- fly auth login
154
- ```
125
+ Repository maintenance commands:
126
+
127
+ - `bun run docs:dev`
128
+ - `bun run docs:check`
129
+ - `bun run docs:build`
130
+ - `bun run test`
131
+ - `bun run typecheck`
155
132
 
156
- ### One-time setup
133
+ CLI reference:
157
134
 
158
- `fly.toml` is included in this repository. Update the `app` name if needed:
135
+ - https://react-bun-ssr.fly.dev/docs/tooling/cli
159
136
 
160
- - `fly.toml`
137
+ ## Working on this repository
161
138
 
162
- Initialize without deploying (optional):
139
+ This repository contains both the framework and the official docs site built with it.
163
140
 
164
141
  ```bash
165
- fly launch --no-deploy
142
+ git clone git@github.com:react-formation/react-bun-ssr.git
143
+ cd react-bun-ssr
144
+ bun install
145
+ bun run docs:dev
166
146
  ```
167
147
 
168
- ### Deploy
148
+ That starts the docs site locally using the framework itself.
169
149
 
170
- ```bash
171
- fly deploy
172
- ```
150
+ ## Project layout
173
151
 
174
- ### Validate deployment
152
+ - `framework/`: runtime, renderer, route handling, build tooling, and CLI internals
153
+ - `bin/rbssr.ts`: CLI entrypoint
154
+ - `app/`: docs site routes, layouts, middleware, blog, and styles
155
+ - `app/routes/docs/**/*.md`: authored documentation pages
156
+ - `app/routes/blog/*.md`: authored blog posts
157
+ - `scripts/`: generators and validation scripts
158
+ - `tests/`: unit and integration tests
159
+ - `e2e/`: Playwright end-to-end tests
175
160
 
176
- ```bash
177
- fly status
178
- fly logs
179
- ```
161
+ ## Contributing
180
162
 
181
- Open:
163
+ Contributions should keep framework behavior, docs, tests, and generated artifacts aligned. For local setup, workflow, validation requirements, and generated-file policy, read [CONTRIBUTING.md](./CONTRIBUTING.md).
182
164
 
183
- ```text
184
- https://<your-fly-app>.fly.dev/docs/getting-started/introduction
185
- ```
165
+ ## Release and deploy
186
166
 
187
- ### Rollback
167
+ - Pushes to `main` run the main-branch CI gate and deploy automatically to Fly.io.
168
+ - Tags like `v0.1.1-rc.0` publish prereleases to npm under `rc`.
169
+ - Tags like `v0.1.1` publish stable releases to npm under `latest`.
170
+ - The release workflow derives the published package version from the Git tag and rewrites `package.json` in the release job before publishing.
171
+ - npm publishing uses trusted publishing with GitHub OIDC instead of an `NPM_TOKEN`.
172
+ - npm package settings must have a trusted publisher configured for `react-formation / react-bun-ssr / release.yml`.
188
173
 
189
- ```bash
190
- fly releases
191
- fly deploy --image <image-ref-from-release>
192
- ```
174
+ ## Deploying
175
+
176
+ Fly.io deployment support is already documented and used by this project.
193
177
 
194
- ### Scaling (optional)
178
+ Happy path:
195
179
 
196
180
  ```bash
197
- fly scale vm shared-cpu-1x
181
+ fly auth login
182
+ fly deploy
198
183
  ```
199
184
 
200
- ### Optional CI deploy
201
-
202
- The workflow includes an optional `deploy-fly` job on `main` push.
203
- Set this repository secret before enabling production deploys:
185
+ Full deployment docs:
204
186
 
205
- - `FLY_API_TOKEN`
187
+ - https://react-bun-ssr.fly.dev/docs/deployment/bun-deployment
188
+ - https://react-bun-ssr.fly.dev/docs/deployment/configuration
189
+ - https://react-bun-ssr.fly.dev/docs/deployment/troubleshooting
@@ -1,5 +1,5 @@
1
- import path from "node:path";
2
- import { createBunRouteAdapter } from "./bun-route-adapter";
1
+ import path from 'node:path';
2
+ import { createBunRouteAdapter } from './bun-route-adapter';
3
3
  import {
4
4
  ensureCleanDir,
5
5
  ensureDir,
@@ -7,15 +7,23 @@ import {
7
7
  glob,
8
8
  statPath,
9
9
  writeTextIfChanged,
10
- } from "./io";
10
+ } from './io';
11
11
  import type {
12
12
  BuildManifest,
13
13
  BuildRouteAsset,
14
14
  PageRouteDefinition,
15
15
  ResolvedConfig,
16
16
  RouteManifest,
17
- } from "./types";
18
- import { normalizeSlashes, stableHash, toImportPath } from "./utils";
17
+ } from './types';
18
+ import { normalizeSlashes, stableHash, toImportPath } from './utils';
19
+
20
+ const BUILD_OPTIMIZE_IMPORTS = [
21
+ 'react-bun-ssr',
22
+ 'react-bun-ssr/route',
23
+ 'react',
24
+ 'react-dom',
25
+ '@datadog/browser-rum-react',
26
+ ];
19
27
 
20
28
  interface ClientEntryFile {
21
29
  routeId: string;
@@ -28,7 +36,7 @@ async function walkFiles(rootDir: string): Promise<string[]> {
28
36
  return [];
29
37
  }
30
38
 
31
- return glob("**/*", { cwd: rootDir, absolute: true });
39
+ return glob('**/*', { cwd: rootDir, absolute: true });
32
40
  }
33
41
 
34
42
  function buildClientEntrySource(options: {
@@ -41,11 +49,13 @@ function buildClientEntrySource(options: {
41
49
 
42
50
  const imports: string[] = [];
43
51
 
44
- const runtimeImport = toImportPath(generatedDir, runtimeClientFile);
52
+ const runtimeImport = normalizeSlashes(path.resolve(runtimeClientFile));
45
53
  const rootImport = toImportPath(generatedDir, rootModulePath);
46
54
  const routeImport = toImportPath(generatedDir, route.filePath);
47
55
 
48
- imports.push(`import { hydrateRoute } from "${runtimeImport}";`);
56
+ imports.push(
57
+ `import { hydrateInitialRoute, registerRouteModules } from "${runtimeImport}";`,
58
+ );
49
59
 
50
60
  imports.push(`import RootDefault from "${rootImport}";`);
51
61
  imports.push(`import * as RootModule from "${rootImport}";`);
@@ -58,19 +68,24 @@ function buildClientEntrySource(options: {
58
68
  const layoutFilePath = route.layoutFiles[index]!;
59
69
  const layoutImportPath = toImportPath(generatedDir, layoutFilePath);
60
70
  imports.push(`import Layout${index}Default from "${layoutImportPath}";`);
61
- imports.push(`import * as Layout${index}Module from "${layoutImportPath}";`);
62
- layoutModuleRefs.push(`{ ...Layout${index}Module, default: Layout${index}Default }`);
71
+ imports.push(
72
+ `import * as Layout${index}Module from "${layoutImportPath}";`,
73
+ );
74
+ layoutModuleRefs.push(
75
+ `{ ...Layout${index}Module, default: Layout${index}Default }`,
76
+ );
63
77
  }
64
78
 
65
- return `${imports.join("\n")}
79
+ return `${imports.join('\n')}
66
80
 
67
81
  const modules = {
68
82
  root: { ...RootModule, default: RootDefault },
69
- layouts: [${layoutModuleRefs.join(", ")}],
83
+ layouts: [${layoutModuleRefs.join(', ')}],
70
84
  route: { ...RouteModule, default: RouteDefault },
71
85
  };
72
86
 
73
- hydrateRoute(modules);
87
+ registerRouteModules(${JSON.stringify(route.id)}, modules);
88
+ hydrateInitialRoute(${JSON.stringify(route.id)});
74
89
  `;
75
90
  }
76
91
 
@@ -82,10 +97,10 @@ export async function generateClientEntries(options: {
82
97
  const { config, manifest, generatedDir } = options;
83
98
  await ensureDir(generatedDir);
84
99
 
85
- const runtimeClientFile = path.resolve(config.cwd, "framework/runtime/client-runtime.tsx");
100
+ const runtimeClientFile = path.resolve(import.meta.dir, 'client-runtime.tsx');
86
101
 
87
102
  return Promise.all(
88
- manifest.pages.map(async route => {
103
+ manifest.pages.map(async (route) => {
89
104
  const entryName = `route__${route.id}.tsx`;
90
105
  const entryFilePath = path.join(generatedDir, entryName);
91
106
  const source = buildClientEntrySource({
@@ -112,14 +127,20 @@ async function mapBuildOutputsByPrefix(options: {
112
127
  publicPrefix: string;
113
128
  }): Promise<Record<string, BuildRouteAsset>> {
114
129
  const { outDir, routeIds, publicPrefix } = options;
115
- const files = (await walkFiles(outDir)).map(filePath => normalizeSlashes(path.relative(outDir, filePath)));
130
+ const files = (await walkFiles(outDir)).map((filePath) =>
131
+ normalizeSlashes(path.relative(outDir, filePath)),
132
+ );
116
133
 
117
134
  const routeAssets: Record<string, BuildRouteAsset> = {};
118
135
 
119
136
  for (const routeId of routeIds) {
120
137
  const base = `route__${routeId}`;
121
- const script = files.find(file => file.startsWith(base) && file.endsWith(".js"));
122
- const css = files.filter(file => file.startsWith(base) && file.endsWith(".css"));
138
+ const script = files.find(
139
+ (file) => file.startsWith(base) && file.endsWith('.js'),
140
+ );
141
+ const css = files.filter(
142
+ (file) => file.startsWith(base) && file.endsWith('.css'),
143
+ );
123
144
 
124
145
  if (!script) {
125
146
  continue;
@@ -127,7 +148,56 @@ async function mapBuildOutputsByPrefix(options: {
127
148
 
128
149
  routeAssets[routeId] = {
129
150
  script: `${publicPrefix}${script}`,
130
- css: css.map(file => `${publicPrefix}${file}`),
151
+ css: css.map((file) => `${publicPrefix}${file}`),
152
+ };
153
+ }
154
+
155
+ return routeAssets;
156
+ }
157
+
158
+ function normalizeMetafilePath(filePath: string): string {
159
+ return normalizeSlashes(filePath).replace(/^\.\//, "");
160
+ }
161
+
162
+ function toPublicBuildPath(publicPrefix: string, filePath: string): string {
163
+ return `${publicPrefix}${normalizeMetafilePath(filePath)}`;
164
+ }
165
+
166
+ function mapBuildOutputsFromMetafile(options: {
167
+ metafile: Bun.BuildMetafile;
168
+ entries: ClientEntryFile[];
169
+ publicPrefix: string;
170
+ }): Record<string, BuildRouteAsset> {
171
+ const routeIdByEntrypoint = new Map<string, string>();
172
+ const routeIdByEntryName = new Map<string, string>();
173
+ for (const entry of options.entries) {
174
+ const absoluteEntrypoint = normalizeMetafilePath(path.resolve(entry.entryFilePath));
175
+ const relativeEntrypoint = normalizeMetafilePath(path.relative(process.cwd(), entry.entryFilePath));
176
+ routeIdByEntrypoint.set(absoluteEntrypoint, entry.routeId);
177
+ routeIdByEntrypoint.set(relativeEntrypoint, entry.routeId);
178
+ routeIdByEntryName.set(path.basename(entry.entryFilePath), entry.routeId);
179
+ }
180
+
181
+ const routeAssets: Record<string, BuildRouteAsset> = {};
182
+
183
+ for (const [outputPath, metadata] of Object.entries(options.metafile.outputs)) {
184
+ if (!outputPath.endsWith(".js") || !metadata.entryPoint) {
185
+ continue;
186
+ }
187
+
188
+ const normalizedEntrypoint = normalizeMetafilePath(metadata.entryPoint);
189
+ const absoluteEntrypoint = normalizeMetafilePath(path.resolve(process.cwd(), normalizedEntrypoint));
190
+ const routeId =
191
+ routeIdByEntrypoint.get(normalizedEntrypoint) ??
192
+ routeIdByEntrypoint.get(absoluteEntrypoint) ??
193
+ routeIdByEntryName.get(path.basename(normalizedEntrypoint));
194
+ if (!routeId) {
195
+ continue;
196
+ }
197
+
198
+ routeAssets[routeId] = {
199
+ script: toPublicBuildPath(options.publicPrefix, outputPath),
200
+ css: metadata.cssBundle ? [toPublicBuildPath(options.publicPrefix, metadata.cssBundle)] : [],
131
201
  };
132
202
  }
133
203
 
@@ -148,42 +218,64 @@ export async function bundleClientEntries(options: {
148
218
  }
149
219
 
150
220
  const result = await Bun.build({
151
- entrypoints: entries.map(entry => entry.entryFilePath),
221
+ entrypoints: entries.map((entry) => entry.entryFilePath),
152
222
  outdir: outDir,
153
- target: "browser",
154
- format: "esm",
155
- splitting: false,
156
- sourcemap: dev ? "inline" : "external",
223
+ target: 'browser',
224
+ format: 'esm',
225
+ metafile: true,
226
+ optimizeImports: BUILD_OPTIMIZE_IMPORTS,
227
+ splitting: true,
228
+ sourcemap: dev ? 'inline' : 'external',
157
229
  minify: !dev,
158
- naming: dev ? "[name].[ext]" : "[name]-[hash].[ext]",
230
+ naming: dev ? '[name].[ext]' : '[name]-[hash].[ext]',
159
231
  });
160
232
 
161
233
  if (!result.success) {
162
- const messages = result.logs.map(log => log.message).join("\n");
234
+ const messages = result.logs.map((log) => log.message).join('\n');
163
235
  throw new Error(`Client bundle failed:\n${messages}`);
164
236
  }
165
237
 
166
- return mapBuildOutputsByPrefix({
238
+ const routeAssetsFromMetafile = result.metafile
239
+ ? mapBuildOutputsFromMetafile({
240
+ metafile: result.metafile,
241
+ entries,
242
+ publicPrefix,
243
+ })
244
+ : {};
245
+
246
+ if (Object.keys(routeAssetsFromMetafile).length === entries.length) {
247
+ return routeAssetsFromMetafile;
248
+ }
249
+
250
+ const routeAssetsFromPrefix = await mapBuildOutputsByPrefix({
167
251
  outDir,
168
- routeIds: entries.map(entry => entry.routeId),
252
+ routeIds: entries.map((entry) => entry.routeId),
169
253
  publicPrefix,
170
254
  });
255
+
256
+ return {
257
+ ...routeAssetsFromPrefix,
258
+ ...routeAssetsFromMetafile,
259
+ };
171
260
  }
172
261
 
173
262
  export async function ensureCleanDirectory(dirPath: string): Promise<void> {
174
263
  await ensureCleanDir(dirPath);
175
264
  }
176
265
 
177
- export async function copyDirRecursive(sourceDir: string, destinationDir: string): Promise<void> {
266
+ export async function copyDirRecursive(
267
+ sourceDir: string,
268
+ destinationDir: string,
269
+ ): Promise<void> {
178
270
  if (!(await existsPath(sourceDir))) {
179
271
  return;
180
272
  }
181
273
 
182
274
  await ensureDir(destinationDir);
183
275
 
184
- const entries = await glob("**/*", { cwd: sourceDir });
276
+ const entries = await glob('**/*', { cwd: sourceDir });
185
277
  await Promise.all(
186
- entries.map(async entry => {
278
+ entries.map(async (entry) => {
187
279
  const from = path.join(sourceDir, entry);
188
280
  const to = path.join(destinationDir, entry);
189
281
  const fileStat = await statPath(from);
@@ -197,7 +289,9 @@ export async function copyDirRecursive(sourceDir: string, destinationDir: string
197
289
  );
198
290
  }
199
291
 
200
- export function createBuildManifest(routeAssets: Record<string, BuildRouteAsset>): BuildManifest {
292
+ export function createBuildManifest(
293
+ routeAssets: Record<string, BuildRouteAsset>,
294
+ ): BuildManifest {
201
295
  return {
202
296
  version: stableHash(JSON.stringify(routeAssets)),
203
297
  generatedAt: new Date().toISOString(),
@@ -207,28 +301,38 @@ export function createBuildManifest(routeAssets: Record<string, BuildRouteAsset>
207
301
 
208
302
  export async function discoverFileSignature(rootDir: string): Promise<string> {
209
303
  const files = (await walkFiles(rootDir))
210
- .filter(file => !normalizeSlashes(file).includes("/node_modules/"))
304
+ .filter((file) => !normalizeSlashes(file).includes('/node_modules/'))
211
305
  .sort();
212
306
 
213
- const signatureBits = (await Promise.all(
214
- files.map(async filePath => {
215
- const fileStat = await statPath(filePath);
216
- if (!fileStat?.isFile()) {
217
- return null;
218
- }
219
- const contentHash = stableHash(await Bun.file(filePath).bytes());
220
- return `${normalizeSlashes(filePath)}:${contentHash}`;
221
- }),
222
- )).filter((value): value is string => Boolean(value));
223
-
224
- return stableHash(signatureBits.join("|"));
307
+ const signatureBits = (
308
+ await Promise.all(
309
+ files.map(async (filePath) => {
310
+ const fileStat = await statPath(filePath);
311
+ if (!fileStat?.isFile()) {
312
+ return null;
313
+ }
314
+ const contentHash = stableHash(await Bun.file(filePath).bytes());
315
+ return `${normalizeSlashes(filePath)}:${contentHash}`;
316
+ }),
317
+ )
318
+ ).filter((value): value is string => Boolean(value));
319
+
320
+ return stableHash(signatureBits.join('|'));
225
321
  }
226
322
 
227
- export async function buildRouteManifest(config: ResolvedConfig): Promise<RouteManifest> {
323
+ export async function buildRouteManifest(
324
+ config: ResolvedConfig,
325
+ ): Promise<RouteManifest> {
228
326
  const adapter = await createBunRouteAdapter({
229
327
  routesDir: config.routesDir,
230
- generatedMarkdownRootDir: path.resolve(config.cwd, ".rbssr/generated/markdown-routes"),
231
- projectionRootDir: path.resolve(config.cwd, ".rbssr/generated/router-projection/build-manifest"),
328
+ generatedMarkdownRootDir: path.resolve(
329
+ config.cwd,
330
+ '.rbssr/generated/markdown-routes',
331
+ ),
332
+ projectionRootDir: path.resolve(
333
+ config.cwd,
334
+ '.rbssr/generated/router-projection/build-manifest',
335
+ ),
232
336
  });
233
337
  return adapter.manifest;
234
338
  }