bosia 0.2.2 → 0.3.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 (87) hide show
  1. package/README.md +39 -39
  2. package/package.json +56 -53
  3. package/src/ambient.d.ts +31 -0
  4. package/src/cli/add.ts +120 -114
  5. package/src/cli/build.ts +10 -10
  6. package/src/cli/create.ts +142 -137
  7. package/src/cli/dev.ts +8 -8
  8. package/src/cli/feat.ts +291 -132
  9. package/src/cli/index.ts +51 -42
  10. package/src/cli/registry.ts +136 -115
  11. package/src/cli/start.ts +17 -17
  12. package/src/cli/test.ts +25 -0
  13. package/src/core/build.ts +72 -56
  14. package/src/core/client/App.svelte +177 -153
  15. package/src/core/client/appState.svelte.ts +57 -0
  16. package/src/core/client/enhance.ts +112 -0
  17. package/src/core/client/hydrate.ts +97 -65
  18. package/src/core/client/prefetch.ts +101 -94
  19. package/src/core/client/router.svelte.ts +64 -51
  20. package/src/core/cookies.ts +70 -66
  21. package/src/core/cors.ts +44 -35
  22. package/src/core/csrf.ts +38 -38
  23. package/src/core/dedup.ts +17 -17
  24. package/src/core/dev.ts +165 -168
  25. package/src/core/env.ts +155 -128
  26. package/src/core/envCodegen.ts +73 -73
  27. package/src/core/errors.ts +48 -49
  28. package/src/core/hooks.ts +50 -50
  29. package/src/core/html.ts +192 -139
  30. package/src/core/matcher.ts +130 -121
  31. package/src/core/paths.ts +8 -10
  32. package/src/core/plugin.ts +113 -107
  33. package/src/core/prerender.ts +191 -118
  34. package/src/core/renderer.ts +359 -265
  35. package/src/core/routeFile.ts +140 -127
  36. package/src/core/routeTypes.ts +144 -83
  37. package/src/core/scanner.ts +125 -95
  38. package/src/core/server.ts +543 -370
  39. package/src/core/types.ts +25 -20
  40. package/src/lib/client.ts +12 -0
  41. package/src/lib/index.ts +8 -8
  42. package/src/lib/utils.ts +44 -30
  43. package/templates/default/.prettierignore +5 -0
  44. package/templates/default/.prettierrc.json +9 -0
  45. package/templates/default/README.md +5 -5
  46. package/templates/default/package.json +22 -18
  47. package/templates/default/src/app.css +80 -80
  48. package/templates/default/src/app.d.ts +3 -3
  49. package/templates/default/src/routes/+error.svelte +7 -10
  50. package/templates/default/src/routes/+layout.svelte +2 -2
  51. package/templates/default/src/routes/+page.svelte +31 -29
  52. package/templates/default/src/routes/about/+page.svelte +3 -3
  53. package/templates/default/tsconfig.json +20 -20
  54. package/templates/demo/.prettierignore +5 -0
  55. package/templates/demo/.prettierrc.json +9 -0
  56. package/templates/demo/README.md +9 -9
  57. package/templates/demo/package.json +22 -17
  58. package/templates/demo/src/app.css +80 -80
  59. package/templates/demo/src/app.d.ts +3 -3
  60. package/templates/demo/src/hooks.server.ts +9 -9
  61. package/templates/demo/src/routes/(public)/+layout.svelte +45 -23
  62. package/templates/demo/src/routes/(public)/+page.svelte +96 -67
  63. package/templates/demo/src/routes/(public)/about/+page.svelte +13 -25
  64. package/templates/demo/src/routes/(public)/all/[...catchall]/+page.svelte +24 -28
  65. package/templates/demo/src/routes/(public)/blog/+page.svelte +55 -46
  66. package/templates/demo/src/routes/(public)/blog/[slug]/+page.server.ts +36 -38
  67. package/templates/demo/src/routes/(public)/blog/[slug]/+page.svelte +60 -42
  68. package/templates/demo/src/routes/+error.svelte +10 -7
  69. package/templates/demo/src/routes/+layout.server.ts +4 -4
  70. package/templates/demo/src/routes/+layout.svelte +2 -2
  71. package/templates/demo/src/routes/actions-test/+page.server.ts +16 -16
  72. package/templates/demo/src/routes/actions-test/+page.svelte +49 -49
  73. package/templates/demo/src/routes/api/hello/+server.ts +25 -25
  74. package/templates/demo/tsconfig.json +20 -20
  75. package/templates/todo/.prettierignore +5 -0
  76. package/templates/todo/.prettierrc.json +9 -0
  77. package/templates/todo/README.md +9 -9
  78. package/templates/todo/package.json +22 -17
  79. package/templates/todo/src/app.css +80 -80
  80. package/templates/todo/src/app.d.ts +7 -7
  81. package/templates/todo/src/hooks.server.ts +9 -9
  82. package/templates/todo/src/routes/+error.svelte +10 -7
  83. package/templates/todo/src/routes/+layout.server.ts +4 -4
  84. package/templates/todo/src/routes/+layout.svelte +2 -2
  85. package/templates/todo/src/routes/+page.svelte +44 -44
  86. package/templates/todo/template.json +1 -1
  87. package/templates/todo/tsconfig.json +20 -20
