@rangojs/router 0.30.1 → 0.31.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.
@@ -4,5 +4,5 @@ export { generatePerModuleTypesSource, generateRouteTypesSource, } from "./route
4
4
  export { DEFAULT_EXCLUDE_PATTERNS, type ScanFilter, createScanFilter, findTsFiles, isInSkippedScanDir, } from "./route-types/scan-filter.js";
5
5
  export { writePerModuleRouteTypes, writePerModuleRouteTypesForFile, } from "./route-types/per-module-writer.js";
6
6
  export { type UnresolvableReason, type UnresolvableInclude, extractIncludesWithDiagnostics, } from "./route-types/include-resolution.js";
7
- export { extractUrlsFromRouter, extractBasenameFromRouter, type UrlsExtractionResult, buildCombinedRouteMapForRouterFile, detectUnresolvableIncludes, detectUnresolvableIncludesForUrlsFile, findNestedRouterConflict, formatNestedRouterConflictError, findRouterFiles, findHostRouterFiles, writeCombinedRouteTypes, genFileTsPath, resolveSearchSchemas, } from "./route-types/router-processing.js";
7
+ export { extractUrlsFromRouter, type UrlsExtractionResult, buildCombinedRouteMapForRouterFile, detectUnresolvableIncludes, detectUnresolvableIncludesForUrlsFile, findNestedRouterConflict, formatNestedRouterConflictError, findRouterFiles, findHostRouterFiles, writeCombinedRouteTypes, genFileTsPath, resolveSearchSchemas, } from "./route-types/router-processing.js";
8
8
  export { findUrlsVariableNames } from "./route-types/per-module-writer.js";
@@ -1,5 +1,5 @@
1
1
  import ts from "typescript";
