bosia 0.9.7 → 0.9.8
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/package.json +1 -1
- package/src/cli/sync.ts +9 -2
- package/src/core/build.ts +9 -2
- package/src/core/scanner.ts +34 -0
package/package.json
CHANGED
package/src/cli/sync.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { scanRoutes } from "../core/scanner.ts";
|
|
1
|
+
import { scanRoutes, RouteConflictError } from "../core/scanner.ts";
|
|
2
2
|
import { generateRoutesFile } from "../core/routeFile.ts";
|
|
3
3
|
import { generateRouteTypes, ensureRootDirs } from "../core/routeTypes.ts";
|
|
4
4
|
import { loadEnv, classifyEnvVars } from "../core/env.ts";
|
|
@@ -8,7 +8,14 @@ import { findBrandPlaceholders, BRAND_SENTINEL } from "../core/brandGuard.ts";
|
|
|
8
8
|
export async function runSync() {
|
|
9
9
|
const envMode = process.env.NODE_ENV === "production" ? "production" : "development";
|
|
10
10
|
const classifiedEnv = classifyEnvVars(loadEnv(envMode));
|
|
11
|
-
|
|
11
|
+
let manifest;
|
|
12
|
+
try {
|
|
13
|
+
manifest = scanRoutes();
|
|
14
|
+
} catch (err) {
|
|
15
|
+
if (!(err instanceof RouteConflictError)) throw err;
|
|
16
|
+
console.error(`❌ ${err.message}`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
12
19
|
generateRoutesFile(manifest);
|
|
13
20
|
generateRouteTypes(manifest);
|
|
14
21
|
ensureRootDirs();
|
package/src/core/build.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { writeFileSync, readFileSync, rmSync, mkdirSync, existsSync } from "fs";
|
|
|
2
2
|
import { basename, join, relative } from "path";
|
|
3
3
|
import type { RouteManifest } from "./types.ts";
|
|
4
4
|
|
|
5
|
-
import { scanRoutes } from "./scanner.ts";
|
|
5
|
+
import { scanRoutes, RouteConflictError } from "./scanner.ts";
|
|
6
6
|
import { generateRoutesFile } from "./routeFile.ts";
|
|
7
7
|
import { generateRouteTypes, ensureRootDirs } from "./routeTypes.ts";
|
|
8
8
|
import { makeBosiaPlugin } from "./plugin.ts";
|
|
@@ -85,7 +85,14 @@ for (const p of [
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
// 1. Scan routes (or reuse the cached manifest — see 0b-pre)
|
|
88
|
-
|
|
88
|
+
let manifest: RouteManifest;
|
|
89
|
+
try {
|
|
90
|
+
manifest = cachedManifest ?? scanRoutes();
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (!(err instanceof RouteConflictError)) throw err;
|
|
93
|
+
console.error(`❌ ${err.message}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
89
96
|
buildCtx.manifest = manifest;
|
|
90
97
|
console.log(
|
|
91
98
|
`📂 Found ${manifest.pages.length} page route(s)${cachedManifest ? " (cached scan)" : ""}:`,
|
package/src/core/scanner.ts
CHANGED
|
@@ -181,11 +181,45 @@ export function scanRoutes(): RouteManifest {
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
preventConflicts(pages, (p) => p.page);
|
|
185
|
+
preventConflicts(apis, (a) => a.server);
|
|
186
|
+
|
|
184
187
|
const errorPage = existsSync(join(ROUTES_DIR, "+error.svelte")) ? "+error.svelte" : null;
|
|
185
188
|
|
|
186
189
|
return { pages, apis, errorPage };
|
|
187
190
|
}
|
|
188
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Fail when two route files serve the same URL — e.g. `+page.svelte` next to
|
|
194
|
+
* `(public)/+page.svelte`, or `blog/[id]` next to `(app)/blog/[slug]`. Route
|
|
195
|
+
* groups vanish from the URL and param names don't affect matching, so one of
|
|
196
|
+
* the two could never be reached. Same rule as SvelteKit's prevent_conflicts
|
|
197
|
+
* and Next.js's "two parallel pages that resolve to the same path".
|
|
198
|
+
*/
|
|
199
|
+
function preventConflicts<T extends { pattern: string }>(routes: T[], fileOf: (r: T) => string) {
|
|
200
|
+
const seen = new Map<string, T>();
|
|
201
|
+
for (const r of routes) {
|
|
202
|
+
const key = r.pattern.replace(/\[\.\.\.\w+\]/g, "[...]").replace(/\[\w+\]/g, "[]");
|
|
203
|
+
const first = seen.get(key);
|
|
204
|
+
if (!first) {
|
|
205
|
+
seen.set(key, r);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
throw new RouteConflictError(
|
|
209
|
+
`The "${first.pattern}" and "${r.pattern}" routes conflict with each other:\n` +
|
|
210
|
+
` src/routes/${fileOf(first)}\n` +
|
|
211
|
+
` src/routes/${fileOf(r)}\n` +
|
|
212
|
+
` Route groups like (public) are not part of the URL, and [id] matches the same URLs as [slug].\n` +
|
|
213
|
+
` Delete or move one of them.`,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Two route files resolve to the same URL. Callers print `message` and exit. */
|
|
219
|
+
export class RouteConflictError extends Error {
|
|
220
|
+
override name = "RouteConflictError";
|
|
221
|
+
}
|
|
222
|
+
|
|
189
223
|
function toUrlPath(segments: string[]): string {
|
|
190
224
|
if (segments.length === 0) return "/";
|
|
191
225
|
return "/" + segments.join("/");
|