@jk2908/solas 0.5.4 → 0.7.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 (75) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +34 -94
  3. package/dist/config.d.ts +24 -0
  4. package/dist/config.js +143 -0
  5. package/dist/events.d.ts +4 -0
  6. package/dist/events.js +5 -0
  7. package/dist/index.d.ts +1 -2
  8. package/dist/index.js +59 -41
  9. package/dist/internal/boundary-error.d.ts +4 -0
  10. package/dist/internal/boundary-error.js +8 -0
  11. package/dist/internal/browser-router/history.d.ts +1 -1
  12. package/dist/internal/browser-router/link.d.ts +1 -1
  13. package/dist/internal/browser-router/link.js +1 -1
  14. package/dist/internal/browser-router/router.d.ts +2 -2
  15. package/dist/internal/browser-router/router.js +5 -5
  16. package/dist/internal/browser-router/shared.d.ts +182 -184
  17. package/dist/internal/browser-router/shared.js +49 -56
  18. package/dist/internal/browser-router/use-router.d.ts +1 -1
  19. package/dist/internal/browser-router/use-search-params.js +3 -3
  20. package/dist/internal/build.d.ts +108 -110
  21. package/dist/internal/build.js +569 -581
  22. package/dist/internal/codegen/config.js +4 -5
  23. package/dist/internal/codegen/environments.d.ts +1 -1
  24. package/dist/internal/codegen/environments.js +6 -9
  25. package/dist/internal/codegen/format.d.ts +6 -0
  26. package/dist/internal/codegen/format.js +24 -0
  27. package/dist/internal/codegen/manifest.js +2 -2
  28. package/dist/internal/codegen/maps.d.ts +1 -1
  29. package/dist/internal/codegen/maps.js +2 -2
  30. package/dist/internal/codegen/types.js +4 -4
  31. package/dist/internal/codegen/utils.js +2 -2
  32. package/dist/internal/env/request-context.js +1 -1
  33. package/dist/internal/env/rsc.d.ts +3 -3
  34. package/dist/internal/env/rsc.js +26 -26
  35. package/dist/internal/env/ssr.js +4 -4
  36. package/dist/internal/http-router/router.js +8 -8
  37. package/dist/internal/metadata.d.ts +1 -1
  38. package/dist/internal/metadata.js +1 -1
  39. package/dist/internal/navigation/http-exception-boundary.d.ts +1 -1
  40. package/dist/internal/navigation/redirect-boundary.d.ts +1 -1
  41. package/dist/internal/navigation/redirect.js +6 -6
  42. package/dist/internal/postbuild.js +18 -17
  43. package/dist/internal/prerender.d.ts +130 -138
  44. package/dist/internal/prerender.js +326 -354
  45. package/dist/internal/public-files.js +2 -4
  46. package/dist/internal/render/head.js +2 -2
  47. package/dist/internal/resolver.js +1 -1
  48. package/dist/internal/runtimes/runtime.d.ts +7 -21
  49. package/dist/internal/runtimes/runtime.js +24 -19
  50. package/dist/internal/server/actions.js +2 -2
  51. package/dist/internal/ui/error-boundary.d.ts +1 -1
  52. package/dist/manifest.d.ts +7 -0
  53. package/dist/manifest.js +85 -0
  54. package/dist/prerender.d.ts +1 -1
  55. package/dist/prerender.js +1 -1
  56. package/dist/solas.d.ts +2 -46
  57. package/dist/solas.js +6 -266
  58. package/dist/types.d.ts +14 -25
  59. package/dist/types.js +1 -1
  60. package/dist/utils/base-path.d.ts +12 -14
  61. package/dist/utils/base-path.js +75 -82
  62. package/dist/utils/compress.d.ts +9 -11
  63. package/dist/utils/compress.js +59 -64
  64. package/dist/utils/context.d.ts +4 -6
  65. package/dist/utils/context.js +17 -22
  66. package/dist/utils/logger.js +2 -2
  67. package/dist/utils/time.d.ts +2 -4
  68. package/dist/utils/time.js +23 -29
  69. package/package.json +18 -8
  70. package/dist/internal/runtimes/bun.d.ts +0 -9
  71. package/dist/internal/runtimes/bun.js +0 -33
  72. package/dist/internal/runtimes/create.d.ts +0 -4
  73. package/dist/internal/runtimes/create.js +0 -9
  74. package/dist/internal/runtimes/node.d.ts +0 -9
  75. package/dist/internal/runtimes/node.js +0 -31
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Breaking
6
+
7
+ - Removed the `runtime` config option and the Bun/Node runtime adapters. Solas now uses the Node standard library directly, which Bun runs natively, so there is no runtime selection to configure. Generated entries no longer call `createRuntime`.
8
+
9
+ ## 0.6.0
10
+
11
+ ### Breaking
12
+
13
+ - Removed the `Solas` runtime namespace exports (`Solas.Config`, `Solas.Runtime`, `Solas.Events`) from the `@jk2908/solas` entry. Runtime constants now live in dedicated internal modules, and generated code no longer reaches through the namespace.
14
+ - Removed the `@jk2908/solas/$` export path. Generated environment entries now import directly from `@jk2908/solas/env/rsc`.
15
+ - Removed the `port` config option from `solas()`. The development port now comes from Vite's `server.port`. `url` remains available as an optional public origin and now resolves from the `url` option → `VITE_APP_URL` → the Vite dev server `host`/`port`.
16
+
17
+ ### Features
18
+
19
+ - Generated `.solas` files are formatted with oxfmt before writing, and stale generated files are pruned when the codegen stops emitting them.
20
+ - Added a Vitest test suite covering unit, integration, and browser (React Testing Library) tests.
21
+
22
+ ### Fixes
23
+
24
+ - Generated `entry.rsc.tsx` now emits valid code (`loadManifest` from `env/rsc`) instead of referencing an undefined `Config` import.
25
+ - Fixed the release pipeline by committing the postbuild measurement script.
26
+
27
+ ### Changed
28
+
29
+ - Pinned `@vitejs/plugin-rsc` to `0.5.34`.
30
+ - Removed the Playwright e2e suite, fixtures, and scripts for now; unit, integration, and browser tests run through Vitest.
31
+ - Declared `engines.node` (`^20.19.0 || >=22.12.0`).
32
+
3
33
  ## 0.5.4 - 2026-06-08