2
- export type UnresolvableReason = "factory-call" | "dynamic-expression" | "unresolvable-import" | "file-not-found";
2
+ export type UnresolvableReason = "factory-call" | "dynamic-expression" | "unresolvable-import" | "file-not-found" | "unresolvable-basename";
3
3
  export interface UnresolvableInclude {
4
4
  pathPrefix: string;
5
5
  namePrefix: string | null;
@@ -30,11 +30,6 @@ export type UrlsExtractionResult = {
30
30
  * Returns either a variable name or an inline code block.
31
31
  */
32
32
  export declare function extractUrlsFromRouter(code: string): UrlsExtractionResult | null;
33
- /**
34
- * Extract the `basename` string literal from createRouter({ basename: "..." }).
35
- * Returns the basename value or undefined if not present.
36
- */
37
- export declare function extractBasenameFromRouter(code: string): string | undefined;
38
33
  export declare function genFileTsPath(sourceFile: string): string;
39
34
  export declare function resolveSearchSchemas(publicRouteNames: string[], runtimeSchemas: Record<string, Record<string, string>> | undefined, sourceFile: string): Record<string, Record<string, string>> | undefined;
40
35
  /**
@@ -47,8 +42,9 @@ export declare function buildCombinedRouteMapForRouterFile(routerFilePath: strin
47
42
  };
48
43
  /**
49
44
  * Walk the full include tree starting from a router file and detect
50
- * all includes that the static parser cannot resolve.
51
- * Returns an array of diagnostics; empty means fully resolvable.
45
+ * all includes that the static parser cannot resolve, plus an explicit
46
+ * createRouter `basename` it cannot read (every pattern would lack the
47
+ * prefix). Returns an array of diagnostics; empty means fully resolvable.
52
48
  */
53
49
  export declare function detectUnresolvableIncludes(routerFilePath: string): UnresolvableInclude[];
54
50
  /**
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * The discovery temp RSC server: a minimal Vite server whose module runner
3
3
  * imports the user's router files. Shared by dev discovery
4
- * (discovery/dev-server-phase.ts) and build discovery
5
- * (discovery/build-discovery-phase.ts).
4
+ * (discovery/dev-server-phase.ts), build discovery
5
+ * (discovery/build-discovery-phase.ts) and `rango generate --runtime`
6
+ * (build/runtime-discovery.ts).
6
7
  *
7
8
  * Sits in src/vite/, not src/vite/discovery/: the loader hook URL below
8
- * resolves against this module's import.meta.url, and the published bundle
9
- * (dist/vite/index.js) ships the hook at the same relative path.
9
+ * resolves against this module's import.meta.url, and the published bundles
10
+ * (dist/vite/index.js, dist/bin/rango.js) ship the hook at the same relative
11
+ * path.
10
12
  */
11
13
  import type { ViteDevServer } from "vite";
12
14
  import type { DiscoveryState } from "./discovery/state.js";
@@ -36,4 +38,6 @@ export declare function createTempRscServer(state: DiscoveryState, options?: {
36
38
  * shell capture actually runs.
37
39
  */
38
40
  realSsrEntry?: boolean;
41
+ /** Caller-specific plugins, appended after the forwarded user plugins. */
42
+ plugins?: import("vite").Plugin[];
39
43
  }): Promise<ViteDevServer>;
@@ -20,6 +20,8 @@ export interface ForwardedRunnerConfig {
20
20
  /**
21
21
  * Extract the data-only config slice to mirror into the discovery temp server.
22
22
  * `alias` is included here so callers no longer need to thread it separately.
23
+ * Accepts the unresolved config too: `rango generate --runtime` must not
24
+ * resolve it (that runs rango()'s configResolved, which writes gen files).
23
25
  *
24
26
  * `tsconfigPaths` is forwarded so Vite 8's native tsconfig `paths` resolution
25
27
  * (a top-level `resolve` flag, off by default) reaches the temp server. The
@@ -34,4 +36,4 @@ export interface ForwardedRunnerConfig {
34
36
  * replaced the deprecated `esbuild` transform option with `oxc`, so we read and
35
37
  * forward `oxc` exclusively — no `esbuild` field is touched.
36
38
  */
37
- export declare function pickForwardedRunnerConfig(config: ResolvedConfig): ForwardedRunnerConfig;
39
+ export declare function pickForwardedRunnerConfig(config: Pick<ResolvedConfig | UserConfig, "resolve" | "define" | "oxc">): ForwardedRunnerConfig;
@@ -4841,6 +4841,11 @@ function getAppVersion() {
4841
4841
  }
4842
4842
 
4843
4843
  // src/router/basename.ts
4844
+ function normalizeBasename(basename2) {
4845
+ if (!basename2) return void 0;
4846
+ const trimmed = basename2.replace(/^\/+|\/+$/g, "");
4847
+ return trimmed ? "/" + trimmed : void 0;
4848
+ }
4844
4849
  function prefixBasename(path6, basename2) {
4845
4850
  if (!basename2 || !path6.startsWith("/") || path6 === basename2 || path6.startsWith(basename2 + "/") || path6.startsWith(basename2 + "?") || path6.startsWith(basename2 + "#")) {
4846
4851
  return path6;
@@ -5988,7 +5993,7 @@ function serializeSearchSchema(route2, value) {
5988
5993
  throw projectionError(route2, 'path option "search" must be an object');
5989
5994
  }
5990
5995
  const entries = [];
5991
- for (const key of Object.keys(value).sort()) {
5996
+ for (const key of Object.keys(value)) {
5992
5997
  const schemaValue = value[key];
5993
5998
  if (typeof schemaValue !== "string" || !SEARCH_SCHEMA_VALUES.has(schemaValue)) {
5994
5999
  throw projectionError(
@@ -7141,7 +7146,7 @@ function resolveVersionsFrom(table, routerId2) {
7141
7146
  // package.json
7142
7147
  var package_default = {
7143
7148
  name: "@rangojs/router",
7144
- version: "0.30.1",
7149
+ version: "0.31.0",
7145
7150
  description: "Django-inspired RSC router with composable URL patterns",
7146
7151
  keywords: [
7147
7152
  "react",
@@ -7334,7 +7339,7 @@ var package_default = {
7334
7339
  tag: "latest"
7335
7340
  },
7336
7341
  scripts: {
7337
- build: "pnpm run build:types && pnpm exec esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm exec esbuild src/testing/vitest.ts --bundle --format=esm --outfile=dist/testing/vitest.js --platform=node --packages=external && pnpm exec esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
7342
+ build: "pnpm run build:types && pnpm exec esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm exec esbuild src/testing/vitest.ts --bundle --format=esm --outfile=dist/testing/vitest.js --platform=node --packages=external && pnpm exec esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js && mkdir -p dist/bin/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/bin/plugins/cloudflare-protocol-loader-hook.mjs",
7338
7343
  "build:types": "rm -rf dist/types && pnpm exec tsc -p tsconfig.types.json",
7339
7344
  prepublishOnly: "pnpm build",
7340
7345
  typecheck: "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit && tsc -p tsconfig.augment-check.json --noEmit",
@@ -9325,14 +9330,7 @@ function collectCreateRouterCallChain(endpoint) {
9325
9330
  current = receiver;
9326
9331
  }
9327
9332
  }
9328
- function extractAllUrlsFromRouter(code) {
9329
- const sourceFile = ts5.createSourceFile(
9330
- "router.tsx",
9331
- code,
9332
- ts5.ScriptTarget.Latest,
9333
- true,
9334
- ts5.ScriptKind.TSX
9335
- );
9333
+ function findRouterUrls(sourceFile) {
9336
9334
  let result = null;
9337
9335
  function visit(node) {
9338
9336
  if (result) return;
@@ -9360,7 +9358,7 @@ function extractAllUrlsFromRouter(code) {
9360
9358
  if (urls2) extracted.push(urls2);
9361
9359
  }
9362
9360
  if (extracted.length > 0) {
9363
- result = extracted;
9361
+ result = { createRouterCall, extractions: extracted };
9364
9362
  return;
9365
9363
  }
9366
9364
  }
@@ -9368,13 +9366,9 @@ function extractAllUrlsFromRouter(code) {
9368
9366
  ts5.forEachChild(node, visit);
9369
9367
  }
9370
9368
  visit(sourceFile);
9371
- return result ?? [];
9372
- }
9373
- function extractUrlsFromRouter(code) {
9374
- const extractions = extractAllUrlsFromRouter(code);
9375
- return extractions[extractions.length - 1] ?? null;
9369
+ return result;
9376
9370
  }
9377
- function extractBasenameFromRouter(code) {
9371
+ function extractAllUrlsFromRouter(code) {
9378
9372
  const sourceFile = ts5.createSourceFile(
9379
9373
  "router.tsx",
9380
9374
  code,
@@ -9382,25 +9376,112 @@ function extractBasenameFromRouter(code) {
9382
9376
  true,
9383
9377
  ts5.ScriptKind.TSX
9384
9378
  );
9385
- let result;
9386
- function visit(node) {
9387
- if (result !== void 0) return;
9388
- if (ts5.isCallExpression(node) && ts5.isIdentifier(node.expression) && node.expression.text === "createRouter") {
9389
- for (const arg of node.arguments) {
9390
- if (ts5.isObjectLiteralExpression(arg)) {
9391
- for (const prop of arg.properties) {
9392
- if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "basename" && ts5.isStringLiteral(prop.initializer)) {
9393
- result = prop.initializer.text;
9394
- return;
9395
- }
9396
- }
9379
+ return findRouterUrls(sourceFile)?.extractions ?? [];
9380
+ }
9381
+ function extractUrlsFromRouter(code) {
9382
+ const extractions = extractAllUrlsFromRouter(code);
9383
+ return extractions[extractions.length - 1] ?? null;
9384
+ }
9385
+ function unwrapTypeOperators(expr) {
9386
+ while (ts5.isAsExpression(expr) || ts5.isSatisfiesExpression(expr)) {
9387
+ expr = expr.expression;
9388
+ }
9389
+ return expr;
9390
+ }
9391
+ function hasModifier(statement, kind) {
9392
+ return ts5.canHaveModifiers(statement) && (ts5.getModifiers(statement)?.some((m) => m.kind === kind) ?? false);
9393
+ }
9394
+ function findTopLevelConst(sourceFile, name, exported) {
9395
+ for (const statement of sourceFile.statements) {
9396
+ if (ts5.isVariableStatement(statement) && statement.declarationList.flags & ts5.NodeFlags.Const && !hasModifier(statement, ts5.SyntaxKind.DeclareKeyword) && (!exported || hasModifier(statement, ts5.SyntaxKind.ExportKeyword))) {
9397
+ for (const decl of statement.declarationList.declarations) {
9398
+ if (ts5.isIdentifier(decl.name) && decl.name.text === name) {
9399
+ return decl.initializer && unwrapTypeOperators(decl.initializer);
9397
9400
  }
9398
9401
  }
9399
9402
  }
9400
- ts5.forEachChild(node, visit);
9401
9403
  }
9402
- visit(sourceFile);
9403
- return result;
9404
+ return void 0;
9405
+ }
9406
+ function findNamedImport(sourceFile, name) {
9407
+ for (const statement of sourceFile.statements) {
9408
+ if (!ts5.isImportDeclaration(statement) || !ts5.isStringLiteral(statement.moduleSpecifier) || statement.importClause?.isTypeOnly) {
9409
+ continue;
9410
+ }
9411
+ const bindings = statement.importClause?.namedBindings;
9412
+ if (!bindings || !ts5.isNamedImports(bindings)) continue;
9413
+ for (const el of bindings.elements) {
9414
+ if (!el.isTypeOnly && el.name.text === name) {
9415
+ return {
9416
+ specifier: statement.moduleSpecifier.text,
9417
+ exportedName: el.propertyName?.text ?? name
9418
+ };
9419
+ }
9420
+ }
9421
+ }
9422
+ return void 0;
9423
+ }
9424
+ function resolveTopLevelConst(name, sourceFile, routerFilePath) {
9425
+ const local = findTopLevelConst(sourceFile, name, false);
9426
+ if (local) return local;
9427
+ const imported = findNamedImport(sourceFile, name);
9428
+ if (!imported) return void 0;
9429
+ const target = resolveImportPath(imported.specifier, routerFilePath);
9430
+ if (!target) return void 0;
9431
+ const targetSource = readFileSync4(target, "utf-8");
9432
+ return findTopLevelConst(
9433
+ ts5.createSourceFile(target, targetSource, ts5.ScriptTarget.Latest, true),
9434
+ imported.exportedName,
9435
+ true
9436
+ );
9437
+ }
9438
+ function isTopLevelScope(node) {
9439
+ for (let p = node.parent; !ts5.isSourceFile(p); p = p.parent) {
9440
+ if (ts5.isFunctionLike(p) || ts5.isClassLike(p) || ts5.isBlock(p) || ts5.isCaseBlock(p) || ts5.isModuleBlock(p) || ts5.isIterationStatement(p, false)) {
9441
+ return false;
9442
+ }
9443
+ }
9444
+ return true;
9445
+ }
9446
+ function resolveRouterBasename(routerSource, routerFilePath) {
9447
+ const sourceFile = ts5.createSourceFile(
9448
+ routerFilePath,
9449
+ routerSource,
9450
+ ts5.ScriptTarget.Latest,
9451
+ true,
9452
+ ts5.ScriptKind.TSX
9453
+ );
9454
+ const options = findRouterUrls(sourceFile)?.createRouterCall.arguments[0];
9455
+ const prop = options && ts5.isObjectLiteralExpression(options) ? options.properties.find(
9456
+ (p) => (ts5.isPropertyAssignment(p) || ts5.isShorthandPropertyAssignment(p)) && ts5.isIdentifier(p.name) && p.name.text === "basename"
9457
+ ) : void 0;
9458
+ if (!prop) return {};
9459
+ const valueExpr = ts5.isPropertyAssignment(prop) ? prop.initializer : prop.name;
9460
+ const expr = unwrapTypeOperators(valueExpr);
9461
+ let value = getStringValue(expr);
9462
+ if (value === null && isTopLevelScope(prop)) {
9463
+ if (ts5.isIdentifier(expr)) {
9464
+ const init = resolveTopLevelConst(expr.text, sourceFile, routerFilePath);
9465
+ value = init ? getStringValue(init) : null;
9466
+ } else if (ts5.isPropertyAccessExpression(expr) && ts5.isIdentifier(expr.expression)) {
9467
+ const owner = resolveTopLevelConst(
9468
+ expr.expression.text,
9469
+ sourceFile,
9470
+ routerFilePath
9471
+ );
9472
+ value = owner ? resolveConstMember(owner, expr.name.text) : null;
9473
+ }
9474
+ }
9475
+ return value === null ? { unresolved: valueExpr.getText(sourceFile).replace(/\s+/g, " ") } : { value };
9476
+ }
9477
+ function resolveConstMember(owner, name) {
9478
+ if (!ts5.isObjectLiteralExpression(owner) || owner.properties.some(ts5.isSpreadAssignment)) {
9479
+ return null;
9480
+ }
9481
+ const member = owner.properties.find(
9482
+ (p) => ts5.isPropertyAssignment(p) && ts5.isIdentifier(p.name) && p.name.text === name
9483
+ );
9484
+ return member ? getStringValue(unwrapTypeOperators(member.initializer)) : null;
9404
9485
  }
9405
9486
  function applyBasenameToRoutes(result, basename2) {
9406
9487
  const prefixed = {};
@@ -9498,8 +9579,9 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
9498
9579
  if (extractions.length === 0) {
9499
9580
  return { routes: {}, searchSchemas: {} };
9500
9581
  }
9501
- const rawBasename = extractBasenameFromRouter(routerSource);
9502
- const basename2 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
9582
+ const basename2 = normalizeBasename(
9583
+ resolveRouterBasename(routerSource, routerFilePath).value
9584
+ );
9503
9585
  const result = { routes: {}, searchSchemas: {} };
9504
9586
  const memo = createScanMemo();
9505
9587
  for (const extraction of extractions) {
@@ -12693,7 +12775,8 @@ async function createTempRscServer(state, options = {}) {
12693
12775
  // to resolveId/load (plus transform for import rewriters, see
12694
12776
  // IMPORT_REWRITE_PLUGINS) and placed last so framework resolution runs first;
12695
12777
  // Vite re-sorts by `enforce`, so `enforce: "pre"` resolvers still lead.
12696
- ...state.userResolvePlugins
12778
+ ...state.userResolvePlugins,
12779
+ ...options.plugins ?? []
12697
12780
  ]
12698
12781
  });
12699
12782
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rangojs/router",
3
- "version": "0.30.1",
3
+ "version": "0.31.0",
4
4
  "description": "Django-inspired RSC router with composable URL patterns",
5
5
  "keywords": [
6
6
  "react",
@@ -259,7 +259,7 @@
259
259
  "node": ">=24.0.0"
260
260
  },
261
261
  "scripts": {
262
- "build": "pnpm run build:types && pnpm exec esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm exec esbuild src/testing/vitest.ts --bundle --format=esm --outfile=dist/testing/vitest.js --platform=node --packages=external && pnpm exec esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
262
+ "build": "pnpm run build:types && pnpm exec esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm exec esbuild src/testing/vitest.ts --bundle --format=esm --outfile=dist/testing/vitest.js --platform=node --packages=external && pnpm exec esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js && mkdir -p dist/bin/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/bin/plugins/cloudflare-protocol-loader-hook.mjs",
263
263
  "build:types": "rm -rf dist/types && pnpm exec tsc -p tsconfig.types.json",
264
264
  "typecheck": "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit && tsc -p tsconfig.augment-check.json --noEmit",
265
265
  "test": "playwright test",
@@ -445,11 +445,11 @@ npx rango generate src/urls.tsx src/api/
445
445
 
446
446
  Three modes:
447
447
 
448
- | Flag | Behavior |
449
- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
450
- | (none) | Static parser. Exits with an error, writing nothing, if an `include()` can't be resolved statically (factory or dynamic). |
451
- | `--static` | Static parser, but writes partial output and prints the unresolvable includes as warnings. |
452
- | `--runtime` | Vite-based runtime discovery (full coverage; needs `vite`; `@vitejs/plugin-rsc` comes with the router). `--config <path>` picks the config. |
448
+ | Flag | Behavior |
449
+ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
450
+ | (none) | Static parser. Exits with an error, writing nothing, if an `include()` can't be resolved statically (factory or dynamic), or an explicit `createRouter` `basename` isn't a string literal or top-level const. |
451
+ | `--static` | Static parser, but writes partial output (an unreadable basename leaves patterns unprefixed) and prints the diagnostics as warnings. |
452
+ | `--runtime` | Vite-based runtime discovery (full coverage; needs `vite`; `@vitejs/plugin-rsc` comes with the router). `--config <path>` picks the config. |
453
453
 
454
454
  ### Auto-detection
455
455
 
@@ -111,7 +111,8 @@ imports it directly, so `Rango.Env`, `Rango.Vars`, and optional
111
111
  and client helpers.
112
112
 
113
113
  CLI flags: the default mode fails (and writes nothing) when an `include()` cannot
114
- be resolved statically (factory functions, dynamic expressions); `--static`
114
+ be resolved statically (factory functions, dynamic expressions) or an explicit
115
+ `createRouter` `basename` is not a string literal or top-level const; `--static`
115
116
  accepts partial output with warnings; `--runtime` uses Vite-based discovery for
116
117
  full coverage (needs `vite`, optional
117
118
  `--config <path>`). Passing a directory scans it for router and `urls()` files.
package/src/bin/rango.ts CHANGED
@@ -189,23 +189,23 @@ function runStaticGeneration(args: string[], mode: "default" | "static") {
189
189
 
190
190
  if (uniqueDiagnostics.length > 0 && mode === "default") {
191
191
  // Hard error: no files written
192
- console.error("\n[rango] Unresolvable includes detected:\n");
192
+ console.error("\n[rango] Unresolvable routes detected:\n");
193
193
  formatDiagnostics(uniqueDiagnostics);
194
194
  console.error(
195
- "\nThe static parser cannot resolve these includes because they use " +
196
- "factory functions or dynamic expressions.\n\n" +
195
+ "\nThe static parser cannot resolve these includes or basename because " +
196
+ "they use factory functions or dynamic expressions. A basename must be " +
197
+ "a string literal or a top-level const (in the router file or imported " +
198
+ "from a relative module).\n\n" +
197
199
  "Options:\n" +
198
200
  " rango generate <path> --runtime Use Vite-based discovery (requires vite)\n" +
199
- " rango generate <path> --static Accept partial output (missing routes above)\n",
201
+ " rango generate <path> --static Accept partial output (missing routes or basename prefix above)\n",
200
202
  );
201
203
  process.exit(1);
202
204
  }
203
205
 
204
206
  if (uniqueDiagnostics.length > 0 && mode === "static") {
205
207
  // Warning: partial output accepted
206
- console.warn(
207
- "\n[rango] Warning: partial output (unresolvable includes):\n",
208
- );
208
+ console.warn("\n[rango] Warning: partial output (unresolvable routes):\n");
209
209
  formatDiagnostics(uniqueDiagnostics);
210
210
  console.warn("");
211
211
  }
@@ -317,7 +317,12 @@ function formatDiagnostics(
317
317
  diagnostics: Array<UnresolvableInclude & { routerFile: string }>,
318
318
  ) {
319
319
  for (const d of diagnostics) {
320
- const prefix = d.namePrefix ? `${d.namePrefix}.*` : `${d.pathPrefix}*`;
320
+ const prefix =
321
+ d.reason === "unresolvable-basename"
322
+ ? "createRouter basename"
323
+ : d.namePrefix
324
+ ? `${d.namePrefix}.*`
325
+ : `${d.pathPrefix}*`;
321
326
  console.error(` ${prefix}`);
322
327
  console.error(` Reason: ${d.reason} -- ${d.detail}`);
323
328
  console.error(` Source: ${d.sourceFile}`);
@@ -26,7 +26,6 @@ export {
26
26
  } from "./route-types/include-resolution.js";
27
27
  export {
28
28
  extractUrlsFromRouter,
29
- extractBasenameFromRouter,
30
29
  type UrlsExtractionResult,
31
30
  buildCombinedRouteMapForRouterFile,
32
31
  detectUnresolvableIncludes,
@@ -12,7 +12,8 @@ export type UnresolvableReason =
12
12
  | "factory-call"
13
13
  | "dynamic-expression"
14
14
  | "unresolvable-import"
15
- | "file-not-found";
15
+ | "file-not-found"
16
+ | "unresolvable-basename";
16
17
 
17
18
  export interface UnresolvableInclude {
18
19
  pathPrefix: string;