@@ -1,6 +1,6 @@
1
- import { readdirSync, existsSync } from "fs";
1
+ import { readdirSync, existsSync, readFileSync } from "fs";
2
2
  import { join } from "path";
3
- import type { PageRoute, ApiRoute, RouteManifest } from "./types.ts";
3
+ import type { PageRoute, ApiRoute, RouteManifest, TrailingSlash } from "./types.ts";
4
4
 
5
5
  // ─── Route Scanner ───────────────────────────────────────
6
6
  // Walks src/routes/ and produces a RouteManifest.
@@ -17,101 +17,131 @@ import type { PageRoute, ApiRoute, RouteManifest } from "./types.ts";
17
17
 
18
18
  const ROUTES_DIR = "./src/routes";
19
19
 
20
+ /**
21
+ * Extract `export const trailingSlash = '...'` from a server module file via
22
+ * regex. Static-string read only — runtime expressions return null. Build-time
23
+ * scan avoids invoking server modules during the client bundle.
24
+ */
25
+ function readTrailingSlash(filePath: string): TrailingSlash | null {
26
+ try {
27
+ const src = readFileSync(filePath, "utf-8");
28
+ const m = src.match(
29
+ /export\s+const\s+trailingSlash\s*(?::\s*[^=]+)?=\s*["'](never|always|ignore)["']/,
30
+ );
31
+ return (m?.[1] ?? null) as TrailingSlash | null;
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
20
37
  export function scanRoutes(): RouteManifest {
21
- const pages: PageRoute[] = [];
22
- const apis: ApiRoute[] = [];
23
-
24
- function walk(
25
- dir: string,
26
- urlSegments: string[],
27
- layoutChain: string[],
28
- layoutServerChain: { path: string; depth: number }[],
29
- ) {
30
- const fullDir = join(ROUTES_DIR, dir);
31
- if (!existsSync(fullDir)) return;
32
-
33
- const items = readdirSync(fullDir, { withFileTypes: true });
34
-
35
- // Accumulate layouts for this level
36
- const currentLayouts = [...layoutChain];
37
- const currentLayoutServers = [...layoutServerChain];
38
-
39
- if (items.some(i => i.isFile() && i.name === "+layout.svelte")) {
40
- currentLayouts.push(join(dir, "+layout.svelte"));
41
- }
42
- if (items.some(i => i.isFile() && i.name === "+layout.server.ts")) {
43
- currentLayoutServers.push({
44
- path: join(dir, "+layout.server.ts"),
45
- depth: currentLayouts.length - 1,
46
- });
47
- }
48
-
49
- // API route (+server.ts)
50
- if (items.some(i => i.isFile() && i.name === "+server.ts")) {
51
- apis.push({
52
- pattern: toUrlPath(urlSegments),
53
- server: join(dir, "+server.ts"),
54
- });
55
- }
56
-
57
- // Page route (+page.svelte)
58
- if (items.some(i => i.isFile() && i.name === "+page.svelte")) {
59
- const pageServerFile = items.some(i => i.isFile() && i.name === "+page.server.ts")
60
- ? join(dir, "+page.server.ts")
61
- : null;
62
-
63
- pages.push({
64
- pattern: toUrlPath(urlSegments),
65
- page: join(dir, "+page.svelte"),
66
- layouts: [...currentLayouts],
67
- pageServer: pageServerFile,
68
- layoutServers: [...currentLayoutServers],
69
- });
70
- }
71
-
72
- // Recurse into subdirectories
73
- for (const entry of items) {
74
- if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name === "node_modules") continue;
75
-
76
- const dirName = entry.name;
77
- // Route groups like (public), (auth) are invisible in URLs
78
- const isGroup = /^\(.*\)$/.test(dirName);
79
-
80
- walk(
81
- dir ? join(dir, dirName) : dirName,
82
- isGroup ? [...urlSegments] : [...urlSegments, dirName],
83
- currentLayouts,
84
- currentLayoutServers,
85
- );
86
- }
87
- }
88
-
89
- walk("", [], [], []);
90
-
91
- // Warn when a catch-all exists but no exact route covers its prefix.
92
- // e.g. "/[...slug]" matches everything EXCEPT "/" (which needs its own +page.svelte).
93
- const exactPatterns = new Set(
94
- pages.filter(p => !p.pattern.includes("[")).map(p => p.pattern),
95
- );
96
- for (const p of pages) {
97
- const m = p.pattern.match(/^(.*?)\/\[\.\.\.(\w+)\]$/);
98
- if (m) {
99
- const exactEquivalent = m[1] || "/";
100
- if (!exactPatterns.has(exactEquivalent)) {
101
- console.warn(
102
- `⚠️ No exact route for "${exactEquivalent}" the catch-all "${p.pattern}" will NOT match it.\n` +
103
- ` Add a +page.svelte at the "${exactEquivalent}" level to serve that URL.`,
104
- );
105
- }
106
- }
107
- }
108
-
109
- const errorPage = existsSync(join(ROUTES_DIR, "+error.svelte")) ? "+error.svelte" : null;
110
-
111
- return { pages, apis, errorPage };
38
+ const pages: PageRoute[] = [];
39
+ const apis: ApiRoute[] = [];
40
+
41
+ function walk(
42
+ dir: string,
43
+ urlSegments: string[],
44
+ layoutChain: string[],
45
+ layoutServerChain: { path: string; depth: number }[],
46
+ inheritedTrailingSlash: TrailingSlash,
47
+ ) {
48
+ const fullDir = join(ROUTES_DIR, dir);
49
+ if (!existsSync(fullDir)) return;
50
+
51
+ const items = readdirSync(fullDir, { withFileTypes: true });
52
+
53
+ // Accumulate layouts for this level
54
+ const currentLayouts = [...layoutChain];
55
+ const currentLayoutServers = [...layoutServerChain];
56
+ let currentTrailingSlash = inheritedTrailingSlash;
57
+
58
+ if (items.some((i) => i.isFile() && i.name === "+layout.svelte")) {
59
+ currentLayouts.push(join(dir, "+layout.svelte"));
60
+ }
61
+ if (items.some((i) => i.isFile() && i.name === "+layout.server.ts")) {
62
+ const layoutServerPath = join(dir, "+layout.server.ts");
63
+ currentLayoutServers.push({
64
+ path: layoutServerPath,
65
+ depth: currentLayouts.length - 1,
66
+ });
67
+ const ts = readTrailingSlash(join(ROUTES_DIR, layoutServerPath));
68
+ if (ts) currentTrailingSlash = ts;
69
+ }
70
+
71
+ // API route (+server.ts)
72
+ if (items.some((i) => i.isFile() && i.name === "+server.ts")) {
73
+ apis.push({
74
+ pattern: toUrlPath(urlSegments),
75
+ server: join(dir, "+server.ts"),
76
+ });
77
+ }
78
+
79
+ // Page route (+page.svelte)
80
+ if (items.some((i) => i.isFile() && i.name === "+page.svelte")) {
81
+ const pageServerFile = items.some((i) => i.isFile() && i.name === "+page.server.ts")
82
+ ? join(dir, "+page.server.ts")
83
+ : null;
84
+
85
+ const pageTs = pageServerFile
86
+ ? readTrailingSlash(join(ROUTES_DIR, pageServerFile))
87
+ : null;
88
+ const effectiveTs: TrailingSlash = pageTs ?? currentTrailingSlash;
89
+
90
+ pages.push({
91
+ pattern: toUrlPath(urlSegments),
92
+ page: join(dir, "+page.svelte"),
93
+ layouts: [...currentLayouts],
94
+ pageServer: pageServerFile,
95
+ layoutServers: [...currentLayoutServers],
96
+ trailingSlash: effectiveTs,
97
+ });
98
+ }
99
+
100
+ // Recurse into subdirectories
101
+ for (const entry of items) {
102
+ if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name === "node_modules")
103
+ continue;
104
+
105
+ const dirName = entry.name;
106
+ // Route groups like (public), (auth) are invisible in URLs
107
+ const isGroup = /^\(.*\)$/.test(dirName);
108
+
109
+ walk(
110
+ dir ? join(dir, dirName) : dirName,
111
+ isGroup ? [...urlSegments] : [...urlSegments, dirName],
112
+ currentLayouts,
113
+ currentLayoutServers,
114
+ currentTrailingSlash,
115
+ );
116
+ }
117
+ }
118
+
119
+ walk("", [], [], [], "never");
120
+
121
+ // Warn when a catch-all exists but no exact route covers its prefix.
122
+ // e.g. "/[...slug]" matches everything EXCEPT "/" (which needs its own +page.svelte).
123
+ const exactPatterns = new Set(
124
+ pages.filter((p) => !p.pattern.includes("[")).map((p) => p.pattern),
125
+ );
126
+ for (const p of pages) {
127
+ const m = p.pattern.match(/^(.*?)\/\[\.\.\.(\w+)\]$/);
128
+ if (m) {
129
+ const exactEquivalent = m[1] || "/";
130
+ if (!exactPatterns.has(exactEquivalent)) {
131
+ console.warn(
132
+ `⚠️ No exact route for "${exactEquivalent}" — the catch-all "${p.pattern}" will NOT match it.\n` +
133
+ ` Add a +page.svelte at the "${exactEquivalent}" level to serve that URL.`,
134
+ );
135
+ }
136
+ }
137
+ }
138
+
139
+ const errorPage = existsSync(join(ROUTES_DIR, "+error.svelte")) ? "+error.svelte" : null;
140
+
141
+ return { pages, apis, errorPage };
112
142
  }
113
143
 
114
144
  function toUrlPath(segments: string[]): string {
115
- if (segments.length === 0) return "/";
116
- return "/" + segments.join("/");
145
+ if (segments.length === 0) return "/";
146
+ return "/" + segments.join("/");
117
147
  }