4
34
 
5
35
  - Add a BrowserRouterHistory API to sync app back/forward navigation with browser navigation.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Solas is a minimal React meta-framework powered by Vite, created for experimenting with routing, streaming, and prerendering with React Server Components.
4
4
 
5
- Solas is experimental and currently has no automated test suite, so expect rough edges.
5
+ Solas is experimental and under active development, so expect rough edges.
6
6
 
7
7
  ## Install
8
8
 
@@ -201,118 +201,45 @@ That keeps your route params aligned across links, imperative navigation, metada
201
201
 
202
202
  All Solas options are passed to `solas()` inside `defineConfig`.
203
203
 
204
- ## Runtime
205
-
206
- Solas uses a runtime for filesystem access, mime lookup, and hashing.
207
-
208
- This is not a deployment or packaging adapter. Platform adapters are not available yet.
209
-
210
- Use the `runtime` config key to select the runtime used by Solas server code.
211
-
212
- Supported values are `'auto'`, `'node'`, and `'bun'`.
213
-
214
- `solas()` defaults to `runtime: 'auto'`. In a Bun process, that selects Bun. Otherwise it falls back to Node.
215
-
216
- If you already run Vite through Bun, `runtime: 'auto'` is usually enough and Solas will detect Bun automatically.
217
-
218
- ### Node runtime
219
-
220
- If you want to pin Node explicitly, set `runtime: 'node'`:
221
-
222
- ```ts
223
- import { defineConfig } from 'vite'
224
-
225
- import solas from '@jk2908/solas'
226
- import react from '@vitejs/plugin-react'
227
-
228
- export default defineConfig({
229
- plugins: [solas({ runtime: 'node' }), react()],
230
- })
231
- ```
232
-
233
- Use the normal Vite commands with the Node runtime:
234
-
235
- ```json
236
- {
237
- "scripts": {
238
- "dev": "vite dev",
239
- "build": "vite build",
240
- "preview": "vite preview"
241
- }
242
- }
243
- ```
244
-
245
- ### Bun runtime
246
-
247
- If you want Solas runtime code to execute in Bun, set `runtime: 'bun'`:
248
-
249
- ```ts
250
- import { defineConfig } from 'vite'
251
-
252
- import solas from '@jk2908/solas'
253
- import react from '@vitejs/plugin-react'
254
-
255
- export default defineConfig({
256
- plugins: [solas({ runtime: 'bun' }), react()],
257
- })
258
- ```
259
-
260
- When you use the Bun runtime, run Vite through Bun so the server/runtime code executes in a Bun process:
261
-
262
- ```json
263
- {
264
- "scripts": {
265
- "dev": "bunx --bun vite dev",
266
- "build": "bunx --bun vite build",
267
- "preview": "bunx --bun vite preview"
268
- }
269
- }
270
- ```
271
-
272
204
  ### `url`
273
205
 
274
- `url` is optional. If you set it, Solas treats it as the public origin for your app.
206
+ Use `url` to set the public origin for your app. It is optional.
275
207
 
