theokit 0.2.4 → 0.4.0-beta.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.
- package/dist/{app-typed-client-US2ZXBEC.js → app-typed-client-62FYBLVJ.js} +3 -3
- package/dist/{app-typed-client-6GC6VWXS.js → app-typed-client-ITIYTBXO.js} +3 -3
- package/dist/{build-MREW6MVS.js → build-HIGHJQQV.js} +8 -8
- package/dist/{chunk-YWWHK7R6.js → chunk-2VQKDRVF.js} +2 -2
- package/dist/{chunk-2BQYEBCK.js → chunk-B6RP57M3.js} +84 -2
- package/dist/chunk-B6RP57M3.js.map +1 -0
- package/dist/{chunk-5OKZY2VL.js → chunk-BIGBFZSU.js} +10 -5
- package/dist/chunk-BIGBFZSU.js.map +1 -0
- package/dist/{chunk-X2OLD264.js → chunk-JAAHOGKI.js} +10 -5
- package/dist/chunk-JAAHOGKI.js.map +1 -0
- package/dist/{chunk-SNDZQ64K.js → chunk-OXIQI2XZ.js} +84 -2
- package/dist/chunk-OXIQI2XZ.js.map +1 -0
- package/dist/{chunk-BE2LKDI7.js → chunk-TRH2L3FI.js} +2 -2
- package/dist/cli/index.js +29 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{dev-6P2DM33Q.js → dev-266MVCVA.js} +6 -6
- package/dist/{dev-emit-PRSRPDHA.js → dev-emit-NO6OZJOQ.js} +3 -3
- package/dist/{dev-emit-MLDFOLUU.js → dev-emit-RBSFZZNO.js} +3 -3
- package/dist/{dist-E6BL4ZVY.js → dist-5V77Z223.js} +1 -1
- package/dist/index.js +2 -2
- package/dist/{openapi-SOKZTFE3.js → openapi-FNVSWZOL.js} +3 -3
- package/dist/{registry-5QFTLOL2.js → registry-4MZ26TZD.js} +1 -1
- package/dist/router-RGZFX75G.js +274 -0
- package/dist/router-RGZFX75G.js.map +1 -0
- package/dist/{routes-XVRAUH6H.js → routes-H4SPLFHJ.js} +2 -2
- package/dist/server/index.js +2 -2
- package/dist/server-routes-hmr-GZJGPWMS.js +44 -0
- package/dist/server-routes-hmr-GZJGPWMS.js.map +1 -0
- package/dist/server-routes-hmr-PBHSKCQJ.js +42 -0
- package/dist/server-routes-hmr-PBHSKCQJ.js.map +1 -0
- package/dist/{start-R7XSQL5L.js → start-HX3QUSOS.js} +5 -5
- package/dist/vite-plugin/index.js +2 -2
- package/dist/{vite-plugin-6MPHTKIW.js → vite-plugin-IK3NOWXS.js} +3 -3
- package/package.json +2 -2
- package/dist/chunk-2BQYEBCK.js.map +0 -1
- package/dist/chunk-5OKZY2VL.js.map +0 -1
- package/dist/chunk-SNDZQ64K.js.map +0 -1
- package/dist/chunk-X2OLD264.js.map +0 -1
- /package/dist/{app-typed-client-6GC6VWXS.js.map → app-typed-client-62FYBLVJ.js.map} +0 -0
- /package/dist/{app-typed-client-US2ZXBEC.js.map → app-typed-client-ITIYTBXO.js.map} +0 -0
- /package/dist/{build-MREW6MVS.js.map → build-HIGHJQQV.js.map} +0 -0
- /package/dist/{chunk-YWWHK7R6.js.map → chunk-2VQKDRVF.js.map} +0 -0
- /package/dist/{chunk-BE2LKDI7.js.map → chunk-TRH2L3FI.js.map} +0 -0
- /package/dist/{dev-6P2DM33Q.js.map → dev-266MVCVA.js.map} +0 -0
- /package/dist/{dev-emit-PRSRPDHA.js.map → dev-emit-NO6OZJOQ.js.map} +0 -0
- /package/dist/{dev-emit-MLDFOLUU.js.map → dev-emit-RBSFZZNO.js.map} +0 -0
- /package/dist/{dist-E6BL4ZVY.js.map → dist-5V77Z223.js.map} +0 -0
- /package/dist/{openapi-SOKZTFE3.js.map → openapi-FNVSWZOL.js.map} +0 -0
- /package/dist/{registry-5QFTLOL2.js.map → registry-4MZ26TZD.js.map} +0 -0
- /package/dist/{routes-XVRAUH6H.js.map → routes-H4SPLFHJ.js.map} +0 -0
- /package/dist/{start-R7XSQL5L.js.map → start-HX3QUSOS.js.map} +0 -0
- /package/dist/{vite-plugin-6MPHTKIW.js.map → vite-plugin-IK3NOWXS.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/contracts/http-methods.ts","../src/server/scan/scan.ts","../src/server/scan/detect-http-methods.ts","../src/server/scan/errors.ts","../src/server/scan/match.ts","../src/server/scan/ws-scan.ts"],"sourcesContent":["/**\n * Canonical HTTP method set used by the typed client codegen + Proxy runtime.\n * Kept here (under `server/scan/`) because the SCAN is the first consumer —\n * `vite-plugin/app-typed-client.ts` (Phase 2) and `client/app-client.ts` (Phase 3)\n * import from here to ensure a single source of truth.\n *\n * Lowercase variants are derived via `.toLowerCase()` at call sites; we do not\n * export them separately to avoid drift between the two lists.\n */\n\nexport const HTTP_METHODS = [\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n 'HEAD',\n 'OPTIONS',\n] as const\n\nexport type HttpMethod = (typeof HTTP_METHODS)[number]\n\nexport const HTTP_METHOD_LOWERCASE = HTTP_METHODS.map((m) => m.toLowerCase()) as readonly string[]\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/routes/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { existsSync, statSync } from 'node:fs'\nimport { basename, extname, join, relative } from 'node:path'\n\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nimport { detectExportedHttpMethods } from './detect-http-methods.js'\nimport { RouterConventionError } from './errors.js'\nimport { compilePattern, type ServerRouteNode } from './match.js'\n\nconst ROUTE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\n\n// EC-4: Co-located unit/spec tests must be silently skipped by the scanner,\n// BEFORE the dotted-basename check fires. Matches `*.test.ts|tsx|js|jsx`\n// and `*.spec.ts|tsx|js|jsx`.\nconst TEST_OR_SPEC_RE = /\\.(test|spec)\\.[jt]sx?$/\n\nfunction isTestOrSpecFile(filePath: string): boolean {\n return TEST_OR_SPEC_RE.test(basename(filePath))\n}\n\nfunction hasDotOutsideBrackets(segment: string): boolean {\n let depth = 0\n for (const ch of segment) {\n if (ch === '[') depth++\n else if (ch === ']') depth--\n else if (ch === '.' && depth === 0) return true\n }\n return false\n}\n\nfunction splitDottedSegmentOutsideBrackets(segment: string): string[] {\n const parts: string[] = []\n let current = ''\n let depth = 0\n for (const ch of segment) {\n if (ch === '[') {\n depth++\n current += ch\n } else if (ch === ']') {\n depth--\n current += ch\n } else if (ch === '.' && depth === 0) {\n if (current) parts.push(current)\n current = ''\n } else {\n current += ch\n }\n }\n if (current) parts.push(current)\n return parts\n}\n\nfunction buildDirectoryNestedSuggestion(filePath: string, routesDir: string): string {\n const rel = relative(routesDir, filePath).replace(/\\\\/g, '/')\n const ext = extname(rel)\n const withoutExt = rel.slice(0, -ext.length)\n const segments = withoutExt.split('/').flatMap(splitDottedSegmentOutsideBrackets)\n return `routes/${segments.join('/')}${ext}`\n}\n\nfunction assertNoDottedSegment(filePath: string, routesDir: string): void {\n const rel = relative(routesDir, filePath).replace(/\\\\/g, '/')\n const ext = extname(rel)\n const withoutExt = rel.slice(0, -ext.length)\n const segments = withoutExt.split('/')\n for (const seg of segments) {\n if (hasDotOutsideBrackets(seg)) {\n throw new RouterConventionError({\n file: filePath,\n suggestion: buildDirectoryNestedSuggestion(filePath, routesDir),\n })\n }\n }\n}\n\nfunction fileToRoutePath(filePath: string, routesDir: string): string {\n let rel = relative(routesDir, filePath)\n // Strip extension\n const ext = extname(rel)\n rel = rel.slice(0, -ext.length)\n // Normalize separators\n rel = rel.replace(/\\\\/g, '/')\n // Strip index suffix\n if (rel.endsWith('/index')) {\n rel = rel.slice(0, -6)\n } else if (rel === 'index') {\n rel = ''\n }\n // Replace [...param] with :...param (catch-all, before regular params).\n // Replace [param] with :param. Inputs are file paths bounded by the\n // OS filename limit; the bracket capture is bounded by `]`.\n rel = rel.replace(/\\[\\.\\.\\.([^\\]]+)\\]/g, ':...$1')\n // eslint-disable-next-line sonarjs/slow-regex -- bounded by `]`; input is a single filename\n rel = rel.replace(/\\[([^\\]]+)\\]/g, ':$1')\n return `/api/${rel}`\n}\n\nexport function scanServerRoutes(serverDir: string): ServerRouteNode[] {\n const routesDir = join(serverDir, 'routes')\n if (!existsSync(routesDir) || !statSync(routesDir).isDirectory()) {\n return []\n }\n\n const results: ServerRouteNode[] = []\n walkSourceFiles(routesDir, { extensions: ROUTE_EXTENSIONS }, (absPath) => {\n // EC-4: skip co-located test/spec files BEFORE the dotted-basename check\n if (isTestOrSpecFile(absPath)) return\n\n // G6 T1.1: reject dotted basenames (legacy convention that produced wrong\n // paramNames due to greedy `:(?:\\.\\.\\.)?([^/]+)` regex in compilePattern).\n assertNoDottedSegment(absPath, routesDir)\n\n const routePath = fileToRoutePath(absPath, routesDir)\n const { pattern, paramNames } = compilePattern(routePath)\n const methods = detectExportedHttpMethods(absPath)\n results.push({\n filePath: absPath,\n routePath,\n paramNames,\n pattern,\n methods,\n })\n })\n\n // T1.4 / EC-2 — refuse to scan if a user route collides with the reserved\n // batch endpoint path. User must rename or disable batching.\n const conflicting = results.find((r) => r.routePath === '/api/__theo_batch__')\n if (conflicting) {\n throw new Error(\n `Server route ${conflicting.filePath} resolves to '/api/__theo_batch__' which is reserved for the batch endpoint. Rename the route or disable batching in theo.config.ts.`,\n )\n }\n\n // Sort: static first, then dynamic, then catch-all last\n const isCatchAll = (route: ServerRouteNode) => route.routePath.includes(':...')\n results.sort((a, b) => {\n const aStatic = a.paramNames.length === 0\n const bStatic = b.paramNames.length === 0\n const aCatchAll = isCatchAll(a)\n const bCatchAll = isCatchAll(b)\n\n // Static routes first\n if (aStatic && !bStatic) return -1\n if (!aStatic && bStatic) return 1\n // Catch-all routes last\n if (aCatchAll && !bCatchAll) return 1\n if (!aCatchAll && bCatchAll) return -1\n return a.routePath.localeCompare(b.routePath)\n })\n\n return results\n}\n","/**\n * Detect which HTTP-method named exports a route file declares.\n *\n * Uses the TypeScript compiler API (not regex) per G1 edge-case review EC-4:\n * regex over file content emits false positives for `// export const GET = ...`\n * in comments and `` `export const GET = ...` `` in template literals. AST\n * walking avoids both classes of bug.\n *\n * `typescript` ships as CommonJS with internal dynamic `require('fs')`.\n * When loaded via ESM `import`, the dynamic requires fail at module-bootstrap.\n * We use `createRequire(import.meta.url)` to keep the package on its native\n * CJS path. The type-only namespace import gives us the AST helpers shape.\n *\n * Returns the set of HTTP methods (uppercase) the file exports. Empty array\n * means the file has no HTTP exports (the route file is util-only).\n */\n\nimport { readFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\n\nimport type * as TS from 'typescript'\n\nimport { HTTP_METHODS, type HttpMethod } from '../../core/contracts/http-methods.js'\n\nconst require_ = createRequire(import.meta.url)\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nconst ts = require_('typescript') as typeof TS\n\nconst HTTP_METHOD_NAMES = new Set<string>(HTTP_METHODS)\n\nfunction hasExportModifier(modifiers: readonly TS.Modifier[] | undefined): boolean {\n if (!modifiers) return false\n for (const m of modifiers) {\n if (m.kind === ts.SyntaxKind.ExportKeyword) return true\n }\n return false\n}\n\nfunction collectFromStatement(stmt: TS.Statement, found: Set<HttpMethod>): void {\n // `export const GET = ...` / `export function GET ...` / `export async function GET ...`\n if (ts.isVariableStatement(stmt) && hasExportModifier(ts.getModifiers(stmt))) {\n for (const decl of stmt.declarationList.declarations) {\n if (ts.isIdentifier(decl.name) && HTTP_METHOD_NAMES.has(decl.name.text)) {\n found.add(decl.name.text as HttpMethod)\n }\n }\n return\n }\n\n if (\n (ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt)) &&\n hasExportModifier(ts.getModifiers(stmt))\n ) {\n if (stmt.name && HTTP_METHOD_NAMES.has(stmt.name.text)) {\n found.add(stmt.name.text as HttpMethod)\n }\n return\n }\n\n // `export { GET }` / `export { handler as GET } from './shared'` (EC-5)\n if (ts.isExportDeclaration(stmt) && stmt.exportClause && ts.isNamedExports(stmt.exportClause)) {\n for (const spec of stmt.exportClause.elements) {\n // spec.name is the exported (re-)name; spec.propertyName is the original (when renamed)\n if (HTTP_METHOD_NAMES.has(spec.name.text)) {\n found.add(spec.name.text as HttpMethod)\n }\n }\n }\n}\n\nexport function detectExportedHttpMethods(filePath: string, content?: string): HttpMethod[] {\n const src = content ?? readFileSync(filePath, 'utf-8')\n const sourceFile = ts.createSourceFile(\n filePath,\n src,\n ts.ScriptTarget.Latest,\n /* setParentNodes */ false,\n ts.ScriptKind.TS,\n )\n const found = new Set<HttpMethod>()\n for (const stmt of sourceFile.statements) {\n collectFromStatement(stmt, found)\n }\n return [...found].sort()\n}\n","/**\n * Router-convention errors thrown by the server-route scanner.\n *\n * Plan: .claude/knowledge-base/plans/g6-router-convention-plan.md v1.1\n *\n * theokit 0.4.0+ enforces directory-nested file-system routing\n * (`auth/[provider]/login.ts`) and REJECTS dotted-basename routes\n * (`auth.[provider].login.ts`) because the legacy regex extracted the\n * dotted basename incorrectly — `params.provider` was undefined at request\n * time. See ADR-XXX (router-convention-decision) in CHANGELOG 0.4.0.\n */\n\n/**\n * Canonical migration guide URL. T4.2 establishes this as the authoritative\n * landing page. EC-3: error message uses this constant so the URL never\n * drifts from the doc location.\n */\nexport const ROUTER_MIGRATION_GUIDE_URL = 'https://theokit.dev/migration/0.3-to-0.4-router'\n\nexport interface RouterConventionErrorOptions {\n /** Absolute path of the offending route file. */\n file: string\n /** Suggested directory-nested replacement path (relative, e.g. `routes/auth/[provider]/login.ts`). */\n suggestion: string\n /** Migration guide URL (defaults to `ROUTER_MIGRATION_GUIDE_URL`). */\n migrationUrl?: string\n}\n\n/**\n * Thrown by `scanServerRoutes` when a route file uses the legacy\n * dotted-basename convention (`auth.[provider].login.ts`).\n *\n * The error is FAIL-FAST by design — running with a route that has wrong\n * `paramNames` produces silent 404s at request time, which is strictly\n * worse than a build-time error.\n */\nexport class RouterConventionError extends Error {\n override readonly name = 'RouterConventionError'\n readonly file: string\n readonly suggestion: string\n readonly migrationUrl: string\n\n constructor(opts: RouterConventionErrorOptions) {\n const migrationUrl = opts.migrationUrl ?? ROUTER_MIGRATION_GUIDE_URL\n const message = [\n `Router convention violation: dotted route basename is not supported in theokit 0.4+.`,\n ``,\n ` File: ${opts.file}`,\n ` Use directory-nested form: ${opts.suggestion}`,\n ``,\n `Migration guide: ${migrationUrl}`,\n `Run \\`theokit migrate router\\` to convert all dotted basenames automatically.`,\n ].join('\\n')\n super(message)\n this.file = opts.file\n this.suggestion = opts.suggestion\n this.migrationUrl = migrationUrl\n }\n}\n","export interface ServerRouteNode {\n filePath: string\n routePath: string\n paramNames: string[]\n pattern: RegExp\n /** HTTP methods (uppercase) the route file exports. Optional for backward\n * compatibility with manifests generated before G1. Empty array means the\n * file has no HTTP exports (util-only); undefined means \"not detected\". */\n methods?: string[]\n}\n\nexport function compilePattern(routePath: string): {\n pattern: RegExp\n paramNames: string[]\n} {\n const paramNames: string[] = []\n // Single pass: handle both catch-all (:...name) and regular (:name) params\n const regexStr = routePath.replace(/:(?:\\.\\.\\.)?([^/]+)/g, (match: string, name: string) => {\n paramNames.push(name)\n // Catch-all matches across slashes, regular matches single segment\n return match.startsWith(':...') ? '(.+)' : '([^/]+)'\n })\n // `regexStr` is derived from a developer-authored route path (build-time\n // input, not HTTP-controlled). The `security/detect-non-literal-regexp`\n // rule cannot see this constraint — disable narrowly.\n // eslint-disable-next-line security/detect-non-literal-regexp -- route pattern from build-time scan, never HTTP input\n return { pattern: new RegExp(`^${regexStr}$`), paramNames }\n}\n\nexport function matchRoute(\n url: string,\n routes: ServerRouteNode[],\n): { route: ServerRouteNode; params: Record<string, string> } | null {\n // Strip query string and trailing slash\n let path = url.split('?')[0]\n if (path.length > 1 && path.endsWith('/')) {\n path = path.slice(0, -1)\n }\n\n for (const route of routes) {\n const match = route.pattern.exec(path)\n if (match) {\n const params: Record<string, string> = {}\n route.paramNames.forEach((name, i) => {\n params[name] = match[i + 1]\n })\n return { route, params }\n }\n }\n return null\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/ws/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { existsSync, statSync } from 'node:fs'\nimport { extname, join, relative } from 'node:path'\n\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nconst WS_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\n\nexport interface WebSocketRouteNode {\n filePath: string\n wsPath: string\n}\n\nexport function scanWebSocketRoutes(serverDir: string): WebSocketRouteNode[] {\n const wsDir = join(serverDir, 'ws')\n if (!existsSync(wsDir) || !statSync(wsDir).isDirectory()) {\n return []\n }\n\n const results: WebSocketRouteNode[] = []\n walkSourceFiles(wsDir, { extensions: WS_EXTENSIONS }, (absPath) => {\n let rel = relative(wsDir, absPath)\n rel = rel.replace(/\\\\/g, '/')\n rel = rel.slice(0, -extname(rel).length)\n if (rel.endsWith('/index')) rel = rel.slice(0, -6)\n else if (rel === 'index') rel = ''\n results.push({\n filePath: absPath,\n wsPath: `/ws/${rel}`,\n })\n })\n return results\n}\n"],"mappings":"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","names":["existsSync","statSync","extname","join","relative","join","existsSync","statSync","relative","extname"]}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
compilePattern,
|
|
5
5
|
scanServerRoutes,
|
|
6
6
|
scanWebSocketRoutes
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-OXIQI2XZ.js";
|
|
8
8
|
import {
|
|
9
9
|
scanServerActions
|
|
10
10
|
} from "./chunk-5OFPQK6N.js";
|
|
@@ -72,4 +72,4 @@ export {
|
|
|
72
72
|
writeManifest,
|
|
73
73
|
loadManifest
|
|
74
74
|
};
|
|
75
|
-
//# sourceMappingURL=chunk-
|
|
75
|
+
//# sourceMappingURL=chunk-TRH2L3FI.js.map
|
package/dist/cli/index.js
CHANGED
|
@@ -6,12 +6,12 @@ import "../chunk-KT3MWNZG.js";
|
|
|
6
6
|
import cac from "cac";
|
|
7
7
|
var cli = cac("theokit");
|
|
8
8
|
cli.command("dev", "Start development server").option("--port <port>", "Port number").action(async (options) => {
|
|
9
|
-
const { devCommand } = await import("../dev-
|
|
9
|
+
const { devCommand } = await import("../dev-266MVCVA.js");
|
|
10
10
|
await devCommand({ port: options.port ? Number(options.port) : void 0 });
|
|
11
11
|
});
|
|
12
12
|
cli.command("build", "Build for production").option("--target <target>", "Deploy target (node, vercel, cloudflare)").action(async (options) => {
|
|
13
13
|
try {
|
|
14
|
-
const { buildCommand } = await import("../build-
|
|
14
|
+
const { buildCommand } = await import("../build-HIGHJQQV.js");
|
|
15
15
|
await buildCommand({ target: options.target });
|
|
16
16
|
} catch (err) {
|
|
17
17
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -23,7 +23,7 @@ cli.command("build", "Build for production").option("--target <target>", "Deploy
|
|
|
23
23
|
});
|
|
24
24
|
cli.command("start", "Start production server").option("--port <port>", "Port number").action(async (options) => {
|
|
25
25
|
try {
|
|
26
|
-
const { startCommand } = await import("../start-
|
|
26
|
+
const { startCommand } = await import("../start-HX3QUSOS.js");
|
|
27
27
|
await startCommand({ port: options.port ? Number(options.port) : void 0 });
|
|
28
28
|
} catch (err) {
|
|
29
29
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -47,7 +47,7 @@ cli.command("generate <type> <name>", "Generate a route, action, page, or ws end
|
|
|
47
47
|
});
|
|
48
48
|
cli.command("routes", "List all routes, actions, and WebSocket endpoints").action(async () => {
|
|
49
49
|
try {
|
|
50
|
-
const { routesCommand } = await import("../routes-
|
|
50
|
+
const { routesCommand } = await import("../routes-H4SPLFHJ.js");
|
|
51
51
|
await routesCommand();
|
|
52
52
|
} catch (err) {
|
|
53
53
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -96,7 +96,7 @@ cli.command(
|
|
|
96
96
|
"Generate <distDir>/openapi.json from route schemas (opt-in via config.openapi)"
|
|
97
97
|
).option("--dry-run", "Print the document to stdout without writing to disk (EC-3)").action(async (options) => {
|
|
98
98
|
try {
|
|
99
|
-
const { openapiCommand } = await import("../openapi-
|
|
99
|
+
const { openapiCommand } = await import("../openapi-FNVSWZOL.js");
|
|
100
100
|
await openapiCommand({ dryRun: Boolean(options.dryRun) });
|
|
101
101
|
} catch (err) {
|
|
102
102
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -106,6 +106,30 @@ cli.command(
|
|
|
106
106
|
process.exit(1);
|
|
107
107
|
}
|
|
108
108
|
});
|
|
109
|
+
cli.command(
|
|
110
|
+
"migrate <kind>",
|
|
111
|
+
"Run a one-shot convention migration. Supported: router (G6 dotted\u2192nested)"
|
|
112
|
+
).option("--dry-run", "Print the migration plan but do not touch the filesystem").option("--force", "Skip the dev-server port pre-flight check (CI / non-TTY)").action(async (kind, options) => {
|
|
113
|
+
try {
|
|
114
|
+
if (kind !== "router") {
|
|
115
|
+
console.error(`
|
|
116
|
+
\u2717 Unknown migration kind: ${kind}. Supported: router
|
|
117
|
+
`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
const { routerMigrateCommand } = await import("../router-RGZFX75G.js");
|
|
121
|
+
await routerMigrateCommand({
|
|
122
|
+
dryRun: Boolean(options.dryRun),
|
|
123
|
+
force: Boolean(options.force)
|
|
124
|
+
});
|
|
125
|
+
} catch (err) {
|
|
126
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
127
|
+
console.error(`
|
|
128
|
+
\u2717 ${msg}
|
|
129
|
+
`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
109
133
|
cli.command("docker", "Generate Dockerfile for production").option("--force", "Overwrite existing Dockerfile").action(async (options) => {
|
|
110
134
|
try {
|
|
111
135
|
const { dockerCommand } = await import("../docker-EZDA5FT7.js");
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["import cac from 'cac'\n\ninterface CliOptions {\n port?: string | number\n target?: string\n upgradeReadiness?: string | number\n json?: boolean\n allowWarnings?: boolean\n force?: boolean\n dryRun?: boolean\n}\n\nconst cli = cac('theokit')\n\ncli\n .command('dev', 'Start development server')\n .option('--port <port>', 'Port number')\n .action(async (options: CliOptions) => {\n const { devCommand } = await import('./commands/dev.js')\n await devCommand({ port: options.port ? Number(options.port) : undefined })\n })\n\ncli\n .command('build', 'Build for production')\n .option('--target <target>', 'Deploy target (node, vercel, cloudflare)')\n .action(async (options: CliOptions) => {\n try {\n const { buildCommand } = await import('./commands/build.js')\n await buildCommand({ target: options.target })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(`\\n ✗ ${msg}\\n`)\n process.exit(1)\n }\n })\n\ncli\n .command('start', 'Start production server')\n .option('--port <port>', 'Port number')\n .action(async (options: CliOptions) => {\n try {\n const { startCommand } = await import('./commands/start.js')\n await startCommand({ port: options.port ? Number(options.port) : undefined })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(`\\n ✗ ${msg}\\n`)\n process.exit(1)\n }\n })\n\ncli\n .command('generate <type> <name>', 'Generate a route, action, page, or ws endpoint')\n .action(async (type: string, name: string) => {\n try {\n const { generateCommand } = await import('./commands/generate.js')\n await generateCommand(type, name)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(`\\n ✗ ${msg}\\n`)\n process.exit(1)\n }\n })\n\ncli.command('routes', 'List all routes, actions, and WebSocket endpoints').action(async () => {\n try {\n const { routesCommand } = await import('./commands/routes.js')\n await routesCommand()\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(`\\n ✗ ${msg}\\n`)\n process.exit(1)\n }\n})\n\ncli\n .command('check', 'Run typecheck + scan + (optional) eslint')\n .option(\n '--upgrade-readiness <version>',\n 'Scan source for anticipated breakage under a future TheoKit version (currently supports 0.3). See docs/migration/0.2-to-0.3.md',\n )\n .option('--json', 'Emit machine-readable JSON (only with --upgrade-readiness)')\n .option(\n '--allow-warnings',\n 'Do not fail the build when violations are present (only with --upgrade-readiness)',\n )\n .action(async (options: CliOptions) => {\n // cac may coerce `0.3` to a JS number — normalize before comparing.\n const targetRaw = options.upgradeReadiness\n if (targetRaw !== undefined) {\n const target = String(targetRaw)\n if (target !== '0.3') {\n console.error(`\\n ✗ --upgrade-readiness: only '0.3' is supported (got '${target}')\\n`)\n process.exit(1)\n }\n const { upgradeReadinessCommand } = await import('./commands/upgrade-readiness.js')\n await upgradeReadinessCommand({\n json: Boolean(options.json),\n allowWarnings: Boolean(options.allowWarnings),\n })\n return\n }\n const { checkCommand } = await import('./commands/check.js')\n await checkCommand()\n })\n\ncli\n .command('add <package>', 'Install a known TheoKit adapter or plugin (whitelist-only)')\n .action(async (pkg: string) => {\n const { addCommand } = await import('./commands/add.js')\n await addCommand(pkg)\n })\n\ncli.command('info', 'Print environment info (runtime, config, routes)').action(async () => {\n const { infoCommand } = await import('./commands/info.js')\n await infoCommand()\n})\n\ncli\n .command(\n 'openapi',\n 'Generate <distDir>/openapi.json from route schemas (opt-in via config.openapi)',\n )\n .option('--dry-run', 'Print the document to stdout without writing to disk (EC-3)')\n .action(async (options: CliOptions) => {\n try {\n const { openapiCommand } = await import('./commands/openapi.js')\n await openapiCommand({ dryRun: Boolean(options.dryRun) })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(`\\n ✗ ${msg}\\n`)\n process.exit(1)\n }\n })\n\ncli\n .command('docker', 'Generate Dockerfile for production')\n .option('--force', 'Overwrite existing Dockerfile')\n .action(async (options: CliOptions) => {\n try {\n const { dockerCommand } = await import('./commands/docker.js')\n await dockerCommand({ force: options.force })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(`\\n ✗ ${msg}\\n`)\n process.exit(1)\n }\n })\n\ncli.help()\ncli.version('0.1.0-alpha.0')\n\nexport function main(): void {\n cli.parse()\n}\n\n// Auto-execute when run as script\nmain()\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["import cac from 'cac'\n\ninterface CliOptions {\n port?: string | number\n target?: string\n upgradeReadiness?: string | number\n json?: boolean\n allowWarnings?: boolean\n force?: boolean\n dryRun?: boolean\n}\n\nconst cli = cac('theokit')\n\ncli\n .command('dev', 'Start development server')\n .option('--port <port>', 'Port number')\n .action(async (options: CliOptions) => {\n const { devCommand } = await import('./commands/dev.js')\n await devCommand({ port: options.port ? Number(options.port) : undefined })\n })\n\ncli\n .command('build', 'Build for production')\n .option('--target <target>', 'Deploy target (node, vercel, cloudflare)')\n .action(async (options: CliOptions) => {\n try {\n const { buildCommand } = await import('./commands/build.js')\n await buildCommand({ target: options.target })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(`\\n ✗ ${msg}\\n`)\n process.exit(1)\n }\n })\n\ncli\n .command('start', 'Start production server')\n .option('--port <port>', 'Port number')\n .action(async (options: CliOptions) => {\n try {\n const { startCommand } = await import('./commands/start.js')\n await startCommand({ port: options.port ? Number(options.port) : undefined })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(`\\n ✗ ${msg}\\n`)\n process.exit(1)\n }\n })\n\ncli\n .command('generate <type> <name>', 'Generate a route, action, page, or ws endpoint')\n .action(async (type: string, name: string) => {\n try {\n const { generateCommand } = await import('./commands/generate.js')\n await generateCommand(type, name)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(`\\n ✗ ${msg}\\n`)\n process.exit(1)\n }\n })\n\ncli.command('routes', 'List all routes, actions, and WebSocket endpoints').action(async () => {\n try {\n const { routesCommand } = await import('./commands/routes.js')\n await routesCommand()\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(`\\n ✗ ${msg}\\n`)\n process.exit(1)\n }\n})\n\ncli\n .command('check', 'Run typecheck + scan + (optional) eslint')\n .option(\n '--upgrade-readiness <version>',\n 'Scan source for anticipated breakage under a future TheoKit version (currently supports 0.3). See docs/migration/0.2-to-0.3.md',\n )\n .option('--json', 'Emit machine-readable JSON (only with --upgrade-readiness)')\n .option(\n '--allow-warnings',\n 'Do not fail the build when violations are present (only with --upgrade-readiness)',\n )\n .action(async (options: CliOptions) => {\n // cac may coerce `0.3` to a JS number — normalize before comparing.\n const targetRaw = options.upgradeReadiness\n if (targetRaw !== undefined) {\n const target = String(targetRaw)\n if (target !== '0.3') {\n console.error(`\\n ✗ --upgrade-readiness: only '0.3' is supported (got '${target}')\\n`)\n process.exit(1)\n }\n const { upgradeReadinessCommand } = await import('./commands/upgrade-readiness.js')\n await upgradeReadinessCommand({\n json: Boolean(options.json),\n allowWarnings: Boolean(options.allowWarnings),\n })\n return\n }\n const { checkCommand } = await import('./commands/check.js')\n await checkCommand()\n })\n\ncli\n .command('add <package>', 'Install a known TheoKit adapter or plugin (whitelist-only)')\n .action(async (pkg: string) => {\n const { addCommand } = await import('./commands/add.js')\n await addCommand(pkg)\n })\n\ncli.command('info', 'Print environment info (runtime, config, routes)').action(async () => {\n const { infoCommand } = await import('./commands/info.js')\n await infoCommand()\n})\n\ncli\n .command(\n 'openapi',\n 'Generate <distDir>/openapi.json from route schemas (opt-in via config.openapi)',\n )\n .option('--dry-run', 'Print the document to stdout without writing to disk (EC-3)')\n .action(async (options: CliOptions) => {\n try {\n const { openapiCommand } = await import('./commands/openapi.js')\n await openapiCommand({ dryRun: Boolean(options.dryRun) })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(`\\n ✗ ${msg}\\n`)\n process.exit(1)\n }\n })\n\ncli\n .command(\n 'migrate <kind>',\n 'Run a one-shot convention migration. Supported: router (G6 dotted→nested)',\n )\n .option('--dry-run', 'Print the migration plan but do not touch the filesystem')\n .option('--force', 'Skip the dev-server port pre-flight check (CI / non-TTY)')\n .action(async (kind: string, options: CliOptions) => {\n try {\n if (kind !== 'router') {\n console.error(`\\n ✗ Unknown migration kind: ${kind}. Supported: router\\n`)\n process.exit(1)\n }\n const { routerMigrateCommand } = await import('./commands/migrate/router.js')\n await routerMigrateCommand({\n dryRun: Boolean(options.dryRun),\n force: Boolean(options.force),\n })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(`\\n ✗ ${msg}\\n`)\n process.exit(1)\n }\n })\n\ncli\n .command('docker', 'Generate Dockerfile for production')\n .option('--force', 'Overwrite existing Dockerfile')\n .action(async (options: CliOptions) => {\n try {\n const { dockerCommand } = await import('./commands/docker.js')\n await dockerCommand({ force: options.force })\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(`\\n ✗ ${msg}\\n`)\n process.exit(1)\n }\n })\n\ncli.help()\ncli.version('0.1.0-alpha.0')\n\nexport function main(): void {\n cli.parse()\n}\n\n// Auto-execute when run as script\nmain()\n"],"mappings":"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","names":[]}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "tsx/esm";
|
|
3
|
-
import {
|
|
4
|
-
preflightNodeAndBindings
|
|
5
|
-
} from "./chunk-F3TG5FJV.js";
|
|
6
3
|
import {
|
|
7
4
|
validateProjectStructure
|
|
8
5
|
} from "./chunk-XMS5VRIK.js";
|
|
6
|
+
import {
|
|
7
|
+
preflightNodeAndBindings
|
|
8
|
+
} from "./chunk-F3TG5FJV.js";
|
|
9
9
|
import {
|
|
10
10
|
theoPluginAsync
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-JAAHOGKI.js";
|
|
12
12
|
import "./chunk-TPCT3MXV.js";
|
|
13
13
|
import {
|
|
14
14
|
loadConfig,
|
|
@@ -20,7 +20,7 @@ import "./chunk-O7335ZGH.js";
|
|
|
20
20
|
import {
|
|
21
21
|
orchestrateDev
|
|
22
22
|
} from "./chunk-WZ7CPVQB.js";
|
|
23
|
-
import "./chunk-
|
|
23
|
+
import "./chunk-OXIQI2XZ.js";
|
|
24
24
|
import "./chunk-5OFPQK6N.js";
|
|
25
25
|
import "./chunk-KT3MWNZG.js";
|
|
26
26
|
|
|
@@ -89,4 +89,4 @@ export {
|
|
|
89
89
|
devCommand,
|
|
90
90
|
startDevServer
|
|
91
91
|
};
|
|
92
|
-
//# sourceMappingURL=dev-
|
|
92
|
+
//# sourceMappingURL=dev-266MVCVA.js.map
|
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
} from "./chunk-ZOXNJV2O.js";
|
|
7
7
|
import {
|
|
8
8
|
generateManifest
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-TRH2L3FI.js";
|
|
10
|
+
import "./chunk-OXIQI2XZ.js";
|
|
11
11
|
import "./chunk-5OFPQK6N.js";
|
|
12
12
|
import "./chunk-KT3MWNZG.js";
|
|
13
13
|
|
|
@@ -41,4 +41,4 @@ export {
|
|
|
41
41
|
_resetInFlightForTests,
|
|
42
42
|
reEmitOpenApi
|
|
43
43
|
};
|
|
44
|
-
//# sourceMappingURL=dev-emit-
|
|
44
|
+
//# sourceMappingURL=dev-emit-NO6OZJOQ.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
generateManifest
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-2VQKDRVF.js";
|
|
4
|
+
import "./chunk-B6RP57M3.js";
|
|
5
5
|
import "./chunk-7TOMTMHT.js";
|
|
6
6
|
import "./chunk-X2VVCJ4V.js";
|
|
7
7
|
import "./chunk-WSJKACWB.js";
|
|
@@ -387,4 +387,4 @@ export {
|
|
|
387
387
|
_resetInFlightForTests,
|
|
388
388
|
reEmitOpenApi
|
|
389
389
|
};
|
|
390
|
-
//# sourceMappingURL=dev-emit-
|
|
390
|
+
//# sourceMappingURL=dev-emit-RBSFZZNO.js.map
|
|
@@ -17085,4 +17085,4 @@ export {
|
|
|
17085
17085
|
preflightCheck,
|
|
17086
17086
|
toShareGptTrajectory
|
|
17087
17087
|
};
|
|
17088
|
-
//# sourceMappingURL=dist-
|
|
17088
|
+
//# sourceMappingURL=dist-5V77Z223.js.map
|
package/dist/index.js
CHANGED
|
@@ -8,11 +8,11 @@ import {
|
|
|
8
8
|
scanRoutes,
|
|
9
9
|
theoConfigSchema,
|
|
10
10
|
theoPlugin
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-BIGBFZSU.js";
|
|
12
12
|
import "./chunk-7BIM27LV.js";
|
|
13
13
|
import "./chunk-O4BLVPML.js";
|
|
14
14
|
import "./chunk-CZM6WWWS.js";
|
|
15
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-B6RP57M3.js";
|
|
16
16
|
import "./chunk-7TOMTMHT.js";
|
|
17
17
|
import "./chunk-PB4GR7EP.js";
|
|
18
18
|
import "./chunk-GY5Q27BJ.js";
|
|
@@ -11,8 +11,8 @@ import "./chunk-X4JAX2U3.js";
|
|
|
11
11
|
import "./chunk-WZ7CPVQB.js";
|
|
12
12
|
import {
|
|
13
13
|
generateManifest
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-TRH2L3FI.js";
|
|
15
|
+
import "./chunk-OXIQI2XZ.js";
|
|
16
16
|
import "./chunk-5OFPQK6N.js";
|
|
17
17
|
import "./chunk-KT3MWNZG.js";
|
|
18
18
|
|
|
@@ -81,4 +81,4 @@ function emitOpenApiInMemory(manifest, config) {
|
|
|
81
81
|
export {
|
|
82
82
|
openapiCommand
|
|
83
83
|
};
|
|
84
|
-
//# sourceMappingURL=openapi-
|
|
84
|
+
//# sourceMappingURL=openapi-FNVSWZOL.js.map
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "tsx/esm";
|
|
3
|
+
import "./chunk-KT3MWNZG.js";
|
|
4
|
+
|
|
5
|
+
// src/cli/commands/migrate/router.ts
|
|
6
|
+
import { execFileSync } from "child_process";
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
8
|
+
import { createServer } from "net";
|
|
9
|
+
import { dirname, resolve } from "path";
|
|
10
|
+
|
|
11
|
+
// src/cli/commands/migrate/router-codemod.ts
|
|
12
|
+
import { readdirSync, statSync } from "fs";
|
|
13
|
+
import { extname, join, relative, sep } from "path";
|
|
14
|
+
var ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
15
|
+
var TEST_OR_SPEC_RE = /\.(test|spec)\.[jt]sx?$/;
|
|
16
|
+
var RouterMigrationCollisionError = class extends Error {
|
|
17
|
+
name = "RouterMigrationCollisionError";
|
|
18
|
+
from;
|
|
19
|
+
to;
|
|
20
|
+
constructor(from, to) {
|
|
21
|
+
super(
|
|
22
|
+
`Router migration collision: ${from} would rename to ${to}, but that file already exists (case-insensitive match). Resolve manually.`
|
|
23
|
+
);
|
|
24
|
+
this.from = from;
|
|
25
|
+
this.to = to;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function computeDepthDelta(from, to, routesDir) {
|
|
29
|
+
const depthOf = (p) => {
|
|
30
|
+
const rel = relative(routesDir, p).replace(/\\/g, "/");
|
|
31
|
+
return rel.split("/").length - 1;
|
|
32
|
+
};
|
|
33
|
+
return depthOf(to) - depthOf(from);
|
|
34
|
+
}
|
|
35
|
+
function rewriteRelativeImports(source, delta) {
|
|
36
|
+
if (delta <= 0) return source;
|
|
37
|
+
const prefix = "../".repeat(delta);
|
|
38
|
+
const transformSpecifier = (specifier) => {
|
|
39
|
+
if (specifier.startsWith("./")) return `${prefix}${specifier.slice(2)}`;
|
|
40
|
+
return `${prefix}${specifier}`;
|
|
41
|
+
};
|
|
42
|
+
const fromRe = /\bfrom\s+(['"])(\.{1,2}\/[^'"]+)\1/g;
|
|
43
|
+
const dynRe = /\bimport\s*\(\s*(['"])(\.{1,2}\/[^'"]+)\1\s*\)/g;
|
|
44
|
+
const sideRe = /\bimport\s+(['"])(\.{1,2}\/[^'"]+)\1/g;
|
|
45
|
+
const fromSub = (_m, q, spec) => `from ${q}${transformSpecifier(spec)}${q}`;
|
|
46
|
+
const dynSub = (_m, q, spec) => `import(${q}${transformSpecifier(spec)}${q})`;
|
|
47
|
+
const sideSub = (_m, q, spec) => `import ${q}${transformSpecifier(spec)}${q}`;
|
|
48
|
+
return source.replace(fromRe, fromSub).replace(dynRe, dynSub).replace(sideRe, sideSub);
|
|
49
|
+
}
|
|
50
|
+
function isTestOrSpecFile(filePath) {
|
|
51
|
+
return TEST_OR_SPEC_RE.test(filePath);
|
|
52
|
+
}
|
|
53
|
+
function hasDotOutsideBrackets(segment) {
|
|
54
|
+
let depth = 0;
|
|
55
|
+
for (const ch of segment) {
|
|
56
|
+
if (ch === "[") depth++;
|
|
57
|
+
else if (ch === "]") depth--;
|
|
58
|
+
else if (ch === "." && depth === 0) return true;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
function splitDottedSegmentOutsideBrackets(segment) {
|
|
63
|
+
const parts = [];
|
|
64
|
+
let current = "";
|
|
65
|
+
let depth = 0;
|
|
66
|
+
for (const ch of segment) {
|
|
67
|
+
if (ch === "[") {
|
|
68
|
+
depth++;
|
|
69
|
+
current += ch;
|
|
70
|
+
} else if (ch === "]") {
|
|
71
|
+
depth--;
|
|
72
|
+
current += ch;
|
|
73
|
+
} else if (ch === "." && depth === 0) {
|
|
74
|
+
if (current) parts.push(current);
|
|
75
|
+
current = "";
|
|
76
|
+
} else {
|
|
77
|
+
current += ch;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (current) parts.push(current);
|
|
81
|
+
return parts;
|
|
82
|
+
}
|
|
83
|
+
function toDirectoryNestedPath(filePath, routesDir) {
|
|
84
|
+
const rel = relative(routesDir, filePath).replace(/\\/g, "/");
|
|
85
|
+
const ext = extname(rel);
|
|
86
|
+
const withoutExt = rel.slice(0, -ext.length);
|
|
87
|
+
const flattened = withoutExt.split("/").flatMap(splitDottedSegmentOutsideBrackets);
|
|
88
|
+
return join(routesDir, `${flattened.join(sep)}${ext}`);
|
|
89
|
+
}
|
|
90
|
+
function walkRoutes(root, onFile) {
|
|
91
|
+
let entries;
|
|
92
|
+
try {
|
|
93
|
+
entries = readdirSync(root, { withFileTypes: true });
|
|
94
|
+
} catch {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
for (const entry of entries) {
|
|
98
|
+
if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
|
|
99
|
+
const full = join(root, entry.name);
|
|
100
|
+
if (entry.isDirectory()) {
|
|
101
|
+
walkRoutes(full, onFile);
|
|
102
|
+
} else if (entry.isFile() && ROUTE_EXTENSIONS.has(extname(entry.name))) {
|
|
103
|
+
onFile(full);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function isDottedRouteFile(filePath, routesDir) {
|
|
108
|
+
const rel = relative(routesDir, filePath).replace(/\\/g, "/");
|
|
109
|
+
const ext = extname(rel);
|
|
110
|
+
const withoutExt = rel.slice(0, -ext.length);
|
|
111
|
+
return withoutExt.split("/").some(hasDotOutsideBrackets);
|
|
112
|
+
}
|
|
113
|
+
function planRouterMigration(routesDir) {
|
|
114
|
+
const plan = [];
|
|
115
|
+
const existingPathsLowercase = /* @__PURE__ */ new Set();
|
|
116
|
+
walkRoutes(routesDir, (absPath) => {
|
|
117
|
+
existingPathsLowercase.add(absPath.toLowerCase());
|
|
118
|
+
});
|
|
119
|
+
walkRoutes(routesDir, (absPath) => {
|
|
120
|
+
if (isTestOrSpecFile(absPath)) return;
|
|
121
|
+
if (!isDottedRouteFile(absPath, routesDir)) return;
|
|
122
|
+
const to = toDirectoryNestedPath(absPath, routesDir);
|
|
123
|
+
const targetLower = to.toLowerCase();
|
|
124
|
+
if (existingPathsLowercase.has(targetLower) && targetLower !== absPath.toLowerCase()) {
|
|
125
|
+
throw new RouterMigrationCollisionError(absPath, to);
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
if (statSync(to).isFile()) {
|
|
129
|
+
throw new RouterMigrationCollisionError(absPath, to);
|
|
130
|
+
}
|
|
131
|
+
} catch (err) {
|
|
132
|
+
if (err instanceof RouterMigrationCollisionError) throw err;
|
|
133
|
+
}
|
|
134
|
+
plan.push({ from: absPath, to });
|
|
135
|
+
});
|
|
136
|
+
return plan;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/cli/commands/migrate/router.ts
|
|
140
|
+
var RouterMigrationPartialFailure = class extends Error {
|
|
141
|
+
name = "RouterMigrationPartialFailure";
|
|
142
|
+
filesAlreadyMigrated;
|
|
143
|
+
failedFile;
|
|
144
|
+
causeMessage;
|
|
145
|
+
constructor(filesAlreadyMigrated, failedFile, causeMessage) {
|
|
146
|
+
super(
|
|
147
|
+
`Router migration partial failure: ${filesAlreadyMigrated.length} files migrated successfully, then failed on ${failedFile}: ${causeMessage}. Re-run \`theokit migrate router\` to retry \u2014 already-migrated files will be skipped.`
|
|
148
|
+
);
|
|
149
|
+
this.filesAlreadyMigrated = filesAlreadyMigrated;
|
|
150
|
+
this.failedFile = failedFile;
|
|
151
|
+
this.causeMessage = causeMessage;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
async function isPortInUse(port) {
|
|
155
|
+
return await new Promise((resolve2) => {
|
|
156
|
+
const probe = createServer();
|
|
157
|
+
probe.once("error", (err) => {
|
|
158
|
+
probe.close(() => {
|
|
159
|
+
resolve2(err.code === "EADDRINUSE");
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
probe.once("listening", () => {
|
|
163
|
+
probe.close(() => {
|
|
164
|
+
resolve2(false);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
probe.listen(port, "127.0.0.1");
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
async function findRunningDevServerPort() {
|
|
171
|
+
for (const port of [3e3, 3100]) {
|
|
172
|
+
if (await isPortInUse(port)) return port;
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
function tryGitMv(from, to) {
|
|
177
|
+
try {
|
|
178
|
+
mkdirSync(dirname(to), { recursive: true });
|
|
179
|
+
execFileSync("git", ["mv", from, to], { stdio: "pipe" });
|
|
180
|
+
return true;
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function fallbackRename(from, to) {
|
|
186
|
+
mkdirSync(dirname(to), { recursive: true });
|
|
187
|
+
renameSync(from, to);
|
|
188
|
+
}
|
|
189
|
+
function applyRename(item) {
|
|
190
|
+
if (!existsSync(item.from)) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (existsSync(item.to)) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
`Refusing to overwrite existing file at ${item.to} during migration of ${item.from}`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
if (!tryGitMv(item.from, item.to)) {
|
|
199
|
+
fallbackRename(item.from, item.to);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function applyImportRewrite(item, routesDir) {
|
|
203
|
+
const delta = computeDepthDelta(item.from, item.to, routesDir);
|
|
204
|
+
if (delta <= 0) return;
|
|
205
|
+
const source = readFileSync(item.to, "utf8");
|
|
206
|
+
const rewritten = rewriteRelativeImports(source, delta);
|
|
207
|
+
if (rewritten !== source) {
|
|
208
|
+
writeFileSync(item.to, rewritten, "utf8");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function makeLog(silent) {
|
|
212
|
+
if (silent) {
|
|
213
|
+
return (_msg) => {
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return (msg) => {
|
|
217
|
+
console.log(msg);
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
async function assertNoRunningDevServer(opts) {
|
|
221
|
+
if (opts.force) return;
|
|
222
|
+
const runningPort = await findRunningDevServerPort();
|
|
223
|
+
if (runningPort !== null) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
`Detected theokit dev server running on port ${runningPort}. STOP it before running migration to avoid HMR cascade (see EC-2). Re-run with --force to override.`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function executeRenames(plan, routesDir, log) {
|
|
230
|
+
const migrated = [];
|
|
231
|
+
for (const item of plan) {
|
|
232
|
+
try {
|
|
233
|
+
applyRename(item);
|
|
234
|
+
applyImportRewrite(item, routesDir);
|
|
235
|
+
migrated.push(item);
|
|
236
|
+
log(` \u2713 ${item.from} \u2192 ${item.to}`);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
const causeMessage = err instanceof Error ? err.message : String(err);
|
|
239
|
+
throw new RouterMigrationPartialFailure(
|
|
240
|
+
migrated.map((m) => m.from),
|
|
241
|
+
item.from,
|
|
242
|
+
causeMessage
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return migrated;
|
|
247
|
+
}
|
|
248
|
+
async function routerMigrateCommand(opts = {}) {
|
|
249
|
+
const routesDir = opts.routesDir ?? resolve(process.cwd(), "server", "routes");
|
|
250
|
+
const log = makeLog(opts.silent);
|
|
251
|
+
await assertNoRunningDevServer(opts);
|
|
252
|
+
const plan = planRouterMigration(routesDir);
|
|
253
|
+
if (plan.length === 0) {
|
|
254
|
+
log(" \u2713 Router already uses directory-nested convention. Nothing to do.");
|
|
255
|
+
return { migrated: [], alreadyClean: true };
|
|
256
|
+
}
|
|
257
|
+
log(` \u2192 Migrating ${plan.length} dotted route(s) to directory-nested form...`);
|
|
258
|
+
if (opts.dryRun) {
|
|
259
|
+
for (const item of plan) {
|
|
260
|
+
log(` [dry-run] ${item.from} \u2192 ${item.to}`);
|
|
261
|
+
}
|
|
262
|
+
return { migrated: [], alreadyClean: false };
|
|
263
|
+
}
|
|
264
|
+
const migrated = executeRenames(plan, routesDir, log);
|
|
265
|
+
log(` \u2713 Migrated ${migrated.length} route(s).`);
|
|
266
|
+
return { migrated, alreadyClean: false };
|
|
267
|
+
}
|
|
268
|
+
export {
|
|
269
|
+
RouterMigrationPartialFailure,
|
|
270
|
+
findRunningDevServerPort,
|
|
271
|
+
isPortInUse,
|
|
272
|
+
routerMigrateCommand
|
|
273
|
+
};
|
|
274
|
+
//# sourceMappingURL=router-RGZFX75G.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/migrate/router.ts","../src/cli/commands/migrate/router-codemod.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time codemod: derives all FS paths from the user's `<routesDir>`\n * argument (defaults to `<cwd>/server/routes`). No HTTP input reaches\n * these fs calls. Operator runs this manually via `theokit migrate router`.\n */\n/**\n * G6 T2.1 — `theokit migrate router` CLI subcommand.\n *\n * Plan: .claude/knowledge-base/plans/g6-router-convention-plan.md v1.1\n *\n * Wraps the pure `planRouterMigration` with side effects:\n * - EC-2: pre-flight `isPortInUse(3000|3100)` warning to prevent the\n * dev-server HMR cascade described in the edge-case review.\n * - EC-7: partial failure produces a typed `RouterMigrationPartialFailure`\n * with `filesAlreadyMigrated` so a re-run can continue from where it\n * stopped (idempotent by virtue of `planRouterMigration`).\n * - Prefers `git mv $from $to` so the rename appears as a single\n * history entry; falls back to `mkdir -p $(dirname $to) && fs.rename`\n * when not in a git repo or git mv fails.\n */\nimport { execFileSync } from 'node:child_process'\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { createServer } from 'node:net'\nimport { dirname, resolve } from 'node:path'\n\nimport {\n computeDepthDelta,\n planRouterMigration,\n rewriteRelativeImports,\n type RouterMigrationPlanItem,\n} from './router-codemod.js'\n\nexport interface RouterMigrateOptions {\n /** Absolute path to `<project>/server/routes`. Defaults to `<cwd>/server/routes`. */\n routesDir?: string\n /**\n * Skip the EC-2 dev-server port pre-flight check (CI / non-TTY).\n * Default: false.\n */\n force?: boolean\n /** When true, prints the plan but performs no renames (dry-run). */\n dryRun?: boolean\n /** When true, suppresses stdout (used in tests). */\n silent?: boolean\n}\n\n/**\n * Thrown when the codemod completes with at least one successful rename\n * AND at least one failure — caller can re-run safely (idempotent) and\n * the second pass will skip already-migrated files.\n */\nexport class RouterMigrationPartialFailure extends Error {\n override readonly name = 'RouterMigrationPartialFailure'\n readonly filesAlreadyMigrated: string[]\n readonly failedFile: string\n readonly causeMessage: string\n constructor(filesAlreadyMigrated: string[], failedFile: string, causeMessage: string) {\n super(\n `Router migration partial failure: ${filesAlreadyMigrated.length} files migrated successfully, then failed on ${failedFile}: ${causeMessage}. Re-run \\`theokit migrate router\\` to retry — already-migrated files will be skipped.`,\n )\n this.filesAlreadyMigrated = filesAlreadyMigrated\n this.failedFile = failedFile\n this.causeMessage = causeMessage\n }\n}\n\n/**\n * EC-2: dev-server detection. Returns true if the port is in use (someone\n * is already listening on localhost:<port>). Uses Node's `node:net` —\n * zero new deps.\n */\nexport async function isPortInUse(port: number): Promise<boolean> {\n return await new Promise<boolean>((resolve) => {\n const probe = createServer()\n probe.once('error', (err: NodeJS.ErrnoException) => {\n probe.close(() => {\n resolve(err.code === 'EADDRINUSE')\n })\n })\n probe.once('listening', () => {\n probe.close(() => {\n resolve(false)\n })\n })\n probe.listen(port, '127.0.0.1')\n })\n}\n\n/**\n * EC-2 pre-flight: returns the first port in use among the typical\n * theokit dev ports (3000 + 3100) so the caller can warn the user.\n */\nexport async function findRunningDevServerPort(): Promise<number | null> {\n for (const port of [3000, 3100]) {\n if (await isPortInUse(port)) return port\n }\n return null\n}\n\nfunction tryGitMv(from: string, to: string): boolean {\n try {\n mkdirSync(dirname(to), { recursive: true })\n // Arguments are absolute paths from a recursive walk of <routesDir>.\n // Pass as argv to avoid shell parsing entirely (no `shell: true`).\n // `git` is resolved via the operator's PATH (same security model as\n // every other CLI tool — `theokit dev`, `pnpm install`, etc.).\n // eslint-disable-next-line sonarjs/no-os-command-from-path\n execFileSync('git', ['mv', from, to], { stdio: 'pipe' })\n return true\n } catch {\n return false\n }\n}\n\nfunction fallbackRename(from: string, to: string): void {\n mkdirSync(dirname(to), { recursive: true })\n renameSync(from, to)\n}\n\nfunction applyRename(item: RouterMigrationPlanItem): void {\n if (!existsSync(item.from)) {\n // Idempotency: already migrated — skip silently\n return\n }\n if (existsSync(item.to)) {\n // The plan() phase would have thrown CollisionError if we knew about\n // it at plan time. This branch covers a race condition where the\n // target was created between plan and apply.\n throw new Error(\n `Refusing to overwrite existing file at ${item.to} during migration of ${item.from}`,\n )\n }\n if (!tryGitMv(item.from, item.to)) {\n fallbackRename(item.from, item.to)\n }\n}\n\n/**\n * Adjust relative imports inside the renamed file to compensate for the\n * extra nesting depth. Without this, `import { foo } from './chat'` inside\n * a file moved from `routes/admin.sdk-config.ts` → `routes/admin/sdk-config.ts`\n * would fail to resolve at typecheck/runtime — the import needs `../chat`\n * now that the file lives one level deeper.\n */\nfunction applyImportRewrite(item: RouterMigrationPlanItem, routesDir: string): void {\n const delta = computeDepthDelta(item.from, item.to, routesDir)\n if (delta <= 0) return\n const source = readFileSync(item.to, 'utf8')\n const rewritten = rewriteRelativeImports(source, delta)\n if (rewritten !== source) {\n writeFileSync(item.to, rewritten, 'utf8')\n }\n}\n\nexport interface RouterMigrateResult {\n /** Successfully renamed (from, to) pairs. */\n migrated: RouterMigrationPlanItem[]\n /** Plan was empty — nothing to do (already-nested). */\n alreadyClean: boolean\n}\n\ntype LogFn = (msg: string) => void\n\nfunction makeLog(silent: boolean | undefined): LogFn {\n if (silent) {\n return (_msg: string) => {\n // intentionally silent\n }\n }\n return (msg: string) => {\n console.log(msg)\n }\n}\n\nasync function assertNoRunningDevServer(opts: RouterMigrateOptions): Promise<void> {\n if (opts.force) return\n const runningPort = await findRunningDevServerPort()\n if (runningPort !== null) {\n throw new Error(\n `Detected theokit dev server running on port ${runningPort}. STOP it before running migration to avoid HMR cascade (see EC-2). Re-run with --force to override.`,\n )\n }\n}\n\nfunction executeRenames(\n plan: readonly RouterMigrationPlanItem[],\n routesDir: string,\n log: LogFn,\n): RouterMigrationPlanItem[] {\n const migrated: RouterMigrationPlanItem[] = []\n for (const item of plan) {\n try {\n applyRename(item)\n applyImportRewrite(item, routesDir)\n migrated.push(item)\n log(` ✓ ${item.from} → ${item.to}`)\n } catch (err) {\n // EC-7: partial failure observability — emit structured error with\n // the list of already-migrated files so a re-run can recover.\n const causeMessage = err instanceof Error ? err.message : String(err)\n throw new RouterMigrationPartialFailure(\n migrated.map((m) => m.from),\n item.from,\n causeMessage,\n )\n }\n }\n return migrated\n}\n\nexport async function routerMigrateCommand(\n opts: RouterMigrateOptions = {},\n): Promise<RouterMigrateResult> {\n const routesDir = opts.routesDir ?? resolve(process.cwd(), 'server', 'routes')\n const log = makeLog(opts.silent)\n\n await assertNoRunningDevServer(opts)\n\n const plan = planRouterMigration(routesDir)\n if (plan.length === 0) {\n log(' ✓ Router already uses directory-nested convention. Nothing to do.')\n return { migrated: [], alreadyClean: true }\n }\n\n log(` → Migrating ${plan.length} dotted route(s) to directory-nested form...`)\n\n if (opts.dryRun) {\n for (const item of plan) {\n log(` [dry-run] ${item.from} → ${item.to}`)\n }\n return { migrated: [], alreadyClean: false }\n }\n\n const migrated = executeRenames(plan, routesDir, log)\n log(` ✓ Migrated ${migrated.length} route(s).`)\n return { migrated, alreadyClean: false }\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time codemod: walks `<routesDir>` derived from the operator's\n * cwd via `theokit migrate router`. No HTTP input reaches these fs calls.\n */\n/**\n * G6 T2.1 — Pure codemod core for `theokit migrate router`.\n *\n * Plan: .claude/knowledge-base/plans/g6-router-convention-plan.md v1.1\n *\n * `planRouterMigration` walks `<routesDir>` recursively and returns the\n * set of (from, to) renames required to convert legacy dotted-basename\n * routes (`auth.[provider].login.ts`) into the directory-nested form\n * (`auth/[provider]/login.ts`). The function is PURE — it does NOT\n * touch the filesystem beyond `readdirSync`/`statSync`. The CLI subcommand\n * (`router.ts`) is responsible for executing the renames via `git mv`\n * or `fs.rename` with the appropriate error handling.\n *\n * Edge cases covered:\n * - EC-4: co-located `*.test.[jt]sx?` / `*.spec.[jt]sx?` files are\n * silently skipped (same filter as the scanner).\n * - EC-5: target collision detection is CASE-INSENSITIVE so a\n * macOS/Windows dev doesn't silently overwrite a sibling file that\n * differs only in case.\n * - Idempotent: invoked twice on the same tree, the second pass is empty.\n */\nimport { readdirSync, statSync } from 'node:fs'\nimport { extname, join, relative, sep } from 'node:path'\n\nconst ROUTE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\nconst TEST_OR_SPEC_RE = /\\.(test|spec)\\.[jt]sx?$/\n\n/**\n * One pending rename emitted by `planRouterMigration`.\n */\nexport interface RouterMigrationPlanItem {\n /** Absolute path of the offending dotted-basename file. */\n from: string\n /** Absolute path of the proposed directory-nested replacement. */\n to: string\n}\n\n/**\n * `RouterMigrationCollisionError` is thrown when migrating a dotted file\n * would overwrite an existing file (case-insensitive). The caller MUST\n * resolve the conflict manually before re-running the codemod.\n */\nexport class RouterMigrationCollisionError extends Error {\n override readonly name = 'RouterMigrationCollisionError'\n readonly from: string\n readonly to: string\n constructor(from: string, to: string) {\n super(\n `Router migration collision: ${from} would rename to ${to}, but that file already exists (case-insensitive match). Resolve manually.`,\n )\n this.from = from\n this.to = to\n }\n}\n\n/**\n * Compute the depth delta (number of extra `..` levels needed) between\n * the original (dotted) path and the migrated (nested) path, relative\n * to a common `routesDir` ancestor.\n *\n * - `admin.sdk-config.ts` (depth 0) → `admin/sdk-config.ts` (depth 1): delta = 1\n * - `debug.stability.last.ts` (depth 0) → `debug/stability/last.ts` (depth 2): delta = 2\n * - `canvas/artifacts.[id].ts` (depth 1) → `canvas/artifacts/[id].ts` (depth 2): delta = 1\n */\nexport function computeDepthDelta(from: string, to: string, routesDir: string): number {\n const depthOf = (p: string): number => {\n const rel = relative(routesDir, p).replace(/\\\\/g, '/')\n // depth = number of `/` separators in the relative path\n return rel.split('/').length - 1\n }\n return depthOf(to) - depthOf(from)\n}\n\n/**\n * Rewrite all relative imports in `source` by prepending `../` × `delta`\n * to every `./X` or `../X` specifier. Absolute / package specifiers\n * (`theokit/server`, `zod`, `node:fs`) are untouched.\n *\n * Handles three import shapes:\n * - `import ... from './foo'`\n * - `import('./foo')` (dynamic)\n * - `import './foo'` (side-effect only)\n * - `export ... from './foo'` (re-exports)\n *\n * Pure: returns the new source; does NOT touch disk.\n */\nexport function rewriteRelativeImports(source: string, delta: number): string {\n if (delta <= 0) return source\n\n const prefix = '../'.repeat(delta)\n\n // Rewrites a captured relative specifier: `./foo` → `<prefix>foo`,\n // `../foo` → `<prefix>../foo`. Single source of truth for the algorithm\n // shared by all three import-shape regexes below.\n const transformSpecifier = (specifier: string): string => {\n if (specifier.startsWith('./')) return `${prefix}${specifier.slice(2)}`\n return `${prefix}${specifier}`\n }\n\n // Three narrow regexes keep us out of catastrophic-backtracking territory.\n // Each is anchored on a keyword (`from`, `import`) and uses a possessive\n // character class (`[^'\"]+`) for the specifier, so backtracking is bounded.\n const fromRe = /\\bfrom\\s+(['\"])(\\.{1,2}\\/[^'\"]+)\\1/g\n const dynRe = /\\bimport\\s*\\(\\s*(['\"])(\\.{1,2}\\/[^'\"]+)\\1\\s*\\)/g\n const sideRe = /\\bimport\\s+(['\"])(\\.{1,2}\\/[^'\"]+)\\1/g\n\n const fromSub = (_m: string, q: string, spec: string): string =>\n `from ${q}${transformSpecifier(spec)}${q}`\n const dynSub = (_m: string, q: string, spec: string): string =>\n `import(${q}${transformSpecifier(spec)}${q})`\n const sideSub = (_m: string, q: string, spec: string): string =>\n `import ${q}${transformSpecifier(spec)}${q}`\n\n // `from` covers both `import ... from '...'` and `export ... from '...'`\n // (the `\\bfrom\\s+` anchor matches the keyword position in both shapes).\n // `dynRe` handles `import('...')`. `sideRe` handles bare `import '...'`.\n // No overlap — `sideRe` requires `import` followed by whitespace then\n // a quote (vs. `import(` for dynRe and `import ... from` for fromRe).\n return source.replace(fromRe, fromSub).replace(dynRe, dynSub).replace(sideRe, sideSub)\n}\n\nfunction isTestOrSpecFile(filePath: string): boolean {\n return TEST_OR_SPEC_RE.test(filePath)\n}\n\nfunction hasDotOutsideBrackets(segment: string): boolean {\n let depth = 0\n for (const ch of segment) {\n if (ch === '[') depth++\n else if (ch === ']') depth--\n else if (ch === '.' && depth === 0) return true\n }\n return false\n}\n\nfunction splitDottedSegmentOutsideBrackets(segment: string): string[] {\n const parts: string[] = []\n let current = ''\n let depth = 0\n for (const ch of segment) {\n if (ch === '[') {\n depth++\n current += ch\n } else if (ch === ']') {\n depth--\n current += ch\n } else if (ch === '.' && depth === 0) {\n if (current) parts.push(current)\n current = ''\n } else {\n current += ch\n }\n }\n if (current) parts.push(current)\n return parts\n}\n\nfunction toDirectoryNestedPath(filePath: string, routesDir: string): string {\n const rel = relative(routesDir, filePath).replace(/\\\\/g, '/')\n const ext = extname(rel)\n const withoutExt = rel.slice(0, -ext.length)\n const flattened = withoutExt.split('/').flatMap(splitDottedSegmentOutsideBrackets)\n return join(routesDir, `${flattened.join(sep)}${ext}`)\n}\n\nfunction walkRoutes(root: string, onFile: (absPath: string) => void): void {\n let entries\n try {\n entries = readdirSync(root, { withFileTypes: true })\n } catch {\n return\n }\n for (const entry of entries) {\n if (entry.name.startsWith('_') || entry.name.startsWith('.')) continue\n const full = join(root, entry.name)\n if (entry.isDirectory()) {\n walkRoutes(full, onFile)\n } else if (entry.isFile() && ROUTE_EXTENSIONS.has(extname(entry.name))) {\n onFile(full)\n }\n }\n}\n\nfunction isDottedRouteFile(filePath: string, routesDir: string): boolean {\n const rel = relative(routesDir, filePath).replace(/\\\\/g, '/')\n const ext = extname(rel)\n const withoutExt = rel.slice(0, -ext.length)\n return withoutExt.split('/').some(hasDotOutsideBrackets)\n}\n\n/**\n * Compute the migration plan for a `<server>/routes` tree.\n *\n * Pure: does NOT mutate the filesystem.\n *\n * @throws RouterMigrationCollisionError when a target collides with an\n * existing file (case-insensitive).\n */\nexport function planRouterMigration(routesDir: string): RouterMigrationPlanItem[] {\n const plan: RouterMigrationPlanItem[] = []\n const existingPathsLowercase = new Set<string>()\n\n walkRoutes(routesDir, (absPath) => {\n existingPathsLowercase.add(absPath.toLowerCase())\n })\n\n walkRoutes(routesDir, (absPath) => {\n if (isTestOrSpecFile(absPath)) return\n if (!isDottedRouteFile(absPath, routesDir)) return\n\n const to = toDirectoryNestedPath(absPath, routesDir)\n\n // EC-5: case-insensitive collision check — macOS HFS+/APFS default\n // and Windows NTFS treat case-only differences as the same path. If\n // any pre-existing file at a case-insensitive match exists, the codemod\n // would silently overwrite on those platforms; we bail explicitly.\n const targetLower = to.toLowerCase()\n if (existingPathsLowercase.has(targetLower) && targetLower !== absPath.toLowerCase()) {\n throw new RouterMigrationCollisionError(absPath, to)\n }\n // Also guard against statSync race using an explicit existence check.\n try {\n if (statSync(to).isFile()) {\n throw new RouterMigrationCollisionError(absPath, to)\n }\n } catch (err) {\n if (err instanceof RouterMigrationCollisionError) throw err\n // ENOENT — fine, target does not exist\n }\n\n plan.push({ from: absPath, to })\n })\n\n return plan\n}\n"],"mappings":"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","names":["resolve"]}
|
|
@@ -11,7 +11,7 @@ import "./chunk-WZ7CPVQB.js";
|
|
|
11
11
|
import {
|
|
12
12
|
scanServerRoutes,
|
|
13
13
|
scanWebSocketRoutes
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-OXIQI2XZ.js";
|
|
15
15
|
import {
|
|
16
16
|
scanServerActions
|
|
17
17
|
} from "./chunk-5OFPQK6N.js";
|
|
@@ -63,4 +63,4 @@ async function routesCommand() {
|
|
|
63
63
|
export {
|
|
64
64
|
routesCommand
|
|
65
65
|
};
|
|
66
|
-
//# sourceMappingURL=routes-
|
|
66
|
+
//# sourceMappingURL=routes-H4SPLFHJ.js.map
|