276
- Solas resolves it in this order:
208
+ Solas resolves the origin in this order:
277
209
 
278
210
  - the `url` option passed to `solas()`
279
211
  - `VITE_APP_URL`
212
+ - the Vite dev server `host` and `port` (defaults to `http://localhost:8787`)
280
213
 
281
- Solas exposes the resolved value as `import.meta.env.VITE_APP_URL`. If `url` is set, prerender also uses it as the request origin for build-time renders.
282
-
283
- In practice, you only need `url` if your app reads `import.meta.env.VITE_APP_URL` or your prerendered output needs a real public origin.
214
+ The resolved origin is exposed to your app as `import.meta.env.VITE_APP_URL` and is used for build-time prerender requests and `sitemap` URLs. When `url` is set, it is also used as a trusted origin for CSRF.
284
215
 
285
- If you do want to set it explicitly, this is the shape:
216
+ You usually only need `url` in production, where the Vite dev server origin is meaningless. For example, to generate a `sitemap.xml` with real URLs:
286
217
 
287
218
  ```ts
288
219
  export default defineConfig(({ mode }) => ({
289
220
  plugins: [
290
221
  solas({
291
- url: mode === 'production' ? 'https://example.com' : 'http://localhost:8787',
222
+ url: mode === 'production' ? 'https://example.com' : undefined,
223
+ sitemap: true,
292
224
  }),
293
225
  ],
294
226
  }))
295
227
  ```
296
228
 
297
- If you prefer an environment variable, set this instead:
229
+ If you prefer an environment variable, set `VITE_APP_URL` instead:
298
230
 
299
231
  ```sh
300
232
  VITE_APP_URL=https://example.com
301
233
  ```
302
234
 
303
- ### `port`
304
-
305
- Use `port` to change the development server port.
306
-
307
- Default: `8787`
235
+ Set the development port with Vite's `server.port`:
308
236
 
309
237
  ```ts
310
238
  export default defineConfig({
311
- plugins: [
312
- solas({
313
- port: 4000,
314
- }),
315
- ],
239
+ server: {
240
+ port: 4000,
241
+ },
242
+ plugins: [solas(), react()],
316
243
  })
317
244
  ```
318
245
 
@@ -510,13 +437,13 @@ Use `sitemap` to generate a `sitemap.xml` at build time.
510
437
 
511
438
  Default: `false`
512
439
 
513
- When enabled, Solas writes a sitemap containing all routes with deterministic URLs: static routes, prerendered routes, and dynamic routes resolved via `params`. The origin for each URL comes from `config.url`.
440
+ When enabled, Solas writes a sitemap containing all routes with deterministic URLs: static routes, prerendered routes, and dynamic routes resolved via `params`. The origin for each URL comes from the resolved origin (see Config > `url`).
514
441
 
515
442
  ```ts
516
443
  export default defineConfig(({ mode }) => ({
517
444
  plugins: [
518
445
  solas({
519
- url: mode === 'production' ? 'https://example.com' : 'http://localhost:8787',
446
+ url: mode === 'production' ? 'https://example.com' : undefined,
520
447
  sitemap: true,
521
448
  }),
522
449
  ],
@@ -528,10 +455,9 @@ Routes with dynamic segments (`[id]`) or catch-all segments (`[...param]`) are o
528
455
  To add routes that Solas cannot discover automatically (for example, catch-all routes backed by a CMS), pass an object with a `routes` function. The function receives the auto-discovered routes and returns the final list:
529
456
 
530
457
  ```ts
531
- export default defineConfig(({ mode }) => ({
458
+ export default defineConfig({
532
459
  plugins: [
533
460
  solas({
534
- url: mode === 'production' ? 'https://example.com' : 'http://localhost:8787',
535
461
  sitemap: {
536
462
  async routes(existing) {
537
463
  const posts = await getPosts()
@@ -540,7 +466,7 @@ export default defineConfig(({ mode }) => ({
540
466
  },
541
467
  }),
542
468
  ],
543
- }))
469
+ })
544
470
  ```
545
471
 
546
472
  The `routes` function can be async. The callback also lets you filter routes:
@@ -581,6 +507,10 @@ export default defineConfig({
581
507
  })
582
508
  ```
583
509
 
510
+ ## Runtime
511
+
512
+ This is not a deployment or packaging adapter. Platform adapters are not available yet.
513
+
584
514
  ## Scripts
585
515
 
586
516
  Add scripts to your app:
@@ -595,7 +525,17 @@ Add scripts to your app:
595
525
  }
596
526
  ```
597
527
 
598
- These defaults assume either `runtime: 'node'` or `runtime: 'auto'` while running Vite under Node. If you set `runtime: 'bun'`, or let `runtime: 'auto'` resolve to Bun by running Vite through Bun, use the Bun-backed commands shown in the runtime section above.
528
+ If you run Vite through Bun, use the Bun-backed commands instead:
529
+
530
+ ```json
531
+ {
532
+ "scripts": {
533
+ "dev": "bunx --bun vite dev",
534
+ "build": "bunx --bun vite build",
535
+ "preview": "bunx --bun vite preview"
536
+ }
537
+ }
538
+ ```
599
539
 
600
540
  ## Commands
601
541
 
@@ -619,7 +559,7 @@ When available, Solas checks browser provenance using:
619
559
  - `Origin`
620
560
  - `Referer`
621
561
 
622
- Solas also considers the effective request origin when your app is behind a proxy by using `X-Forwarded-Host`, `X-Forwarded-Proto`, and `config.url` when present.
562
+ Solas also considers the effective request origin when your app is behind a proxy by using `X-Forwarded-Host` and `X-Forwarded-Proto`.
623
563
 
624
564
  If you need to allow a trusted third-party browser POST source, configure it explicitly:
625
565
 
@@ -0,0 +1,24 @@
1
+ import type { PluginConfig } from './types.js';
2
+ export declare const NAME = "Solas";
3
+ export declare const SLUG: string;
4
+ export declare const PKG_NAME: string;
5
+ export declare const OUT_DIR = "dist";
6
+ export declare const APP_DIR = "app";
7
+ export declare const GENERATED_DIR: string;
8
+ export declare const ENTRY_RSC = "entry.rsc.tsx";
9
+ export declare const ENTRY_SSR = "entry.ssr.tsx";
10
+ export declare const ENTRY_BROWSER = "entry.browser.tsx";
11
+ export declare const ASSETS_DIR: string;
12
+ export declare const PUBLIC_DIR = "public";
13
+ export declare const $: unique symbol;
14
+ export declare const REQUEST_META_KEY: string;
15
+ export declare const LOG_LEVELS: readonly ["debug", "info", "warn", "error", "fatal"];
16
+ export declare const PRERENDER_MODES: readonly ["full", "ppr", false];
17
+ export declare const TRAILING_SLASH_MODES: readonly ["always", "never", "ignore"];
18
+ export declare const RUNTIME_MANIFEST = "runtime-manifest.json";
19
+ /**
20
+ * Validate the plugin configuration object, throwing an error if invalid
21
+ * @param input - the unvalidated configuration object
22
+ * @return the typed and validated configuration object
23
+ */
24
+ export declare function validate(input: unknown): PluginConfig;
package/dist/config.js ADDED
@@ -0,0 +1,143 @@
1
+ export const NAME = 'Solas';
2
+ export const SLUG = NAME.toLowerCase();
3
+ export const PKG_NAME = `@jk2908/${SLUG}`;
4
+ export const OUT_DIR = 'dist';
5
+ export const APP_DIR = 'app';
6
+ export const GENERATED_DIR = `.${SLUG}`;
7
+ export const ENTRY_RSC = 'entry.rsc.tsx';
8
+ export const ENTRY_SSR = 'entry.ssr.tsx';
9
+ export const ENTRY_BROWSER = 'entry.browser.tsx';
10
+ export const ASSETS_DIR = `_${SLUG}`;
11
+ export const PUBLIC_DIR = 'public';
12
+ export const $ = Symbol(SLUG);
13
+ export const REQUEST_META_KEY = `__${SLUG.toUpperCase()}__`;
14
+ export const LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'fatal'];
15
+ export const PRERENDER_MODES = ['full', 'ppr', false];
16
+ export const TRAILING_SLASH_MODES = ['always', 'never', 'ignore'];
17
+ export const RUNTIME_MANIFEST = 'runtime-manifest.json';
18
+ const CONFIG_KEYS = new Set([
19
+ 'logger',
20
+ 'metadata',
21
+ 'precompress',
22
+ 'prerender',
23
+ 'sitemap',
24
+ 'trustedOrigins',
25
+ 'trailingSlash',
26
+ 'url',
27
+ ]);
28
+ const LOGGER_KEYS = new Set(['level']);
29
+ /**
30
+ * Validate the plugin configuration object, throwing an error if invalid
31
+ * @param input - the unvalidated configuration object
32
+ * @return the typed and validated configuration object
33
+ */
34
+ export function validate(input) {
35
+ if (input === undefined)
36
+ return {};
37
+ const errors = [];
38
+ if (!isRecord(input)) {
39
+ throw new Error(`[${NAME}] Invalid config:\n- Expected plugin config to be an object`);
40
+ }
41
+ for (const key of Object.keys(input)) {
42
+ if (!CONFIG_KEYS.has(key)) {
43
+ errors.push(`Unknown config key: ${key}`);
44
+ }
45
+ }
46
+ if ('url' in input && input.url !== undefined) {
47
+ if (typeof input.url !== 'string') {
48
+ errors.push('url must be a string');
49
+ }
50
+ else {
51
+ try {
52
+ const url = new URL(input.url);
53
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
54
+ errors.push('url must use http:// or https://');
55
+ }
56
+ }
57
+ catch {
58
+ errors.push('url must be a valid URL');
59
+ }
60
+ }
61
+ }
62
+ if ('precompress' in input && input.precompress !== undefined) {
63
+ if (typeof input.precompress !== 'boolean') {
64
+ errors.push('precompress must be a boolean');
65
+ }
66
+ }
67
+ if ('trustedOrigins' in input && input.trustedOrigins !== undefined) {
68
+ if (!Array.isArray(input.trustedOrigins)) {
69
+ errors.push('trustedOrigins must be an array of origins');
70
+ }
71
+ else {
72
+ for (const [index, value] of input.trustedOrigins.entries()) {
73
+ if (typeof value !== 'string') {
74
+ errors.push(`trustedOrigins[${index}] must be a string`);
75
+ continue;
76
+ }
77
+ try {
78
+ const url = new URL(value);
79
+ const canonical = value.replace(/\/$/, '');
80
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
81
+ errors.push(`trustedOrigins[${index}] must use http:// or https://`);
82
+ continue;
83
+ }
84
+ if (canonical !== url.origin) {
85
+ errors.push(`trustedOrigins[${index}] must be an origin without a path, query, or hash`);
86
+ }
87
+ }
88
+ catch {
89
+ errors.push(`trustedOrigins[${index}] must be a valid URL origin`);
90
+ }
91
+ }
92
+ }
93
+ }
94
+ if ('sitemap' in input && input.sitemap !== undefined && input.sitemap !== false) {
95
+ if (typeof input.sitemap !== 'boolean' && typeof input.sitemap !== 'object') {
96
+ errors.push('sitemap must be a boolean or an object with a routes function');
97
+ }
98
+ if (typeof input.sitemap === 'object' &&
99
+ input.sitemap !== null &&
100
+ typeof input.sitemap.routes !== 'function') {
101
+ errors.push('sitemap.routes must be a function');
102
+ }
103
+ }
104
+ if ('prerender' in input && input.prerender !== undefined) {
105
+ if (!new Set(PRERENDER_MODES).has(input.prerender)) {
106
+ errors.push("prerender must be 'full', 'ppr', or false");
107
+ }
108
+ }
109
+ if ('trailingSlash' in input && input.trailingSlash !== undefined) {
110
+ if (typeof input.trailingSlash !== 'string' ||
111
+ !new Set(TRAILING_SLASH_MODES).has(input.trailingSlash)) {
112
+ errors.push("trailingSlash must be 'always', 'never', or 'ignore'");
113
+ }
114
+ }
115
+ if ('metadata' in input && input.metadata !== undefined && !isRecord(input.metadata)) {
116
+ errors.push('metadata must be an object when provided');
117
+ }
118
+ if ('logger' in input && input.logger !== undefined) {
119
+ if (!isRecord(input.logger)) {
120
+ errors.push('logger must be an object when provided');
121
+ }
122
+ else {
123
+ for (const key of Object.keys(input.logger)) {
124
+ if (!LOGGER_KEYS.has(key)) {
125
+ errors.push(`Unknown logger key: ${key}`);
126
+ }
127
+ }
128
+ if ('level' in input.logger && input.logger.level !== undefined) {
129
+ if (typeof input.logger.level !== 'string' ||
130
+ !new Set(LOG_LEVELS).has(input.logger.level)) {
131
+ errors.push('logger.level must be one of: debug, info, warn, error, fatal');
132
+ }
133
+ }
134
+ }
135
+ }
136
+ if (errors.length > 0) {
137
+ throw new Error(`[${NAME}] Invalid config:\n- ${errors.join('\n- ')}`);
138
+ }
139
+ return input;
140
+ }
141
+ function isRecord(value) {
142
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
143
+ }
@@ -0,0 +1,4 @@
1
+ export declare const names: {
2
+ readonly NAVIGATION: `${string}navigation`;
3
+ readonly NAVIGATION_ERROR: `${string}navigationerror`;
4
+ };
package/dist/events.js ADDED
@@ -0,0 +1,5 @@
1
+ import * as Config from './config.js';
2
+ export const names = {
3
+ NAVIGATION: `${Config.SLUG}navigation`,
4
+ NAVIGATION_ERROR: `${Config.SLUG}navigationerror`,
5
+ };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,5 @@ import { type PluginOption } from 'vite';
2
2
  import type { PluginConfig } from './types.js';
3
3
  declare function solas(c?: PluginConfig): PluginOption[];
4
4
  export default solas;
5
- export type * from './solas.d.ts';
6
- export { Solas } from './solas.js';
5
+ export * as Solas from './solas.js';
7
6
  export type * from './types.js';
package/dist/index.js CHANGED
@@ -5,36 +5,32 @@ import { createServer, loadConfigFromFile, } from 'vite';
5
5
  import rsc from '@vitejs/plugin-rsc';
6
6
  import { ExportReader } from './utils/export-reader.js';
7
7
  import { Logger } from './utils/logger.js';
8
- import { Time } from './utils/time.js';
9
- import { Build } from './internal/build.js';
8
+ import { debounce } from './utils/time.js';
9
+ import * as Config from './config.js';
10
+ import * as Build from './internal/build.js';
10
11
  import { writeConfig } from './internal/codegen/config.js';
11
12
  import { writeBrowserEntry, writeRSCEntry, writeSSREntry, } from './internal/codegen/environments.js';
13
+ import { format as formatSource } from './internal/codegen/format.js';
12
14
  import { writeManifest } from './internal/codegen/manifest.js';
13
15
  import { writeMaps } from './internal/codegen/maps.js';
14
16
  import { writeTypes } from './internal/codegen/types.js';
15
17
  import { postbuild } from './internal/postbuild.js';
16
18
  import { collect as collectPublicFiles } from './internal/public-files.js';
17
- import { createRuntime } from './internal/runtimes/create.js';
18
19
  import { Runtime } from './internal/runtimes/runtime.js';
19
- import { Solas } from './solas.js';
20
20
  const DEFAULT_CONFIG = {
21
- runtime: 'auto',
22
21
  precompress: false,
23
22
  prerender: false,
24
23
  trustedOrigins: [],
25
24
  trailingSlash: 'never',
26
25
  };
27
26
  function solas(c) {
28
- const validatedConfig = Solas.Config.validate({
29
- ...c,
30
- url: c?.url ?? process.env.VITE_APP_URL?.toString(),
31
- });
27
+ const validatedConfig = Config.validate(c);
32
28
  const config = {
33
29
  ...DEFAULT_CONFIG,
34
30
  ...validatedConfig,
35
- runtime: validatedConfig.runtime ?? DEFAULT_CONFIG.runtime,
36
31
  };
37
- Runtime.runtime = createRuntime(config.runtime);
32
+ const envUrl = process.env.VITE_APP_URL?.toString();
33
+ let resolvedUrl = c?.url ?? (envUrl && envUrl.length > 0 ? envUrl : undefined);
38
34
  if (config.logger?.level)
39
35
  Logger.defaultLevel = config.logger.level;
40
36
  const logger = new Logger();
@@ -93,8 +89,8 @@ function solas(c) {
93
89
  }
94
90
  async function build() {
95
91
  const cwd = process.cwd();
96
- const routesDir = path.join(cwd, Solas.Config.APP_DIR);
97
- const generatedDir = path.join(cwd, Solas.Config.GENERATED_DIR);
92
+ const routesDir = path.join(cwd, Config.APP_DIR);
93
+ const generatedDir = path.join(cwd, Config.GENERATED_DIR);
98
94
  await Promise.all([
99
95
  fs.mkdir(routesDir, { recursive: true }),
100
96
  fs.mkdir(generatedDir, { recursive: true }),
@@ -107,12 +103,27 @@ function solas(c) {
107
103
  ['config.ts', writeConfig(config)],
108
104
  ['manifest.ts', writeManifest(manifest)],
109
105
  ['maps.ts', writeMaps(imports, modules)],
110
- [`${Solas.Config.SLUG}.d.ts`, writeTypes(manifest)],
111
- [Solas.Config.ENTRY_RSC, writeRSCEntry(config)],
112
- [Solas.Config.ENTRY_SSR, writeSSREntry()],
113
- [Solas.Config.ENTRY_BROWSER, writeBrowserEntry()],
106
+ [`${Config.SLUG}.d.ts`, writeTypes(manifest)],
107
+ [Config.ENTRY_RSC, writeRSCEntry(config)],
108
+ [Config.ENTRY_SSR, writeSSREntry()],
109
+ [Config.ENTRY_BROWSER, writeBrowserEntry()],
114
110
  ];
115
- const writes = await Promise.all(files.map(([file, content]) => maybeWrite(path.join(generatedDir, file), content)));
111
+ // remove generated files no longer emitted by the current codegen, so
112
+ // old artifacts don't linger and break module resolution. keep
113
+ // closeBundle's build.json, which lives in the same directory
114
+ const currentFiles = new Set(files.map(([file]) => file));
115
+ const existing = await fs
116
+ .readdir(generatedDir, { withFileTypes: true })
117
+ .catch(() => []);
118
+ await Promise.all(existing
119
+ .filter(entry => entry.isFile() &&
120
+ entry.name !== 'build.json' &&
121
+ !currentFiles.has(entry.name))
122
+ .map(entry => fs.rm(path.join(generatedDir, entry.name), { force: true })));
123
+ const writes = await Promise.all(files.map(async ([file, content]) => {
124
+ const formatted = await formatSource(file, content);
125
+ return maybeWrite(path.join(generatedDir, file), formatted);
126
+ }));
116
127
  const changed = writes.filter(n => n !== null);
117
128
  // early return if nothing has changed
118
129
  if (!changed.length)
@@ -125,7 +136,7 @@ function solas(c) {
125
136
  // normalise all watcher paths to forward slashes so path checks behave the
126
137
  // same on Windows and POSIX
127
138
  const WATCH_CWD = process.cwd().replace(/\\/g, '/');
128
- const WATCH_APP_ROOT = `${WATCH_CWD}/${Solas.Config.APP_DIR}/`;
139
+ const WATCH_APP_ROOT = `${WATCH_CWD}/${Config.APP_DIR}/`;
129
140
  // convert watcher paths to a consistent slash format before comparing them
130
141
  const normaliseWatchPath = (p) => p.replace(/\\/g, '/');
131
142
  // resolve relative watcher paths against the project root so prefix checks are reliable
@@ -136,7 +147,7 @@ function solas(c) {
136
147
  // edits needing special treatment because verb exports can change in-place
137
148
  const routeFile = /\/\+(layout|page|401|403|404|500|loading|middleware|endpoint)\.(t|j)sx?$/;
138
149
  const endpointFile = /\/\+endpoint\.(t|j)sx?$/;
139
- const rebuild = Time.debounce((event, p) => {
150
+ const rebuild = debounce((event, p) => {
140
151
  const queue = () => {
141
152
  void (async () => {
142
153
  // collapse bursts of file events into one active rebuild plus a single
@@ -197,9 +208,7 @@ function solas(c) {
197
208
  return [];
198
209
  if (Array.isArray(plugin))
199
210
  return plugin.flatMap(flatten);
200
- if (typeof plugin === 'object' &&
201
- 'name' in plugin &&
202
- plugin.name === Solas.Config.NAME) {
211
+ if (typeof plugin === 'object' && 'name' in plugin && plugin.name === Config.NAME) {
203
212
  return [];
204
213
  }
205
214
  return [plugin];
@@ -219,26 +228,35 @@ function solas(c) {
219
228
  return utilityServer;
220
229
  }
221
230
  const plugin = {
222
- name: Solas.Config.NAME,
231
+ name: Config.NAME,
223
232
  async config(viteConfig) {
224
233
  const pkg = JSON.parse(fsSync.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
225
234
  if (typeof pkg.name !== 'string' || pkg.name.length === 0) {
226
- throw new Error(`Missing ${Solas.Config.NAME} package name`);
235
+ throw new Error(`Missing ${Config.NAME} package name`);
227
236
  }
228
237
  if (typeof pkg.version !== 'string' || pkg.version.length === 0) {
229
- throw new Error(`Missing ${Solas.Config.NAME} package version`);
238
+ throw new Error(`Missing ${Config.NAME} package version`);
230
239
  }
231
240
  viteConfig.build ??= {};
232
- viteConfig.build.outDir = Solas.Config.OUT_DIR;
241
+ viteConfig.build.outDir = Config.OUT_DIR;
233
242
  // keep framework files under one reserved url prefix
234
- viteConfig.build.assetsDir = Solas.Config.ASSETS_DIR;
243
+ viteConfig.build.assetsDir = Config.ASSETS_DIR;
235
244
  viteConfig.build.emptyOutDir = true;
236
245
  // let users move the source public folder if they want
237
- viteConfig.publicDir ??= Solas.Config.PUBLIC_DIR;
246
+ viteConfig.publicDir ??= Config.PUBLIC_DIR;
238
247
  viteConfig.server ??= {};
239
- viteConfig.server.port = config.port ?? viteConfig.server.port ?? 8787;
248
+ viteConfig.server.port ??= 8787;
249
+ // derive the public url from vite's dev server settings when no
250
+ // origin was set explicitly via the url option or VITE_APP_URL
251
+ if (resolvedUrl === undefined) {
252
+ const { host, port } = viteConfig.server;
253
+ const hostname = typeof host === 'string' && host.length > 0 && host !== '0.0.0.0'
254
+ ? host
255
+ : 'localhost';
256
+ resolvedUrl = `http://${hostname}:${port}`;
257
+ }
240
258
  viteConfig.define ??= {};
241
- viteConfig.define['import.meta.env.VITE_APP_URL'] = JSON.stringify(config.url);
259
+ viteConfig.define['import.meta.env.VITE_APP_URL'] = JSON.stringify(resolvedUrl);
242
260
  viteConfig.define['import.meta.env.SOLAS_VERSION'] = JSON.stringify(pkg.version);
243
261
  viteConfig.optimizeDeps ??= {};
244
262
  viteConfig.optimizeDeps.exclude = [
@@ -255,12 +273,12 @@ function solas(c) {
255
273
  ...viteConfig.resolve.alias,
256
274
  {
257
275
  find: '.solas',
258
- replacement: path.resolve(process.cwd(), Solas.Config.GENERATED_DIR),
276
+ replacement: path.resolve(process.cwd(), Config.GENERATED_DIR),
259
277
  },
260
278
  ]
261
279
  : {
262
280
  ...viteConfig.resolve.alias,
263
- '.solas': path.resolve(process.cwd(), Solas.Config.GENERATED_DIR),
281
+ '.solas': path.resolve(process.cwd(), Config.GENERATED_DIR),
264
282
  };
265
283
  },
266
284
  configResolved(resolvedConfig) {
@@ -268,7 +286,7 @@ function solas(c) {
268
286
  buildContext.command = resolvedConfig.command;
269
287
  },
270
288
  configureServer(server) {
271
- logger.info('[configureServer]', `Watching for changes in ./${Solas.Config.APP_DIR}...`);
289
+ logger.info('[configureServer]', `Watching for changes in ./${Config.APP_DIR}...`);
272
290
  server.watcher
273
291
  .on('add', (p) => rebuild('add', p))
274
292
  .on('change', (p) => rebuild('change', p))
@@ -294,7 +312,7 @@ function solas(c) {
294
312
  }
295
313
  // resolve sitemap routes
296
314
  let sitemapRoutes = [];
297
- if (config.sitemap && config.url) {
315
+ if (config.sitemap && resolvedUrl) {
298
316
  const auto = [
299
317
  ...new Set([...buildContext.knownRoutes, ...buildContext.prerenderRoutes]),
300
318
  ];
@@ -306,7 +324,7 @@ function solas(c) {
306
324
  }
307
325
  }
308
326
  // write build manifest
309
- const generatedDir = path.join(process.cwd(), Solas.Config.GENERATED_DIR);
327
+ const generatedDir = path.join(process.cwd(), Config.GENERATED_DIR);
310
328
  await Runtime.write(path.join(generatedDir, 'build.json'), JSON.stringify({
311
329
  base: resolvedViteConfig?.base ?? '/',
312
330
  publicFiles: await collectPublicFiles(resolvedViteConfig?.publicDir),
@@ -314,7 +332,7 @@ function solas(c) {
314
332
  sitemapRoutes,
315
333
  precompress: config.precompress,
316
334
  trailingSlash: config.trailingSlash,
317
- url: config.url,
335
+ url: resolvedUrl,
318
336
  }));
319
337
  },
320
338
  buildApp: {
@@ -328,12 +346,12 @@ function solas(c) {
328
346
  plugin,
329
347
  rsc({
330
348
  entries: {
331
- rsc: `./${Solas.Config.GENERATED_DIR}/${Solas.Config.ENTRY_RSC}`,
332
- ssr: `./${Solas.Config.GENERATED_DIR}/${Solas.Config.ENTRY_SSR}`,
333
- client: `./${Solas.Config.GENERATED_DIR}/${Solas.Config.ENTRY_BROWSER}`,
349
+ rsc: `./${Config.GENERATED_DIR}/${Config.ENTRY_RSC}`,
350
+ ssr: `./${Config.GENERATED_DIR}/${Config.ENTRY_SSR}`,
351
+ client: `./${Config.GENERATED_DIR}/${Config.ENTRY_BROWSER}`,
334
352
  },
335
353
  }),
336
354
  ];
337
355
  }
338
356
  export default solas;
339
- export { Solas } from './solas.js';
357
+ export * as Solas from './solas.js';
@@ -0,0 +1,4 @@
1
+ export declare class BoundaryError extends Error {
2
+ digest?: string;
3
+ constructor(digest?: string);
4
+ }