theokit 0.9.2 → 0.9.3
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/{actions-virtual-module-52C6RKDK.js → actions-virtual-module-WNA77RHT.js} +3 -4
- package/dist/{actions-virtual-module-52C6RKDK.js.map → actions-virtual-module-WNA77RHT.js.map} +1 -1
- package/dist/{add-2ZFFGGE4.js → add-W6FNTAFS.js} +1 -2
- package/dist/{add-2ZFFGGE4.js.map → add-W6FNTAFS.js.map} +1 -1
- package/dist/{app-typed-client-A4CP55NM.js → app-typed-client-XGCP563T.js} +3 -4
- package/dist/{app-typed-client-A4CP55NM.js.map → app-typed-client-XGCP563T.js.map} +1 -1
- package/dist/{aws-lambda-WT4HRBNL.js → aws-lambda-EC74AYN4.js} +1 -2
- package/dist/{aws-lambda-WT4HRBNL.js.map → aws-lambda-EC74AYN4.js.map} +1 -1
- package/dist/{broadcast-QOQGUNNK.js → broadcast-LUMOJIJT.js} +1 -2
- package/dist/{build-S73OUGYP.js → build-B7B5Q7TV.js} +6 -7
- package/dist/{build-S73OUGYP.js.map → build-B7B5Q7TV.js.map} +1 -1
- package/dist/{build-request-body-preview-II2235E6.js → build-request-body-preview-QMWD2IXK.js} +1 -2
- package/dist/{build-request-body-preview-II2235E6.js.map → build-request-body-preview-QMWD2IXK.js.map} +1 -1
- package/dist/{bun-UTDVN6R4.js → bun-FUYLUP62.js} +1 -2
- package/dist/{bun-UTDVN6R4.js.map → bun-FUYLUP62.js.map} +1 -1
- package/dist/{check-F3UDYSZ4.js → check-PD2FTKMM.js} +2 -3
- package/dist/{check-F3UDYSZ4.js.map → check-PD2FTKMM.js.map} +1 -1
- package/dist/{chunk-2DXLN7FF.js → chunk-AUNPGO2V.js} +8 -8
- package/dist/{chunk-Z374WCMZ.js → chunk-BLT7PPII.js} +5 -5
- package/dist/{chunk-256WFVQQ.js → chunk-HTNVOG34.js} +2 -2
- package/dist/cli/index.js +14 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/{cloudflare-U5GYYI47.js → cloudflare-KHF7JJZ7.js} +1 -2
- package/dist/{cloudflare-U5GYYI47.js.map → cloudflare-KHF7JJZ7.js.map} +1 -1
- package/dist/{configure-agent-registry-6YGTJYBC.js → configure-agent-registry-ZOBVU3MV.js} +1 -2
- package/dist/{configure-agent-registry-6YGTJYBC.js.map → configure-agent-registry-ZOBVU3MV.js.map} +1 -1
- package/dist/{db-ZPDW3UCU.js → db-3UNAMSFK.js} +1 -2
- package/dist/{db-ZPDW3UCU.js.map → db-3UNAMSFK.js.map} +1 -1
- package/dist/{deno-deploy-3QHJ3AJQ.js → deno-deploy-O4JP56BM.js} +1 -2
- package/dist/{deno-deploy-3QHJ3AJQ.js.map → deno-deploy-O4JP56BM.js.map} +1 -1
- package/dist/{dev-WLUBGVDM.js → dev-KHBWZUY3.js} +7 -8
- package/dist/{dev-WLUBGVDM.js.map → dev-KHBWZUY3.js.map} +1 -1
- package/dist/{dev-emit-OUPI7NAQ.js → dev-emit-FEFEDLZF.js} +1 -2
- package/dist/{dev-emit-OUPI7NAQ.js.map → dev-emit-FEFEDLZF.js.map} +1 -1
- package/dist/{dispatcher-EJME6C6M.js → dispatcher-EJHL6JMJ.js} +1 -2
- package/dist/{dispatcher-EJME6C6M.js.map → dispatcher-EJHL6JMJ.js.map} +1 -1
- package/dist/{docker-AZSJFLP2.js → docker-LZZB4D5E.js} +1 -2
- package/dist/{docker-AZSJFLP2.js.map → docker-LZZB4D5E.js.map} +1 -1
- package/dist/{generate-ODDAYXNR.js → generate-OMKHQ7OM.js} +1 -2
- package/dist/{generate-ODDAYXNR.js.map → generate-OMKHQ7OM.js.map} +1 -1
- package/dist/{info-CSGWIAXA.js → info-DWUBVHHF.js} +1 -2
- package/dist/{info-CSGWIAXA.js.map → info-DWUBVHHF.js.map} +1 -1
- package/dist/{netlify-DUB7BZ4E.js → netlify-E63264H6.js} +1 -2
- package/dist/{netlify-DUB7BZ4E.js.map → netlify-E63264H6.js.map} +1 -1
- package/dist/{node-ELE236WK.js → node-XVZAGVVO.js} +1 -2
- package/dist/{openapi-NXGRXABL.js → openapi-NPFP4WZY.js} +1 -2
- package/dist/{openapi-NXGRXABL.js.map → openapi-NPFP4WZY.js.map} +1 -1
- package/dist/registry-WHMRQG5F.js +24 -0
- package/dist/{registry-B7FA6KMI.js.map → registry-WHMRQG5F.js.map} +1 -1
- package/dist/{router-RGZFX75G.js → router-TLEAOFID.js} +1 -2
- package/dist/{router-RGZFX75G.js.map → router-TLEAOFID.js.map} +1 -1
- package/dist/{routes-6A5XFGF7.js → routes-GHMCCV7V.js} +1 -2
- package/dist/{routes-6A5XFGF7.js.map → routes-GHMCCV7V.js.map} +1 -1
- package/dist/{scan-I5PT6TUX.js → scan-7MJC6PYU.js} +1 -2
- package/dist/{schema-NPI4NJEF.js → schema-7K4SFSA3.js} +1 -2
- package/dist/{server-error-to-envelope-W7KJ6AXW.js → server-error-to-envelope-OHQCV66V.js} +1 -2
- package/dist/{server-routes-hmr-GZJGPWMS.js → server-routes-hmr-VZXOP5YT.js} +1 -2
- package/dist/{server-routes-hmr-GZJGPWMS.js.map → server-routes-hmr-VZXOP5YT.js.map} +1 -1
- package/dist/{services-json-6SHWMYJH.js → services-json-Y3XODNB5.js} +1 -2
- package/dist/{services-json-6SHWMYJH.js.map → services-json-Y3XODNB5.js.map} +1 -1
- package/dist/{services-typed-client-24VBMDOF.js → services-typed-client-6JPFJJ56.js} +1 -2
- package/dist/{services-typed-client-24VBMDOF.js.map → services-typed-client-6JPFJJ56.js.map} +1 -1
- package/dist/{start-ALXGQIVT.js → start-ETABX6ZK.js} +8 -9
- package/dist/{start-ALXGQIVT.js.map → start-ETABX6ZK.js.map} +1 -1
- package/dist/{static-YWF2M6YT.js → static-3JXPPDIH.js} +1 -2
- package/dist/{static-YWF2M6YT.js.map → static-3JXPPDIH.js.map} +1 -1
- package/dist/{storage-manager-D5KBXXKB.js → storage-manager-ZSQFLEYT.js} +1 -2
- package/dist/{storage-manager-D5KBXXKB.js.map → storage-manager-ZSQFLEYT.js.map} +1 -1
- package/dist/{theo-cloud-EUK56I7O.js → theo-cloud-I3KQE3JN.js} +1 -2
- package/dist/{theo-cloud-EUK56I7O.js.map → theo-cloud-I3KQE3JN.js.map} +1 -1
- package/dist/{upgrade-readiness-GUJBCJIS.js → upgrade-readiness-ACGW44JC.js} +1 -2
- package/dist/{upgrade-readiness-GUJBCJIS.js.map → upgrade-readiness-ACGW44JC.js.map} +1 -1
- package/dist/{vercel-LGDQWYZD.js → vercel-6VWQTIUL.js} +1 -2
- package/dist/{vercel-LGDQWYZD.js.map → vercel-6VWQTIUL.js.map} +1 -1
- package/dist/{vite-plugin-TEHQFA6D.js → vite-plugin-AYWW2YTE.js} +4 -5
- package/package.json +6 -2
- package/dist/chunk-KT3MWNZG.js +0 -18
- package/dist/dist-IW7G5Y23.js +0 -19454
- package/dist/dist-IW7G5Y23.js.map +0 -1
- package/dist/lib-JBI3XXH3.js +0 -781
- package/dist/lib-JBI3XXH3.js.map +0 -1
- package/dist/proper-lockfile-MVKMQGFK.js +0 -1592
- package/dist/proper-lockfile-MVKMQGFK.js.map +0 -1
- package/dist/registry-B7FA6KMI.js +0 -25
- package/dist/sqlite-vec-54KB4RD3.js +0 -41
- package/dist/sqlite-vec-54KB4RD3.js.map +0 -1
- package/dist/vite-plugin-TEHQFA6D.js.map +0 -1
- /package/dist/{broadcast-QOQGUNNK.js.map → broadcast-LUMOJIJT.js.map} +0 -0
- /package/dist/{chunk-2DXLN7FF.js.map → chunk-AUNPGO2V.js.map} +0 -0
- /package/dist/{chunk-Z374WCMZ.js.map → chunk-BLT7PPII.js.map} +0 -0
- /package/dist/{chunk-256WFVQQ.js.map → chunk-HTNVOG34.js.map} +0 -0
- /package/dist/{chunk-KT3MWNZG.js.map → node-XVZAGVVO.js.map} +0 -0
- /package/dist/{node-ELE236WK.js.map → scan-7MJC6PYU.js.map} +0 -0
- /package/dist/{scan-I5PT6TUX.js.map → schema-7K4SFSA3.js.map} +0 -0
- /package/dist/{schema-NPI4NJEF.js.map → server-error-to-envelope-OHQCV66V.js.map} +0 -0
- /package/dist/{server-error-to-envelope-W7KJ6AXW.js.map → vite-plugin-AYWW2YTE.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/commands/generate.ts","../src/cli/commands/generate-resource.ts","../src/cli/commands/generate-types.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\n\nimport { generateResource } from './generate-resource.js'\nimport {\n VALID_TYPES,\n type GeneratorType,\n type GenerateOptions,\n type GenerateStatus,\n type GenerateResult,\n} from './generate-types.js'\n\nexport {\n VALID_TYPES,\n type GeneratorType,\n type GenerateOptions,\n type GenerateStatus,\n type GenerateResult,\n}\n\nfunction toKebabCase(name: string): boolean {\n return /^[a-z][a-z0-9/-]*$/.test(name)\n}\n\n/**\n * Reserved JS identifier names that conflict with object prototype machinery\n * or would shadow built-in module shapes if used as action/route names.\n * Mirrors action-scan's RESERVED_NAMES (T1.4).\n */\nconst RESERVED_BASENAMES = new Set([\n 'index',\n 'constructor',\n '__proto__',\n 'prototype',\n 'hasOwnProperty',\n])\n\n/**\n * Validate that a name segment doesn't hit the reserved-identifier list.\n * Checks the BASENAME (last `/` segment) — nested `admin/constructor` still\n * rejected because the file `constructor.ts` would be the conflict.\n */\nfunction hasReservedSegment(name: string): boolean {\n const basename = name.includes('/') ? (name.split('/').pop() ?? name) : name\n return RESERVED_BASENAMES.has(basename)\n}\n\n/**\n * EC-4: validate that resolving `targetSubpath` under `cwd` stays inside `cwd`.\n * Returns `true` when path is safe (stays inside), `false` when it escapes via\n * `..`, absolute path, null byte, or similar traversal vector.\n */\nfunction isPathInside(cwd: string, targetSubpath: string): boolean {\n if (targetSubpath.includes('\\x00')) return false\n if (targetSubpath.startsWith('/') || targetSubpath.startsWith('\\\\')) return false\n const resolved = resolve(cwd, targetSubpath)\n const cwdResolved = resolve(cwd)\n const sep = process.platform === 'win32' ? '\\\\' : '/'\n return resolved === cwdResolved || resolved.startsWith(cwdResolved + sep)\n}\n\nfunction toPascalCase(name: string): string {\n return name\n .split(/[-/]/)\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join('')\n}\n\nfunction toCamelCase(name: string): string {\n const pascal = toPascalCase(name)\n return pascal.charAt(0).toLowerCase() + pascal.slice(1)\n}\n\nfunction generateRouteTemplate(name: string): string {\n return [\n `import { defineRoute } from 'theokit/server'`,\n `import { z } from 'zod'`,\n ``,\n `export const GET = defineRoute({`,\n ` handler: ({ ctx }) => {`,\n ` return { message: 'TODO: implement ${name} GET' }`,\n ` },`,\n `})`,\n ``,\n ].join('\\n')\n}\n\nfunction generateControllerTemplate(name: string): string {\n const className = toPascalCase(name) + 'Controller'\n const baseName = name.split('/').pop() ?? name\n return [\n `// AUTO-GENERATED by \\`theokit generate controller ${name}\\``,\n `import { Controller, Get } from '@theokit/http'`,\n ``,\n `@Controller('${baseName}')`,\n `export class ${className} {`,\n ` @Get()`,\n ` findAll(): string {`,\n ` return 'This action returns all ${baseName}'`,\n ` }`,\n `}`,\n ``,\n ].join('\\n')\n}\n\nfunction generateActionTemplate(name: string): string {\n const camel = toCamelCase(name)\n return [\n `import { defineAction } from 'theokit/server'`,\n `import { z } from 'zod'`,\n ``,\n `export const ${camel} = defineAction({`,\n ` input: z.object({}),`,\n ` handler: ({ input, ctx }) => {`,\n ` return { message: 'TODO: implement ${name}' }`,\n ` },`,\n `})`,\n ``,\n ].join('\\n')\n}\n\n/**\n * Co-located test template for `theokit generate action <name>` (T5.2 +\n * plan Q4 cenário 1: roundtrip serialize). Skeleton uses vitest BDD shape\n * per testing.md.\n */\nfunction generateAgentTemplate(name: string): string {\n const pascal = toPascalCase(name)\n return [\n `import 'reflect-metadata'`,\n `import { Agent, MainLoop } from '@theokit/agents'`,\n `import { UseGuards } from '@theokit/http'`,\n ``,\n `@Agent({`,\n ` name: '${name}',`,\n ` route: '/api/agents/${name}',`,\n ` model: 'openai/gpt-4o-mini',`,\n ` systemPrompt: 'You are a helpful ${name} assistant.',`,\n `})`,\n `export class ${pascal}Agent {`,\n ` @MainLoop({ strategy: 'react', maxIterations: 5 })`,\n ` async run() {}`,\n `}`,\n ``,\n ].join('\\n')\n}\n\nfunction generateToolboxTemplate(name: string): string {\n const pascal = toPascalCase(name)\n return [\n `import 'reflect-metadata'`,\n `import { z } from 'zod'`,\n `import { Toolbox, Tool } from '@theokit/agents'`,\n ``,\n `@Toolbox({ namespace: '${name}' })`,\n `export class ${pascal}Tools {`,\n ` @Tool({`,\n ` name: 'hello',`,\n ` description: 'Say hello',`,\n ` input: z.object({ name: z.string() }),`,\n ` })`,\n ` async hello(input: { name: string }) {`,\n ` return \\`Hello, \\${input.name}!\\``,\n ` }`,\n `}`,\n ``,\n ].join('\\n')\n}\n\nfunction resolveTemplate(\n cwd: string,\n type: GeneratorType,\n name: string,\n): { filePath: string; content: string } | null {\n switch (type) {\n case 'route':\n return {\n filePath: resolve(cwd, 'server/routes', `${name}.ts`),\n content: generateRouteTemplate(name),\n }\n case 'action':\n return {\n filePath: resolve(cwd, 'server/actions', `${name}.ts`),\n content: generateActionTemplate(name),\n }\n case 'page':\n return { filePath: resolve(cwd, `app/${name}/page.tsx`), content: generatePageTemplate(name) }\n case 'ws':\n return {\n filePath: resolve(cwd, 'server/ws', `${name}.ts`),\n content: generateWsTemplate(name),\n }\n case 'controller':\n return {\n filePath: resolve(cwd, 'server/controllers', `${name}.controller.ts`),\n content: generateControllerTemplate(name),\n }\n case 'agent':\n return {\n filePath: resolve(cwd, 'server/agents', `${name}.agent.ts`),\n content: generateAgentTemplate(name),\n }\n case 'toolbox':\n return {\n filePath: resolve(cwd, 'server/toolboxes', `${name}.tools.ts`),\n content: generateToolboxTemplate(name),\n }\n default:\n return null\n }\n}\n\nfunction generateActionTestTemplate(name: string): string {\n const camel = toCamelCase(name)\n return [\n `import { describe, it, expect } from 'vitest'`,\n ``,\n `import { ${camel} } from './${name.split('/').pop()}.js'`,\n ``,\n `describe('${name} action', () => {`,\n ` it('should accept a valid input shape', () => {`,\n ` expect(${camel}.input).toBeDefined()`,\n ` expect(typeof ${camel}.handler).toBe('function')`,\n ` })`,\n ``,\n ` it('should reject invalid input via zod schema', () => {`,\n ` const parsed = ${camel}.input.safeParse({ __invalid: true })`,\n ` // Empty object schema accepts {}; tighten this assertion when adding fields.`,\n ` expect(parsed.success).toBe(true)`,\n ` })`,\n `})`,\n ``,\n ].join('\\n')\n}\n\nfunction generatePageTemplate(name: string): string {\n const pascal = toPascalCase(name)\n return [`export default function ${pascal}Page() {`, ` return <h1>${pascal}</h1>`, `}`, ``].join(\n '\\n',\n )\n}\n\nfunction generateWsTemplate(_name: string): string {\n return [\n `import { defineWebSocket } from 'theokit/server'`,\n ``,\n `export default defineWebSocket({`,\n ` onMessage(ws, data) {`,\n ` ws.send(\\`echo: \\${data}\\`)`,\n ` },`,\n `})`,\n ``,\n ].join('\\n')\n}\n\n/**\n * Programmatic generate. Returns a structured result instead of throwing —\n * Studio (`theokit_generate` tool) consumes this directly. The CLI wrapper\n * below maps the structured result to console output + exit code semantics.\n */\n// eslint-disable-next-line @typescript-eslint/require-await\nexport async function generate(opts: GenerateOptions): Promise<GenerateResult> {\n const { cwd, type, name } = opts\n\n if (!existsSync(resolve(cwd, 'theo.config.ts')) && !existsSync(resolve(cwd, 'theo.config.js'))) {\n return {\n status: 'not_a_project',\n message: 'Not a Theo project. cwd has no theo.config.ts or theo.config.js',\n }\n }\n\n if (!VALID_TYPES.includes(type as GeneratorType)) {\n return {\n status: 'invalid_kind',\n message: `Invalid generator type \"${type}\". Available: ${VALID_TYPES.join(', ')}`,\n }\n }\n\n if (!name || !toKebabCase(name)) {\n return {\n status: 'invalid_name',\n message: `Invalid name \"${name}\". Use kebab-case: lowercase letters, numbers, hyphens.`,\n }\n }\n\n // EC-2-related: reject reserved JS identifier basenames (would shadow\n // built-in prototype machinery in virtual module emit or scan).\n if (hasReservedSegment(name)) {\n return {\n status: 'invalid_name',\n message: `Reserved name \"${name}\" — basename collides with built-in identifier (index/constructor/__proto__/prototype/hasOwnProperty).`,\n }\n }\n\n // Resource generates multiple files — handle separately\n if (type === 'resource') {\n return generateResource(cwd, name, opts.fields ?? [])\n }\n\n const resolved = resolveTemplate(cwd, type as GeneratorType, name)\n if (resolved === null) {\n return { status: 'invalid_kind', message: `Unknown type: ${type}` }\n }\n const { filePath, content } = resolved\n\n // EC-4: confirm the resolved filePath stays inside cwd. `toKebabCase`\n // already rejects most traversal vectors but a defense-in-depth check\n // against `..` slipping in via valid-looking segments is cheap.\n const relativeFromCwd = filePath.startsWith(resolve(cwd))\n ? filePath.slice(resolve(cwd).length + 1)\n : filePath\n if (!isPathInside(cwd, relativeFromCwd)) {\n return {\n status: 'invalid_name',\n message: `Path traversal denied: \"${name}\" would escape project root.`,\n }\n }\n\n if (existsSync(filePath)) {\n return { status: 'already_exists', filePath, kind: type as GeneratorType, name }\n }\n\n mkdirSync(dirname(filePath), { recursive: true })\n writeFileSync(filePath, content)\n\n // T5.2 + plan Q4: emit co-located test file alongside actions (only\n // for action type; routes/pages/ws keep prior single-file behavior).\n // Skip if existing test file present (preserve user-customized tests).\n if (type === 'action') {\n const testPath = filePath.replace(/\\.ts$/, '.test.ts')\n if (!existsSync(testPath)) {\n writeFileSync(testPath, generateActionTestTemplate(name))\n }\n }\n\n return { status: 'created', filePath, kind: type as GeneratorType, name }\n}\n\n/**\n * CLI entry point — preserves the original surface (throws + console.log).\n * Wraps the programmatic `generate` function.\n */\nexport async function generateCommand(\n type: string,\n name: string,\n fields?: string[],\n): Promise<void> {\n const result = await generate({ cwd: process.cwd(), type, name, fields })\n switch (result.status) {\n case 'not_a_project':\n throw new Error('Not a Theo project. Run this from a project root with theo.config.ts')\n case 'invalid_kind':\n throw new Error(result.message ?? 'Invalid kind')\n case 'invalid_name':\n throw new Error(\n `Invalid name \"${name}\". Use kebab-case: lowercase letters, numbers, hyphens. Example: my-route`,\n )\n case 'already_exists':\n console.log(`\\n ⚠ ${result.filePath} already exists. Skipping.\\n`)\n return\n case 'created':\n console.log(`\\n ✓ Created ${type}: ${result.filePath}\\n`)\n return\n }\n}\n","import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\n\nimport type { GenerateResult } from './generate-types.js'\n\n// --- Field parsing ---\n\nconst ALLOWED_FIELD_TYPES = new Set(['string', 'text', 'number', 'boolean'])\nconst RESERVED_FIELDS = new Set(['id', 'createdAt', 'created_at'])\n\nexport interface ResourceField {\n name: string\n type: 'string' | 'text' | 'number' | 'boolean'\n drizzleColumn: string\n zodType: string\n}\n\nexport function parseResourceFields(args: string[]): ResourceField[] {\n if (args.length === 0) {\n throw new Error(\n 'Resource requires at least one field. Example: theokit generate resource posts title:string',\n )\n }\n return args.map((arg) => {\n const parts = arg.split(':')\n if (parts.length !== 2 || !parts[0] || !parts[1]) {\n throw new Error(`Invalid field \"${arg}\". Use name:type format (e.g. title:string)`)\n }\n const [name, type] = parts\n if (!ALLOWED_FIELD_TYPES.has(type)) {\n throw new Error(\n `Unknown field type \"${type}\" in \"${arg}\". Valid types: ${[...ALLOWED_FIELD_TYPES].join(', ')}`,\n )\n }\n if (RESERVED_FIELDS.has(name)) {\n throw new Error(`Field \"${name}\" is reserved (auto-generated by the framework)`)\n }\n const fieldType = type as ResourceField['type']\n return {\n name,\n type: fieldType,\n drizzleColumn: mapDrizzleColumn(name, fieldType),\n zodType: mapZodType(fieldType),\n }\n })\n}\n\n// --- Column / type mapping ---\n\nfunction mapDrizzleColumn(name: string, type: ResourceField['type']): string {\n switch (type) {\n case 'string':\n case 'text':\n return `text('${name}').notNull()`\n case 'number':\n return `integer('${name}').notNull()`\n case 'boolean':\n return `integer('${name}', { mode: 'boolean' }).notNull().default(false)`\n }\n}\n\nfunction mapZodType(type: ResourceField['type']): string {\n switch (type) {\n case 'string':\n case 'text':\n return 'z.string()'\n case 'number':\n return 'z.number()'\n case 'boolean':\n return 'z.boolean()'\n }\n}\n\n// --- Template generators ---\n\nfunction generateSchemaEntry(resourceName: string, fields: ResourceField[]): string {\n const cols = [\n ` id: integer('id').primaryKey({ autoIncrement: true }),`,\n ...fields.map((f) => ` ${f.name}: ${f.drizzleColumn},`),\n ` createdAt: text('created_at')`,\n ` .notNull()`,\n ` .$defaultFn(() => new Date().toISOString().split('T')[0]),`,\n ]\n return [\n ``,\n `export const ${resourceName} = sqliteTable('${resourceName}', {`,\n ...cols,\n `})`,\n ``,\n ].join('\\n')\n}\n\nfunction generateRouteIndex(resourceName: string, fields: ResourceField[]): string {\n const bodyFields = fields.map((f) => ` ${f.name}: ${f.zodType},`).join('\\n')\n return [\n `import { defineRoute } from 'theokit/server/define'`,\n `import { z } from 'zod'`,\n `import { db } from '../../db/index.js'`,\n `import { ${resourceName} } from '../../db/schema.js'`,\n ``,\n `export const GET = defineRoute({`,\n ` handler: () => db.select().from(${resourceName}).all(),`,\n `})`,\n ``,\n `export const POST = defineRoute({`,\n ` body: z.object({`,\n bodyFields,\n ` }),`,\n ` status: 201,`,\n ` handler: ({ body }) => {`,\n ` const result = db.insert(${resourceName}).values(body).returning().get()`,\n ` return result`,\n ` },`,\n `})`,\n ``,\n ].join('\\n')\n}\n\nfunction generateRouteId(resourceName: string, fields: ResourceField[]): string {\n const updateFields = fields.map((f) => ` ${f.name}: ${f.zodType}.optional(),`).join('\\n')\n return [\n `import { defineRoute } from 'theokit/server/define'`,\n `import { z } from 'zod'`,\n `import { db } from '../../db/index.js'`,\n `import { ${resourceName} } from '../../db/schema.js'`,\n `import { eq } from 'drizzle-orm'`,\n ``,\n `export const GET = defineRoute({`,\n ` params: z.object({ id: z.coerce.number() }),`,\n ` handler: ({ params }) => {`,\n ` const item = db.select().from(${resourceName}).where(eq(${resourceName}.id, params.id)).get()`,\n ` if (!item) return new Response(JSON.stringify({ error: 'Not found' }), { status: 404 })`,\n ` return item`,\n ` },`,\n `})`,\n ``,\n `export const PUT = defineRoute({`,\n ` params: z.object({ id: z.coerce.number() }),`,\n ` body: z.object({`,\n updateFields,\n ` }),`,\n ` handler: ({ params, body }) => {`,\n ` const result = db.update(${resourceName}).set(body).where(eq(${resourceName}.id, params.id)).returning().get()`,\n ` if (!result) return new Response(JSON.stringify({ error: 'Not found' }), { status: 404 })`,\n ` return result`,\n ` },`,\n `})`,\n ``,\n `export const DELETE = defineRoute({`,\n ` params: z.object({ id: z.coerce.number() }),`,\n ` status: 204,`,\n ` handler: ({ params }) => {`,\n ` db.delete(${resourceName}).where(eq(${resourceName}.id, params.id)).run()`,\n ` },`,\n `})`,\n ``,\n ].join('\\n')\n}\n\nfunction generateTestTemplate(resourceName: string): string {\n return [\n `import { describe, it, expect } from 'vitest'`,\n ``,\n `describe('${resourceName} API', () => {`,\n ` it('should have a schema defined', async () => {`,\n ` const schema = await import('../server/db/schema.js')`,\n ` expect(schema.${resourceName}).toBeDefined()`,\n ` })`,\n ``,\n ` it('should have route handlers', async () => {`,\n ` const index = await import('../server/routes/${resourceName}/index.js')`,\n ` expect(index.GET).toBeDefined()`,\n ` expect(index.POST).toBeDefined()`,\n ` })`,\n `})`,\n ``,\n ].join('\\n')\n}\n\n// --- Orchestrator ---\n\nexport function generateResource(cwd: string, name: string, fieldArgs: string[]): GenerateResult {\n let fields: ResourceField[]\n try {\n fields = parseResourceFields(fieldArgs)\n } catch (err) {\n return { status: 'invalid_name', message: err instanceof Error ? err.message : String(err) }\n }\n\n const schemaPath = resolve(cwd, 'server/db/schema.ts')\n const routeIndexPath = resolve(cwd, `server/routes/${name}/index.ts`)\n const routeIdPath = resolve(cwd, `server/routes/${name}/[id].ts`)\n const testPath = resolve(cwd, `tests/${name}.test.ts`)\n\n if (!existsSync(schemaPath)) {\n return {\n status: 'not_a_project',\n message:\n 'server/db/schema.ts not found. Run this from a TheoKit project with database setup.',\n }\n }\n\n if (existsSync(routeIndexPath)) {\n return { status: 'already_exists', filePath: routeIndexPath, kind: 'resource', name }\n }\n\n const existingSchema = readFileSync(schemaPath, 'utf-8')\n if (existingSchema.includes(`export const ${name} =`)) {\n return {\n status: 'already_exists',\n filePath: schemaPath,\n kind: 'resource',\n name,\n message: `Table \"${name}\" already exists in schema.ts`,\n }\n }\n\n // 1. Append schema entry\n appendFileSync(schemaPath, generateSchemaEntry(name, fields))\n\n // 2. Create route files\n mkdirSync(dirname(routeIndexPath), { recursive: true })\n writeFileSync(routeIndexPath, generateRouteIndex(name, fields))\n writeFileSync(routeIdPath, generateRouteId(name, fields))\n\n // 3. Create test file\n mkdirSync(dirname(testPath), { recursive: true })\n if (!existsSync(testPath)) {\n writeFileSync(testPath, generateTestTemplate(name))\n }\n\n return { status: 'created', filePath: routeIndexPath, kind: 'resource', name }\n}\n","/**\n * Shared types for the `theokit generate` CLI command family.\n *\n * Extracted to break the circular dependency between generate.ts ↔ generate-resource.ts\n * (architecture-remediation plan T1.1, 2026-06-12).\n */\n\nexport const VALID_TYPES = [\n 'route',\n 'action',\n 'page',\n 'ws',\n 'controller',\n 'agent',\n 'toolbox',\n 'resource',\n] as const\nexport type GeneratorType = (typeof VALID_TYPES)[number]\n\nexport interface GenerateOptions {\n cwd: string\n type: string\n name: string\n fields?: string[]\n}\n\nexport type GenerateStatus =\n | 'created'\n | 'already_exists'\n | 'invalid_kind'\n | 'invalid_name'\n | 'not_a_project'\n\nexport interface GenerateResult {\n status: GenerateStatus\n filePath?: string\n kind?: GeneratorType\n name?: string\n message?: string\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","writeFileSync","resolve","dirname","resolve","existsSync","mkdirSync","dirname","writeFileSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/generate.ts","../src/cli/commands/generate-resource.ts","../src/cli/commands/generate-types.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\n\nimport { generateResource } from './generate-resource.js'\nimport {\n VALID_TYPES,\n type GeneratorType,\n type GenerateOptions,\n type GenerateStatus,\n type GenerateResult,\n} from './generate-types.js'\n\nexport {\n VALID_TYPES,\n type GeneratorType,\n type GenerateOptions,\n type GenerateStatus,\n type GenerateResult,\n}\n\nfunction toKebabCase(name: string): boolean {\n return /^[a-z][a-z0-9/-]*$/.test(name)\n}\n\n/**\n * Reserved JS identifier names that conflict with object prototype machinery\n * or would shadow built-in module shapes if used as action/route names.\n * Mirrors action-scan's RESERVED_NAMES (T1.4).\n */\nconst RESERVED_BASENAMES = new Set([\n 'index',\n 'constructor',\n '__proto__',\n 'prototype',\n 'hasOwnProperty',\n])\n\n/**\n * Validate that a name segment doesn't hit the reserved-identifier list.\n * Checks the BASENAME (last `/` segment) — nested `admin/constructor` still\n * rejected because the file `constructor.ts` would be the conflict.\n */\nfunction hasReservedSegment(name: string): boolean {\n const basename = name.includes('/') ? (name.split('/').pop() ?? name) : name\n return RESERVED_BASENAMES.has(basename)\n}\n\n/**\n * EC-4: validate that resolving `targetSubpath` under `cwd` stays inside `cwd`.\n * Returns `true` when path is safe (stays inside), `false` when it escapes via\n * `..`, absolute path, null byte, or similar traversal vector.\n */\nfunction isPathInside(cwd: string, targetSubpath: string): boolean {\n if (targetSubpath.includes('\\x00')) return false\n if (targetSubpath.startsWith('/') || targetSubpath.startsWith('\\\\')) return false\n const resolved = resolve(cwd, targetSubpath)\n const cwdResolved = resolve(cwd)\n const sep = process.platform === 'win32' ? '\\\\' : '/'\n return resolved === cwdResolved || resolved.startsWith(cwdResolved + sep)\n}\n\nfunction toPascalCase(name: string): string {\n return name\n .split(/[-/]/)\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join('')\n}\n\nfunction toCamelCase(name: string): string {\n const pascal = toPascalCase(name)\n return pascal.charAt(0).toLowerCase() + pascal.slice(1)\n}\n\nfunction generateRouteTemplate(name: string): string {\n return [\n `import { defineRoute } from 'theokit/server'`,\n `import { z } from 'zod'`,\n ``,\n `export const GET = defineRoute({`,\n ` handler: ({ ctx }) => {`,\n ` return { message: 'TODO: implement ${name} GET' }`,\n ` },`,\n `})`,\n ``,\n ].join('\\n')\n}\n\nfunction generateControllerTemplate(name: string): string {\n const className = toPascalCase(name) + 'Controller'\n const baseName = name.split('/').pop() ?? name\n return [\n `// AUTO-GENERATED by \\`theokit generate controller ${name}\\``,\n `import { Controller, Get } from '@theokit/http'`,\n ``,\n `@Controller('${baseName}')`,\n `export class ${className} {`,\n ` @Get()`,\n ` findAll(): string {`,\n ` return 'This action returns all ${baseName}'`,\n ` }`,\n `}`,\n ``,\n ].join('\\n')\n}\n\nfunction generateActionTemplate(name: string): string {\n const camel = toCamelCase(name)\n return [\n `import { defineAction } from 'theokit/server'`,\n `import { z } from 'zod'`,\n ``,\n `export const ${camel} = defineAction({`,\n ` input: z.object({}),`,\n ` handler: ({ input, ctx }) => {`,\n ` return { message: 'TODO: implement ${name}' }`,\n ` },`,\n `})`,\n ``,\n ].join('\\n')\n}\n\n/**\n * Co-located test template for `theokit generate action <name>` (T5.2 +\n * plan Q4 cenário 1: roundtrip serialize). Skeleton uses vitest BDD shape\n * per testing.md.\n */\nfunction generateAgentTemplate(name: string): string {\n const pascal = toPascalCase(name)\n return [\n `import 'reflect-metadata'`,\n `import { Agent, MainLoop } from '@theokit/agents'`,\n `import { UseGuards } from '@theokit/http'`,\n ``,\n `@Agent({`,\n ` name: '${name}',`,\n ` route: '/api/agents/${name}',`,\n ` model: 'openai/gpt-4o-mini',`,\n ` systemPrompt: 'You are a helpful ${name} assistant.',`,\n `})`,\n `export class ${pascal}Agent {`,\n ` @MainLoop({ strategy: 'react', maxIterations: 5 })`,\n ` async run() {}`,\n `}`,\n ``,\n ].join('\\n')\n}\n\nfunction generateToolboxTemplate(name: string): string {\n const pascal = toPascalCase(name)\n return [\n `import 'reflect-metadata'`,\n `import { z } from 'zod'`,\n `import { Toolbox, Tool } from '@theokit/agents'`,\n ``,\n `@Toolbox({ namespace: '${name}' })`,\n `export class ${pascal}Tools {`,\n ` @Tool({`,\n ` name: 'hello',`,\n ` description: 'Say hello',`,\n ` input: z.object({ name: z.string() }),`,\n ` })`,\n ` async hello(input: { name: string }) {`,\n ` return \\`Hello, \\${input.name}!\\``,\n ` }`,\n `}`,\n ``,\n ].join('\\n')\n}\n\nfunction resolveTemplate(\n cwd: string,\n type: GeneratorType,\n name: string,\n): { filePath: string; content: string } | null {\n switch (type) {\n case 'route':\n return {\n filePath: resolve(cwd, 'server/routes', `${name}.ts`),\n content: generateRouteTemplate(name),\n }\n case 'action':\n return {\n filePath: resolve(cwd, 'server/actions', `${name}.ts`),\n content: generateActionTemplate(name),\n }\n case 'page':\n return { filePath: resolve(cwd, `app/${name}/page.tsx`), content: generatePageTemplate(name) }\n case 'ws':\n return {\n filePath: resolve(cwd, 'server/ws', `${name}.ts`),\n content: generateWsTemplate(name),\n }\n case 'controller':\n return {\n filePath: resolve(cwd, 'server/controllers', `${name}.controller.ts`),\n content: generateControllerTemplate(name),\n }\n case 'agent':\n return {\n filePath: resolve(cwd, 'server/agents', `${name}.agent.ts`),\n content: generateAgentTemplate(name),\n }\n case 'toolbox':\n return {\n filePath: resolve(cwd, 'server/toolboxes', `${name}.tools.ts`),\n content: generateToolboxTemplate(name),\n }\n default:\n return null\n }\n}\n\nfunction generateActionTestTemplate(name: string): string {\n const camel = toCamelCase(name)\n return [\n `import { describe, it, expect } from 'vitest'`,\n ``,\n `import { ${camel} } from './${name.split('/').pop()}.js'`,\n ``,\n `describe('${name} action', () => {`,\n ` it('should accept a valid input shape', () => {`,\n ` expect(${camel}.input).toBeDefined()`,\n ` expect(typeof ${camel}.handler).toBe('function')`,\n ` })`,\n ``,\n ` it('should reject invalid input via zod schema', () => {`,\n ` const parsed = ${camel}.input.safeParse({ __invalid: true })`,\n ` // Empty object schema accepts {}; tighten this assertion when adding fields.`,\n ` expect(parsed.success).toBe(true)`,\n ` })`,\n `})`,\n ``,\n ].join('\\n')\n}\n\nfunction generatePageTemplate(name: string): string {\n const pascal = toPascalCase(name)\n return [`export default function ${pascal}Page() {`, ` return <h1>${pascal}</h1>`, `}`, ``].join(\n '\\n',\n )\n}\n\nfunction generateWsTemplate(_name: string): string {\n return [\n `import { defineWebSocket } from 'theokit/server'`,\n ``,\n `export default defineWebSocket({`,\n ` onMessage(ws, data) {`,\n ` ws.send(\\`echo: \\${data}\\`)`,\n ` },`,\n `})`,\n ``,\n ].join('\\n')\n}\n\n/**\n * Programmatic generate. Returns a structured result instead of throwing —\n * Studio (`theokit_generate` tool) consumes this directly. The CLI wrapper\n * below maps the structured result to console output + exit code semantics.\n */\n// eslint-disable-next-line @typescript-eslint/require-await\nexport async function generate(opts: GenerateOptions): Promise<GenerateResult> {\n const { cwd, type, name } = opts\n\n if (!existsSync(resolve(cwd, 'theo.config.ts')) && !existsSync(resolve(cwd, 'theo.config.js'))) {\n return {\n status: 'not_a_project',\n message: 'Not a Theo project. cwd has no theo.config.ts or theo.config.js',\n }\n }\n\n if (!VALID_TYPES.includes(type as GeneratorType)) {\n return {\n status: 'invalid_kind',\n message: `Invalid generator type \"${type}\". Available: ${VALID_TYPES.join(', ')}`,\n }\n }\n\n if (!name || !toKebabCase(name)) {\n return {\n status: 'invalid_name',\n message: `Invalid name \"${name}\". Use kebab-case: lowercase letters, numbers, hyphens.`,\n }\n }\n\n // EC-2-related: reject reserved JS identifier basenames (would shadow\n // built-in prototype machinery in virtual module emit or scan).\n if (hasReservedSegment(name)) {\n return {\n status: 'invalid_name',\n message: `Reserved name \"${name}\" — basename collides with built-in identifier (index/constructor/__proto__/prototype/hasOwnProperty).`,\n }\n }\n\n // Resource generates multiple files — handle separately\n if (type === 'resource') {\n return generateResource(cwd, name, opts.fields ?? [])\n }\n\n const resolved = resolveTemplate(cwd, type as GeneratorType, name)\n if (resolved === null) {\n return { status: 'invalid_kind', message: `Unknown type: ${type}` }\n }\n const { filePath, content } = resolved\n\n // EC-4: confirm the resolved filePath stays inside cwd. `toKebabCase`\n // already rejects most traversal vectors but a defense-in-depth check\n // against `..` slipping in via valid-looking segments is cheap.\n const relativeFromCwd = filePath.startsWith(resolve(cwd))\n ? filePath.slice(resolve(cwd).length + 1)\n : filePath\n if (!isPathInside(cwd, relativeFromCwd)) {\n return {\n status: 'invalid_name',\n message: `Path traversal denied: \"${name}\" would escape project root.`,\n }\n }\n\n if (existsSync(filePath)) {\n return { status: 'already_exists', filePath, kind: type as GeneratorType, name }\n }\n\n mkdirSync(dirname(filePath), { recursive: true })\n writeFileSync(filePath, content)\n\n // T5.2 + plan Q4: emit co-located test file alongside actions (only\n // for action type; routes/pages/ws keep prior single-file behavior).\n // Skip if existing test file present (preserve user-customized tests).\n if (type === 'action') {\n const testPath = filePath.replace(/\\.ts$/, '.test.ts')\n if (!existsSync(testPath)) {\n writeFileSync(testPath, generateActionTestTemplate(name))\n }\n }\n\n return { status: 'created', filePath, kind: type as GeneratorType, name }\n}\n\n/**\n * CLI entry point — preserves the original surface (throws + console.log).\n * Wraps the programmatic `generate` function.\n */\nexport async function generateCommand(\n type: string,\n name: string,\n fields?: string[],\n): Promise<void> {\n const result = await generate({ cwd: process.cwd(), type, name, fields })\n switch (result.status) {\n case 'not_a_project':\n throw new Error('Not a Theo project. Run this from a project root with theo.config.ts')\n case 'invalid_kind':\n throw new Error(result.message ?? 'Invalid kind')\n case 'invalid_name':\n throw new Error(\n `Invalid name \"${name}\". Use kebab-case: lowercase letters, numbers, hyphens. Example: my-route`,\n )\n case 'already_exists':\n console.log(`\\n ⚠ ${result.filePath} already exists. Skipping.\\n`)\n return\n case 'created':\n console.log(`\\n ✓ Created ${type}: ${result.filePath}\\n`)\n return\n }\n}\n","import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\n\nimport type { GenerateResult } from './generate-types.js'\n\n// --- Field parsing ---\n\nconst ALLOWED_FIELD_TYPES = new Set(['string', 'text', 'number', 'boolean'])\nconst RESERVED_FIELDS = new Set(['id', 'createdAt', 'created_at'])\n\nexport interface ResourceField {\n name: string\n type: 'string' | 'text' | 'number' | 'boolean'\n drizzleColumn: string\n zodType: string\n}\n\nexport function parseResourceFields(args: string[]): ResourceField[] {\n if (args.length === 0) {\n throw new Error(\n 'Resource requires at least one field. Example: theokit generate resource posts title:string',\n )\n }\n return args.map((arg) => {\n const parts = arg.split(':')\n if (parts.length !== 2 || !parts[0] || !parts[1]) {\n throw new Error(`Invalid field \"${arg}\". Use name:type format (e.g. title:string)`)\n }\n const [name, type] = parts\n if (!ALLOWED_FIELD_TYPES.has(type)) {\n throw new Error(\n `Unknown field type \"${type}\" in \"${arg}\". Valid types: ${[...ALLOWED_FIELD_TYPES].join(', ')}`,\n )\n }\n if (RESERVED_FIELDS.has(name)) {\n throw new Error(`Field \"${name}\" is reserved (auto-generated by the framework)`)\n }\n const fieldType = type as ResourceField['type']\n return {\n name,\n type: fieldType,\n drizzleColumn: mapDrizzleColumn(name, fieldType),\n zodType: mapZodType(fieldType),\n }\n })\n}\n\n// --- Column / type mapping ---\n\nfunction mapDrizzleColumn(name: string, type: ResourceField['type']): string {\n switch (type) {\n case 'string':\n case 'text':\n return `text('${name}').notNull()`\n case 'number':\n return `integer('${name}').notNull()`\n case 'boolean':\n return `integer('${name}', { mode: 'boolean' }).notNull().default(false)`\n }\n}\n\nfunction mapZodType(type: ResourceField['type']): string {\n switch (type) {\n case 'string':\n case 'text':\n return 'z.string()'\n case 'number':\n return 'z.number()'\n case 'boolean':\n return 'z.boolean()'\n }\n}\n\n// --- Template generators ---\n\nfunction generateSchemaEntry(resourceName: string, fields: ResourceField[]): string {\n const cols = [\n ` id: integer('id').primaryKey({ autoIncrement: true }),`,\n ...fields.map((f) => ` ${f.name}: ${f.drizzleColumn},`),\n ` createdAt: text('created_at')`,\n ` .notNull()`,\n ` .$defaultFn(() => new Date().toISOString().split('T')[0]),`,\n ]\n return [\n ``,\n `export const ${resourceName} = sqliteTable('${resourceName}', {`,\n ...cols,\n `})`,\n ``,\n ].join('\\n')\n}\n\nfunction generateRouteIndex(resourceName: string, fields: ResourceField[]): string {\n const bodyFields = fields.map((f) => ` ${f.name}: ${f.zodType},`).join('\\n')\n return [\n `import { defineRoute } from 'theokit/server/define'`,\n `import { z } from 'zod'`,\n `import { db } from '../../db/index.js'`,\n `import { ${resourceName} } from '../../db/schema.js'`,\n ``,\n `export const GET = defineRoute({`,\n ` handler: () => db.select().from(${resourceName}).all(),`,\n `})`,\n ``,\n `export const POST = defineRoute({`,\n ` body: z.object({`,\n bodyFields,\n ` }),`,\n ` status: 201,`,\n ` handler: ({ body }) => {`,\n ` const result = db.insert(${resourceName}).values(body).returning().get()`,\n ` return result`,\n ` },`,\n `})`,\n ``,\n ].join('\\n')\n}\n\nfunction generateRouteId(resourceName: string, fields: ResourceField[]): string {\n const updateFields = fields.map((f) => ` ${f.name}: ${f.zodType}.optional(),`).join('\\n')\n return [\n `import { defineRoute } from 'theokit/server/define'`,\n `import { z } from 'zod'`,\n `import { db } from '../../db/index.js'`,\n `import { ${resourceName} } from '../../db/schema.js'`,\n `import { eq } from 'drizzle-orm'`,\n ``,\n `export const GET = defineRoute({`,\n ` params: z.object({ id: z.coerce.number() }),`,\n ` handler: ({ params }) => {`,\n ` const item = db.select().from(${resourceName}).where(eq(${resourceName}.id, params.id)).get()`,\n ` if (!item) return new Response(JSON.stringify({ error: 'Not found' }), { status: 404 })`,\n ` return item`,\n ` },`,\n `})`,\n ``,\n `export const PUT = defineRoute({`,\n ` params: z.object({ id: z.coerce.number() }),`,\n ` body: z.object({`,\n updateFields,\n ` }),`,\n ` handler: ({ params, body }) => {`,\n ` const result = db.update(${resourceName}).set(body).where(eq(${resourceName}.id, params.id)).returning().get()`,\n ` if (!result) return new Response(JSON.stringify({ error: 'Not found' }), { status: 404 })`,\n ` return result`,\n ` },`,\n `})`,\n ``,\n `export const DELETE = defineRoute({`,\n ` params: z.object({ id: z.coerce.number() }),`,\n ` status: 204,`,\n ` handler: ({ params }) => {`,\n ` db.delete(${resourceName}).where(eq(${resourceName}.id, params.id)).run()`,\n ` },`,\n `})`,\n ``,\n ].join('\\n')\n}\n\nfunction generateTestTemplate(resourceName: string): string {\n return [\n `import { describe, it, expect } from 'vitest'`,\n ``,\n `describe('${resourceName} API', () => {`,\n ` it('should have a schema defined', async () => {`,\n ` const schema = await import('../server/db/schema.js')`,\n ` expect(schema.${resourceName}).toBeDefined()`,\n ` })`,\n ``,\n ` it('should have route handlers', async () => {`,\n ` const index = await import('../server/routes/${resourceName}/index.js')`,\n ` expect(index.GET).toBeDefined()`,\n ` expect(index.POST).toBeDefined()`,\n ` })`,\n `})`,\n ``,\n ].join('\\n')\n}\n\n// --- Orchestrator ---\n\nexport function generateResource(cwd: string, name: string, fieldArgs: string[]): GenerateResult {\n let fields: ResourceField[]\n try {\n fields = parseResourceFields(fieldArgs)\n } catch (err) {\n return { status: 'invalid_name', message: err instanceof Error ? err.message : String(err) }\n }\n\n const schemaPath = resolve(cwd, 'server/db/schema.ts')\n const routeIndexPath = resolve(cwd, `server/routes/${name}/index.ts`)\n const routeIdPath = resolve(cwd, `server/routes/${name}/[id].ts`)\n const testPath = resolve(cwd, `tests/${name}.test.ts`)\n\n if (!existsSync(schemaPath)) {\n return {\n status: 'not_a_project',\n message:\n 'server/db/schema.ts not found. Run this from a TheoKit project with database setup.',\n }\n }\n\n if (existsSync(routeIndexPath)) {\n return { status: 'already_exists', filePath: routeIndexPath, kind: 'resource', name }\n }\n\n const existingSchema = readFileSync(schemaPath, 'utf-8')\n if (existingSchema.includes(`export const ${name} =`)) {\n return {\n status: 'already_exists',\n filePath: schemaPath,\n kind: 'resource',\n name,\n message: `Table \"${name}\" already exists in schema.ts`,\n }\n }\n\n // 1. Append schema entry\n appendFileSync(schemaPath, generateSchemaEntry(name, fields))\n\n // 2. Create route files\n mkdirSync(dirname(routeIndexPath), { recursive: true })\n writeFileSync(routeIndexPath, generateRouteIndex(name, fields))\n writeFileSync(routeIdPath, generateRouteId(name, fields))\n\n // 3. Create test file\n mkdirSync(dirname(testPath), { recursive: true })\n if (!existsSync(testPath)) {\n writeFileSync(testPath, generateTestTemplate(name))\n }\n\n return { status: 'created', filePath: routeIndexPath, kind: 'resource', name }\n}\n","/**\n * Shared types for the `theokit generate` CLI command family.\n *\n * Extracted to break the circular dependency between generate.ts ↔ generate-resource.ts\n * (architecture-remediation plan T1.1, 2026-06-12).\n */\n\nexport const VALID_TYPES = [\n 'route',\n 'action',\n 'page',\n 'ws',\n 'controller',\n 'agent',\n 'toolbox',\n 'resource',\n] as const\nexport type GeneratorType = (typeof VALID_TYPES)[number]\n\nexport interface GenerateOptions {\n cwd: string\n type: string\n name: string\n fields?: string[]\n}\n\nexport type GenerateStatus =\n | 'created'\n | 'already_exists'\n | 'invalid_kind'\n | 'invalid_name'\n | 'not_a_project'\n\nexport interface GenerateResult {\n status: GenerateStatus\n filePath?: string\n kind?: GeneratorType\n name?: string\n message?: string\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","writeFileSync","resolve","dirname","resolve","existsSync","mkdirSync","dirname","writeFileSync"]}
|
|
@@ -8,7 +8,6 @@ import "./chunk-CCVC6P6B.js";
|
|
|
8
8
|
import {
|
|
9
9
|
scanRoutes
|
|
10
10
|
} from "./chunk-IEES3CHD.js";
|
|
11
|
-
import "./chunk-KT3MWNZG.js";
|
|
12
11
|
|
|
13
12
|
// src/cli/commands/info.ts
|
|
14
13
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -101,4 +100,4 @@ export {
|
|
|
101
100
|
buildInfo,
|
|
102
101
|
infoCommand
|
|
103
102
|
};
|
|
104
|
-
//# sourceMappingURL=info-
|
|
103
|
+
//# sourceMappingURL=info-DWUBVHHF.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/commands/info.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * CLI `theo info` reader. Reads `package.json`, `theo.config.*`, lock\n * files under the user's `cwd`. Read-only build-time tool. No HTTP input.\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport { loadConfig } from '../../config/load-config.js'\nimport { scanRoutes } from '../../router/scan.js'\n\nexport interface RuntimeInfo {\n name: 'node' | 'bun' | 'deno' | 'unknown'\n version: string\n}\n\nexport interface PackageJsonMinimal {\n name?: string\n version?: string\n}\n\nexport interface ConfigSummary {\n ok: boolean\n summary: string\n}\n\nexport interface BuildInfoDeps {\n cwd: string\n readPackageJson?: () => PackageJsonMinimal | null\n detectRuntime?: () => RuntimeInfo\n loadConfig?: () => Promise<ConfigSummary>\n countRoutes?: () => number\n}\n\nfunction defaultDetectRuntime(): RuntimeInfo {\n const g = globalThis as { Bun?: { version: string }; Deno?: { version: { deno: string } } }\n if (g.Bun?.version) {\n return { name: 'bun', version: g.Bun.version }\n }\n if (g.Deno?.version) {\n return { name: 'deno', version: g.Deno.version.deno }\n }\n if (typeof process !== 'undefined' && process.versions.node) {\n return { name: 'node', version: process.versions.node }\n }\n return { name: 'unknown', version: '?' }\n}\n\nfunction defaultReadPackageJson(cwd: string): PackageJsonMinimal | null {\n const path = resolve(cwd, 'package.json')\n if (!existsSync(path)) return null\n try {\n const raw = readFileSync(path, 'utf-8')\n const parsed = JSON.parse(raw) as PackageJsonMinimal\n return parsed\n } catch {\n return null\n }\n}\n\nasync function defaultLoadConfig(cwd: string): Promise<ConfigSummary> {\n try {\n await loadConfig(cwd)\n return { ok: true, summary: 'config OK' }\n } catch (err) {\n return { ok: false, summary: (err as Error).message }\n }\n}\n\nfunction defaultCountRoutes(cwd: string): number {\n const appDir = resolve(cwd, 'app')\n if (!existsSync(appDir)) return 0\n const tree = scanRoutes(appDir)\n let count = 0\n function walk(node: { page?: string; children: { page?: string; children: unknown[] }[] }): void {\n if (node.page) count++\n for (const child of node.children) walk(child as never)\n }\n walk(tree)\n return count\n}\n\nexport async function buildInfo(deps: BuildInfoDeps): Promise<string> {\n const cwd = deps.cwd\n const readPkg = deps.readPackageJson ?? (() => defaultReadPackageJson(cwd))\n const runtime = (deps.detectRuntime ?? defaultDetectRuntime)()\n const loadCfg = deps.loadConfig ?? (() => defaultLoadConfig(cwd))\n const countRts = deps.countRoutes ?? (() => defaultCountRoutes(cwd))\n\n const pkg = readPkg()\n const config = await loadCfg()\n\n let routesCount = 0\n let scanError: string | null = null\n try {\n routesCount = countRts()\n } catch (err) {\n scanError = (err as Error).message\n }\n\n const projectLine = pkg?.name ? `${pkg.name}@${pkg.version ?? '?'}` : '(missing)'\n\n const configLine = config.ok ? 'OK' : `INVALID — ${config.summary}`\n\n const routesLine = scanError ? `Scan failed: ${scanError}` : `Routes: ${routesCount}`\n\n return [\n `# Theo info`,\n ``,\n `## Project`,\n `Project: ${projectLine}`,\n ``,\n `## Runtime`,\n `${runtime.name} ${runtime.version}`,\n ``,\n `## Config`,\n `Config: ${configLine}`,\n ``,\n `## App`,\n routesLine,\n ``,\n ].join('\\n')\n}\n\nexport async function infoCommand(): Promise<void> {\n const cwd = process.cwd()\n const output = await buildInfo({ cwd })\n console.log(output)\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/info.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * CLI `theo info` reader. Reads `package.json`, `theo.config.*`, lock\n * files under the user's `cwd`. Read-only build-time tool. No HTTP input.\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport { loadConfig } from '../../config/load-config.js'\nimport { scanRoutes } from '../../router/scan.js'\n\nexport interface RuntimeInfo {\n name: 'node' | 'bun' | 'deno' | 'unknown'\n version: string\n}\n\nexport interface PackageJsonMinimal {\n name?: string\n version?: string\n}\n\nexport interface ConfigSummary {\n ok: boolean\n summary: string\n}\n\nexport interface BuildInfoDeps {\n cwd: string\n readPackageJson?: () => PackageJsonMinimal | null\n detectRuntime?: () => RuntimeInfo\n loadConfig?: () => Promise<ConfigSummary>\n countRoutes?: () => number\n}\n\nfunction defaultDetectRuntime(): RuntimeInfo {\n const g = globalThis as { Bun?: { version: string }; Deno?: { version: { deno: string } } }\n if (g.Bun?.version) {\n return { name: 'bun', version: g.Bun.version }\n }\n if (g.Deno?.version) {\n return { name: 'deno', version: g.Deno.version.deno }\n }\n if (typeof process !== 'undefined' && process.versions.node) {\n return { name: 'node', version: process.versions.node }\n }\n return { name: 'unknown', version: '?' }\n}\n\nfunction defaultReadPackageJson(cwd: string): PackageJsonMinimal | null {\n const path = resolve(cwd, 'package.json')\n if (!existsSync(path)) return null\n try {\n const raw = readFileSync(path, 'utf-8')\n const parsed = JSON.parse(raw) as PackageJsonMinimal\n return parsed\n } catch {\n return null\n }\n}\n\nasync function defaultLoadConfig(cwd: string): Promise<ConfigSummary> {\n try {\n await loadConfig(cwd)\n return { ok: true, summary: 'config OK' }\n } catch (err) {\n return { ok: false, summary: (err as Error).message }\n }\n}\n\nfunction defaultCountRoutes(cwd: string): number {\n const appDir = resolve(cwd, 'app')\n if (!existsSync(appDir)) return 0\n const tree = scanRoutes(appDir)\n let count = 0\n function walk(node: { page?: string; children: { page?: string; children: unknown[] }[] }): void {\n if (node.page) count++\n for (const child of node.children) walk(child as never)\n }\n walk(tree)\n return count\n}\n\nexport async function buildInfo(deps: BuildInfoDeps): Promise<string> {\n const cwd = deps.cwd\n const readPkg = deps.readPackageJson ?? (() => defaultReadPackageJson(cwd))\n const runtime = (deps.detectRuntime ?? defaultDetectRuntime)()\n const loadCfg = deps.loadConfig ?? (() => defaultLoadConfig(cwd))\n const countRts = deps.countRoutes ?? (() => defaultCountRoutes(cwd))\n\n const pkg = readPkg()\n const config = await loadCfg()\n\n let routesCount = 0\n let scanError: string | null = null\n try {\n routesCount = countRts()\n } catch (err) {\n scanError = (err as Error).message\n }\n\n const projectLine = pkg?.name ? `${pkg.name}@${pkg.version ?? '?'}` : '(missing)'\n\n const configLine = config.ok ? 'OK' : `INVALID — ${config.summary}`\n\n const routesLine = scanError ? `Scan failed: ${scanError}` : `Routes: ${routesCount}`\n\n return [\n `# Theo info`,\n ``,\n `## Project`,\n `Project: ${projectLine}`,\n ``,\n `## Runtime`,\n `${runtime.name} ${runtime.version}`,\n ``,\n `## Config`,\n `Config: ${configLine}`,\n ``,\n `## App`,\n routesLine,\n ``,\n ].join('\\n')\n}\n\nexport async function infoCommand(): Promise<void> {\n const cwd = process.cwd()\n const output = await buildInfo({ cwd })\n console.log(output)\n}\n"],"mappings":";;;;;;;;;;;;AAIA,SAAS,YAAY,oBAAoB;AACzC,SAAS,eAAe;AA4BxB,SAAS,uBAAoC;AAC3C,QAAM,IAAI;AACV,MAAI,EAAE,KAAK,SAAS;AAClB,WAAO,EAAE,MAAM,OAAO,SAAS,EAAE,IAAI,QAAQ;AAAA,EAC/C;AACA,MAAI,EAAE,MAAM,SAAS;AACnB,WAAO,EAAE,MAAM,QAAQ,SAAS,EAAE,KAAK,QAAQ,KAAK;AAAA,EACtD;AACA,MAAI,OAAO,YAAY,eAAe,QAAQ,SAAS,MAAM;AAC3D,WAAO,EAAE,MAAM,QAAQ,SAAS,QAAQ,SAAS,KAAK;AAAA,EACxD;AACA,SAAO,EAAE,MAAM,WAAW,SAAS,IAAI;AACzC;AAEA,SAAS,uBAAuB,KAAwC;AACtE,QAAM,OAAO,QAAQ,KAAK,cAAc;AACxC,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACF,UAAM,MAAM,aAAa,MAAM,OAAO;AACtC,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,kBAAkB,KAAqC;AACpE,MAAI;AACF,UAAM,WAAW,GAAG;AACpB,WAAO,EAAE,IAAI,MAAM,SAAS,YAAY;AAAA,EAC1C,SAAS,KAAK;AACZ,WAAO,EAAE,IAAI,OAAO,SAAU,IAAc,QAAQ;AAAA,EACtD;AACF;AAEA,SAAS,mBAAmB,KAAqB;AAC/C,QAAM,SAAS,QAAQ,KAAK,KAAK;AACjC,MAAI,CAAC,WAAW,MAAM,EAAG,QAAO;AAChC,QAAM,OAAO,WAAW,MAAM;AAC9B,MAAI,QAAQ;AACZ,WAAS,KAAK,MAAmF;AAC/F,QAAI,KAAK,KAAM;AACf,eAAW,SAAS,KAAK,SAAU,MAAK,KAAc;AAAA,EACxD;AACA,OAAK,IAAI;AACT,SAAO;AACT;AAEA,eAAsB,UAAU,MAAsC;AACpE,QAAM,MAAM,KAAK;AACjB,QAAM,UAAU,KAAK,oBAAoB,MAAM,uBAAuB,GAAG;AACzE,QAAM,WAAW,KAAK,iBAAiB,sBAAsB;AAC7D,QAAM,UAAU,KAAK,eAAe,MAAM,kBAAkB,GAAG;AAC/D,QAAM,WAAW,KAAK,gBAAgB,MAAM,mBAAmB,GAAG;AAElE,QAAM,MAAM,QAAQ;AACpB,QAAM,SAAS,MAAM,QAAQ;AAE7B,MAAI,cAAc;AAClB,MAAI,YAA2B;AAC/B,MAAI;AACF,kBAAc,SAAS;AAAA,EACzB,SAAS,KAAK;AACZ,gBAAa,IAAc;AAAA,EAC7B;AAEA,QAAM,cAAc,KAAK,OAAO,GAAG,IAAI,IAAI,IAAI,IAAI,WAAW,GAAG,KAAK;AAEtE,QAAM,aAAa,OAAO,KAAK,OAAO,kBAAa,OAAO,OAAO;AAEjE,QAAM,aAAa,YAAY,gBAAgB,SAAS,KAAK,WAAW,WAAW;AAEnF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY,WAAW;AAAA,IACvB;AAAA,IACA;AAAA,IACA,GAAG,QAAQ,IAAI,IAAI,QAAQ,OAAO;AAAA,IAClC;AAAA,IACA;AAAA,IACA,WAAW,UAAU;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAEA,eAAsB,cAA6B;AACjD,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,SAAS,MAAM,UAAU,EAAE,IAAI,CAAC;AACtC,UAAQ,IAAI,MAAM;AACpB;","names":[]}
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
assertServicesUnsupported,
|
|
8
8
|
readManifest
|
|
9
9
|
} from "./chunk-CCVC6P6B.js";
|
|
10
|
-
import "./chunk-KT3MWNZG.js";
|
|
11
10
|
|
|
12
11
|
// src/adapters/netlify.ts
|
|
13
12
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -148,4 +147,4 @@ export {
|
|
|
148
147
|
netlifyAdapter,
|
|
149
148
|
renderNetlifyFunction
|
|
150
149
|
};
|
|
151
|
-
//# sourceMappingURL=netlify-
|
|
150
|
+
//# sourceMappingURL=netlify-E63264H6.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters/netlify.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Netlify deploy adapter. All paths derived from `cwd` and a fixed\n * `.theokit/netlify/` output layout. Build-time tool — no HTTP input.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport type { TheoConfig } from '../config/schema.js'\nimport { assertServicesUnsupported, readManifest } from '../services/index.js'\n\nimport { nodeAdapter } from './node.js'\nimport type { AdapterBuildContext, DeployAdapter } from './types.js'\n\nexport class NetlifyConflictError extends Error {\n constructor(path: string, currentTo: string) {\n super(\n `netlify.toml has a conflicting redirect: \"${path}\" → \"${currentTo}\". ` +\n `Remove it manually or edit it to point to \"/.netlify/functions/theo\" ` +\n `before running the Netlify adapter again.`,\n )\n this.name = 'NetlifyConflictError'\n }\n}\n\nexport interface NetlifyBuildDeps {\n runNodeBuild?: (config: TheoConfig, cwd: string, ctx?: AdapterBuildContext) => Promise<void>\n writeFile?: (path: string, content: string) => void\n ensureDir?: (path: string) => void\n readTomlIfExists?: () => string | null\n}\n\nexport function renderNetlifyFunction(): string {\n return [\n `// Generated by Theo — Netlify Functions adapter`,\n `import { resolve } from 'node:path'`,\n `import { randomUUID } from 'node:crypto'`,\n `import { scanServerRoutes, matchRoute, executeRoute, createProductionLoader } from 'theokit/server'`,\n `import { createWebShim } from 'theokit/adapters/web-shim'`,\n ``,\n `const cwd = process.cwd()`,\n `const serverDir = resolve(cwd, 'server')`,\n `let routesCache = null`,\n `let loaderCache = null`,\n ``,\n `export default async (request, context) => {`,\n ` const url = new URL(request.url)`,\n ` if (!url.pathname.startsWith('/api/')) {`,\n ` return new Response('Not Found', { status: 404 })`,\n ` }`,\n ``,\n ` if (!routesCache) routesCache = scanServerRoutes(serverDir)`,\n ` if (!loaderCache) loaderCache = createProductionLoader()`,\n ``,\n ` const match = matchRoute(url.pathname, routesCache)`,\n ` if (!match) {`,\n ` return new Response(JSON.stringify({ error: { code: 'NOT_FOUND' } }), {`,\n ` status: 404,`,\n ` headers: { 'content-type': 'application/json' },`,\n ` })`,\n ` }`,\n ``,\n ` const { req, res, toResponse } = createWebShim(request)`,\n ` const requestId = randomUUID()`,\n ` const method = request.method.toUpperCase()`,\n ` await executeRoute({ route: match.route, method, params: match.params, req, res, loadModule: loaderCache, serverDir, requestId })`,\n ` return toResponse()`,\n `}`,\n ].join('\\n')\n}\n\nconst THEO_REDIRECT_FROM = '/api/*'\nconst THEO_REDIRECT_TO = '/.netlify/functions/theo'\n\n/**\n * TOML merge (EC-2) — non-destructive.\n *\n * We avoid adding a TOML parser dependency by working at the line level. The\n * supported subset is sufficient for the Netlify use case:\n * - detect existing `[[redirects]]` blocks\n * - find a block whose `from = \"...\"` matches our target\n * - error if such a block points elsewhere\n * - otherwise append our block\n *\n * Unknown sections (e.g. `[build]`, `[[headers]]`, `[context.production.environment]`)\n * are preserved as-is.\n */\ninterface NetlifyRedirectBlock {\n startLine: number\n endLine: number\n from?: string\n to?: string\n}\n\nfunction parseRedirectBlocks(lines: readonly string[]): NetlifyRedirectBlock[] {\n const blocks: NetlifyRedirectBlock[] = []\n let current: NetlifyRedirectBlock | null = null\n\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i].trim()\n if (trimmed === '[[redirects]]') {\n if (current) {\n current.endLine = i - 1\n blocks.push(current)\n }\n current = { startLine: i, endLine: i }\n continue\n }\n if (current && /^\\[(\\[|[^[])/.test(trimmed)) {\n // Entering another section — close current.\n current.endLine = i - 1\n blocks.push(current)\n current = null\n }\n if (current) {\n const fromMatch = /^from\\s*=\\s*\"([^\"]+)\"/.exec(trimmed)\n if (fromMatch) current.from = fromMatch[1]\n const toMatch = /^to\\s*=\\s*\"([^\"]+)\"/.exec(trimmed)\n if (toMatch) current.to = toMatch[1]\n current.endLine = i\n }\n }\n if (current) blocks.push(current)\n return blocks\n}\n\nconst THEO_REDIRECT_TARGET = [\n '[[redirects]]',\n ' from = \"/api/*\"',\n ' to = \"/.netlify/functions/theo\"',\n ' status = 200',\n ' force = true',\n].join('\\n')\n\nexport function mergeNetlifyToml(existing: string | null): string {\n if (existing === null || existing.trim().length === 0) {\n return `${THEO_REDIRECT_TARGET}\\n`\n }\n\n const blocks = parseRedirectBlocks(existing.split(/\\r?\\n/))\n\n // Detect conflict: every `/api/*` block must point at our function.\n for (const b of blocks) {\n if (b.from !== THEO_REDIRECT_FROM) continue\n if (b.to === THEO_REDIRECT_TO) {\n // Already merged — return existing as-is (idempotent).\n return existing.endsWith('\\n') ? existing : `${existing}\\n`\n }\n throw new NetlifyConflictError(b.from, b.to ?? '(unknown)')\n }\n\n // No conflict — append target block.\n const sep = existing.endsWith('\\n') ? '' : '\\n'\n return `${existing}${sep}\\n${THEO_REDIRECT_TARGET}\\n`\n}\n\nexport async function buildNetlify(\n config: TheoConfig,\n cwd: string,\n deps: NetlifyBuildDeps = {},\n ctx?: AdapterBuildContext,\n): Promise<void> {\n // Wave 2 (T2.2) — reject polyglot services on this adapter.\n assertServicesUnsupported('netlify', readManifest(cwd))\n\n const runNodeBuild = deps.runNodeBuild ?? nodeAdapter.build.bind(nodeAdapter)\n await runNodeBuild(config, cwd, ctx)\n\n const ensureDir = deps.ensureDir ?? ((p: string) => mkdirSync(p, { recursive: true }))\n const writeFile =\n deps.writeFile ??\n ((p, c) => {\n writeFileSync(p, c)\n })\n const readTomlIfExists =\n deps.readTomlIfExists ??\n (() => {\n const p = resolve(cwd, 'netlify.toml')\n return existsSync(p) ? readFileSync(p, 'utf-8') : null\n })\n\n const fnDir = resolve(cwd, '.netlify/functions')\n ensureDir(fnDir)\n writeFile(resolve(fnDir, 'theo.mjs'), renderNetlifyFunction())\n\n const existingToml = readTomlIfExists()\n const merged = mergeNetlifyToml(existingToml)\n writeFile(resolve(cwd, 'netlify.toml'), merged)\n\n // eslint-disable-next-line no-console -- CLI build progress\n console.log('\\n ✓ Netlify output → .netlify/functions/theo.mjs + netlify.toml\\n')\n}\n\nexport const netlifyAdapter: DeployAdapter = {\n name: 'netlify',\n build(config, cwd, ctx) {\n return buildNetlify(config, cwd, {}, ctx)\n },\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/adapters/netlify.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Netlify deploy adapter. All paths derived from `cwd` and a fixed\n * `.theokit/netlify/` output layout. Build-time tool — no HTTP input.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport type { TheoConfig } from '../config/schema.js'\nimport { assertServicesUnsupported, readManifest } from '../services/index.js'\n\nimport { nodeAdapter } from './node.js'\nimport type { AdapterBuildContext, DeployAdapter } from './types.js'\n\nexport class NetlifyConflictError extends Error {\n constructor(path: string, currentTo: string) {\n super(\n `netlify.toml has a conflicting redirect: \"${path}\" → \"${currentTo}\". ` +\n `Remove it manually or edit it to point to \"/.netlify/functions/theo\" ` +\n `before running the Netlify adapter again.`,\n )\n this.name = 'NetlifyConflictError'\n }\n}\n\nexport interface NetlifyBuildDeps {\n runNodeBuild?: (config: TheoConfig, cwd: string, ctx?: AdapterBuildContext) => Promise<void>\n writeFile?: (path: string, content: string) => void\n ensureDir?: (path: string) => void\n readTomlIfExists?: () => string | null\n}\n\nexport function renderNetlifyFunction(): string {\n return [\n `// Generated by Theo — Netlify Functions adapter`,\n `import { resolve } from 'node:path'`,\n `import { randomUUID } from 'node:crypto'`,\n `import { scanServerRoutes, matchRoute, executeRoute, createProductionLoader } from 'theokit/server'`,\n `import { createWebShim } from 'theokit/adapters/web-shim'`,\n ``,\n `const cwd = process.cwd()`,\n `const serverDir = resolve(cwd, 'server')`,\n `let routesCache = null`,\n `let loaderCache = null`,\n ``,\n `export default async (request, context) => {`,\n ` const url = new URL(request.url)`,\n ` if (!url.pathname.startsWith('/api/')) {`,\n ` return new Response('Not Found', { status: 404 })`,\n ` }`,\n ``,\n ` if (!routesCache) routesCache = scanServerRoutes(serverDir)`,\n ` if (!loaderCache) loaderCache = createProductionLoader()`,\n ``,\n ` const match = matchRoute(url.pathname, routesCache)`,\n ` if (!match) {`,\n ` return new Response(JSON.stringify({ error: { code: 'NOT_FOUND' } }), {`,\n ` status: 404,`,\n ` headers: { 'content-type': 'application/json' },`,\n ` })`,\n ` }`,\n ``,\n ` const { req, res, toResponse } = createWebShim(request)`,\n ` const requestId = randomUUID()`,\n ` const method = request.method.toUpperCase()`,\n ` await executeRoute({ route: match.route, method, params: match.params, req, res, loadModule: loaderCache, serverDir, requestId })`,\n ` return toResponse()`,\n `}`,\n ].join('\\n')\n}\n\nconst THEO_REDIRECT_FROM = '/api/*'\nconst THEO_REDIRECT_TO = '/.netlify/functions/theo'\n\n/**\n * TOML merge (EC-2) — non-destructive.\n *\n * We avoid adding a TOML parser dependency by working at the line level. The\n * supported subset is sufficient for the Netlify use case:\n * - detect existing `[[redirects]]` blocks\n * - find a block whose `from = \"...\"` matches our target\n * - error if such a block points elsewhere\n * - otherwise append our block\n *\n * Unknown sections (e.g. `[build]`, `[[headers]]`, `[context.production.environment]`)\n * are preserved as-is.\n */\ninterface NetlifyRedirectBlock {\n startLine: number\n endLine: number\n from?: string\n to?: string\n}\n\nfunction parseRedirectBlocks(lines: readonly string[]): NetlifyRedirectBlock[] {\n const blocks: NetlifyRedirectBlock[] = []\n let current: NetlifyRedirectBlock | null = null\n\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i].trim()\n if (trimmed === '[[redirects]]') {\n if (current) {\n current.endLine = i - 1\n blocks.push(current)\n }\n current = { startLine: i, endLine: i }\n continue\n }\n if (current && /^\\[(\\[|[^[])/.test(trimmed)) {\n // Entering another section — close current.\n current.endLine = i - 1\n blocks.push(current)\n current = null\n }\n if (current) {\n const fromMatch = /^from\\s*=\\s*\"([^\"]+)\"/.exec(trimmed)\n if (fromMatch) current.from = fromMatch[1]\n const toMatch = /^to\\s*=\\s*\"([^\"]+)\"/.exec(trimmed)\n if (toMatch) current.to = toMatch[1]\n current.endLine = i\n }\n }\n if (current) blocks.push(current)\n return blocks\n}\n\nconst THEO_REDIRECT_TARGET = [\n '[[redirects]]',\n ' from = \"/api/*\"',\n ' to = \"/.netlify/functions/theo\"',\n ' status = 200',\n ' force = true',\n].join('\\n')\n\nexport function mergeNetlifyToml(existing: string | null): string {\n if (existing === null || existing.trim().length === 0) {\n return `${THEO_REDIRECT_TARGET}\\n`\n }\n\n const blocks = parseRedirectBlocks(existing.split(/\\r?\\n/))\n\n // Detect conflict: every `/api/*` block must point at our function.\n for (const b of blocks) {\n if (b.from !== THEO_REDIRECT_FROM) continue\n if (b.to === THEO_REDIRECT_TO) {\n // Already merged — return existing as-is (idempotent).\n return existing.endsWith('\\n') ? existing : `${existing}\\n`\n }\n throw new NetlifyConflictError(b.from, b.to ?? '(unknown)')\n }\n\n // No conflict — append target block.\n const sep = existing.endsWith('\\n') ? '' : '\\n'\n return `${existing}${sep}\\n${THEO_REDIRECT_TARGET}\\n`\n}\n\nexport async function buildNetlify(\n config: TheoConfig,\n cwd: string,\n deps: NetlifyBuildDeps = {},\n ctx?: AdapterBuildContext,\n): Promise<void> {\n // Wave 2 (T2.2) — reject polyglot services on this adapter.\n assertServicesUnsupported('netlify', readManifest(cwd))\n\n const runNodeBuild = deps.runNodeBuild ?? nodeAdapter.build.bind(nodeAdapter)\n await runNodeBuild(config, cwd, ctx)\n\n const ensureDir = deps.ensureDir ?? ((p: string) => mkdirSync(p, { recursive: true }))\n const writeFile =\n deps.writeFile ??\n ((p, c) => {\n writeFileSync(p, c)\n })\n const readTomlIfExists =\n deps.readTomlIfExists ??\n (() => {\n const p = resolve(cwd, 'netlify.toml')\n return existsSync(p) ? readFileSync(p, 'utf-8') : null\n })\n\n const fnDir = resolve(cwd, '.netlify/functions')\n ensureDir(fnDir)\n writeFile(resolve(fnDir, 'theo.mjs'), renderNetlifyFunction())\n\n const existingToml = readTomlIfExists()\n const merged = mergeNetlifyToml(existingToml)\n writeFile(resolve(cwd, 'netlify.toml'), merged)\n\n // eslint-disable-next-line no-console -- CLI build progress\n console.log('\\n ✓ Netlify output → .netlify/functions/theo.mjs + netlify.toml\\n')\n}\n\nexport const netlifyAdapter: DeployAdapter = {\n name: 'netlify',\n build(config, cwd, ctx) {\n return buildNetlify(config, cwd, {}, ctx)\n },\n}\n"],"mappings":"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","names":[]}
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
generateManifest
|
|
14
14
|
} from "./chunk-3LVRAGAZ.js";
|
|
15
15
|
import "./chunk-6FYD34NX.js";
|
|
16
|
-
import "./chunk-KT3MWNZG.js";
|
|
17
16
|
|
|
18
17
|
// src/cli/commands/openapi.ts
|
|
19
18
|
import { mkdtempSync, rmSync } from "fs";
|
|
@@ -80,4 +79,4 @@ function emitOpenApiInMemory(manifest, config) {
|
|
|
80
79
|
export {
|
|
81
80
|
openapiCommand
|
|
82
81
|
};
|
|
83
|
-
//# sourceMappingURL=openapi-
|
|
82
|
+
//# sourceMappingURL=openapi-NPFP4WZY.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/commands/openapi.ts"],"sourcesContent":["/**\n * `theokit openapi` — regenerate `<distDir>/openapi.json` standalone.\n *\n * Mirrors trpc-openapi's `pnpm codegen` script. Useful in dev workflows\n * when only schemas changed and a full `theokit build` would be\n * overkill.\n *\n * Per G2 plan v1.1 T2.3. Single output (dev location only) — for the\n * dist artifact, run `theokit build`.\n *\n * Flags:\n * --dry-run Validate + print the document to stdout; skip the\n * filesystem write (EC-3 absorbed).\n */\nimport { mkdtempSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join, resolve } from 'node:path'\n\nimport { loadConfig } from '../../config/load-config.js'\nimport { generateManifest } from '../../server/scan/manifest.js'\nimport { emitOpenApi } from '../../vite-plugin/openapi-emit/emit.js'\nimport { loadRoutesForOpenApi } from '../../vite-plugin/openapi-emit/load-routes.js'\n\nconst MIGRATION_GUIDE_URL = 'docs/concepts/openapi.md'\n\nexport interface OpenApiCommandOptions {\n /** Project root (defaults to `process.cwd()`). */\n cwd?: string\n /** Skip the filesystem write; print the document to stdout (EC-3). */\n dryRun?: boolean\n}\n\nexport async function openapiCommand(options: OpenApiCommandOptions = {}): Promise<void> {\n const cwd = options.cwd ?? process.cwd()\n const config = await loadConfig(cwd)\n\n if (config.openapi === undefined) {\n console.error(\n `\\n ✗ openapi: not configured in theo.config.ts\\n\\n` +\n ` Add the \\`openapi\\` block to opt in:\\n\\n` +\n ` export default defineConfig({\\n` +\n ` openapi: {\\n` +\n ` title: 'My App',\\n` +\n ` version: '1.0.0',\\n` +\n ` },\\n` +\n ` })\\n\\n` +\n ` See ${MIGRATION_GUIDE_URL} for the full reference.\\n`,\n )\n process.exit(1)\n return\n }\n\n const serverDir = resolve(cwd, config.serverDir)\n const manifest = generateManifest(serverDir)\n const hydrated = await loadRoutesForOpenApi({ serverDir, routes: manifest.routes })\n\n if (options.dryRun) {\n // Build the document without writing — for the dry-run we still want a\n // valid emit pass, but with an in-memory output dir we never touch disk.\n const result = emitOpenApiInMemory(hydrated, config.openapi)\n console.log(`\\n ✓ openapi.json (dry-run): ${String(hydrated.length)} ops`)\n console.log(JSON.stringify(result.document, null, 2))\n console.log(`\\n Docs: ${MIGRATION_GUIDE_URL}\\n`)\n return\n }\n\n const distDir = resolve(cwd, config.distDir)\n const result = emitOpenApi({\n manifest: hydrated,\n config: { ...config.openapi, outDir: distDir },\n })\n console.log(\n `\\n ✓ openapi.json: ${String(hydrated.length)} ops → ${result.path}\\n` +\n ` Docs: ${MIGRATION_GUIDE_URL}\\n`,\n )\n}\n\n/**\n * Dry-run helper: build the OpenAPI doc without writing. Routes through\n * the same `emitOpenApi` but discards the side-effecting write by\n * targeting a temp directory we immediately ignore.\n *\n * Honest tradeoff: this still calls `mkdirSync` (no-op on existing tmpdir)\n * + a synchronous write to a discardable tempfile. Cost is < 1ms and the\n * code stays the single transformation. A pure no-write helper would\n * duplicate the orchestration in `emit.ts` — KISS prefers reuse.\n */\nfunction emitOpenApiInMemory(\n manifest: Parameters<typeof emitOpenApi>[0]['manifest'],\n config: Omit<Parameters<typeof emitOpenApi>[0]['config'], 'outDir'>,\n): { document: unknown } {\n const tmp = mkdtempSync(join(tmpdir(), 'theokit-openapi-dryrun-'))\n try {\n const result = emitOpenApi({ manifest, config: { ...config, outDir: tmp } })\n return { document: result.document }\n } finally {\n rmSync(tmp, { recursive: true, force: true })\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/openapi.ts"],"sourcesContent":["/**\n * `theokit openapi` — regenerate `<distDir>/openapi.json` standalone.\n *\n * Mirrors trpc-openapi's `pnpm codegen` script. Useful in dev workflows\n * when only schemas changed and a full `theokit build` would be\n * overkill.\n *\n * Per G2 plan v1.1 T2.3. Single output (dev location only) — for the\n * dist artifact, run `theokit build`.\n *\n * Flags:\n * --dry-run Validate + print the document to stdout; skip the\n * filesystem write (EC-3 absorbed).\n */\nimport { mkdtempSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join, resolve } from 'node:path'\n\nimport { loadConfig } from '../../config/load-config.js'\nimport { generateManifest } from '../../server/scan/manifest.js'\nimport { emitOpenApi } from '../../vite-plugin/openapi-emit/emit.js'\nimport { loadRoutesForOpenApi } from '../../vite-plugin/openapi-emit/load-routes.js'\n\nconst MIGRATION_GUIDE_URL = 'docs/concepts/openapi.md'\n\nexport interface OpenApiCommandOptions {\n /** Project root (defaults to `process.cwd()`). */\n cwd?: string\n /** Skip the filesystem write; print the document to stdout (EC-3). */\n dryRun?: boolean\n}\n\nexport async function openapiCommand(options: OpenApiCommandOptions = {}): Promise<void> {\n const cwd = options.cwd ?? process.cwd()\n const config = await loadConfig(cwd)\n\n if (config.openapi === undefined) {\n console.error(\n `\\n ✗ openapi: not configured in theo.config.ts\\n\\n` +\n ` Add the \\`openapi\\` block to opt in:\\n\\n` +\n ` export default defineConfig({\\n` +\n ` openapi: {\\n` +\n ` title: 'My App',\\n` +\n ` version: '1.0.0',\\n` +\n ` },\\n` +\n ` })\\n\\n` +\n ` See ${MIGRATION_GUIDE_URL} for the full reference.\\n`,\n )\n process.exit(1)\n return\n }\n\n const serverDir = resolve(cwd, config.serverDir)\n const manifest = generateManifest(serverDir)\n const hydrated = await loadRoutesForOpenApi({ serverDir, routes: manifest.routes })\n\n if (options.dryRun) {\n // Build the document without writing — for the dry-run we still want a\n // valid emit pass, but with an in-memory output dir we never touch disk.\n const result = emitOpenApiInMemory(hydrated, config.openapi)\n console.log(`\\n ✓ openapi.json (dry-run): ${String(hydrated.length)} ops`)\n console.log(JSON.stringify(result.document, null, 2))\n console.log(`\\n Docs: ${MIGRATION_GUIDE_URL}\\n`)\n return\n }\n\n const distDir = resolve(cwd, config.distDir)\n const result = emitOpenApi({\n manifest: hydrated,\n config: { ...config.openapi, outDir: distDir },\n })\n console.log(\n `\\n ✓ openapi.json: ${String(hydrated.length)} ops → ${result.path}\\n` +\n ` Docs: ${MIGRATION_GUIDE_URL}\\n`,\n )\n}\n\n/**\n * Dry-run helper: build the OpenAPI doc without writing. Routes through\n * the same `emitOpenApi` but discards the side-effecting write by\n * targeting a temp directory we immediately ignore.\n *\n * Honest tradeoff: this still calls `mkdirSync` (no-op on existing tmpdir)\n * + a synchronous write to a discardable tempfile. Cost is < 1ms and the\n * code stays the single transformation. A pure no-write helper would\n * duplicate the orchestration in `emit.ts` — KISS prefers reuse.\n */\nfunction emitOpenApiInMemory(\n manifest: Parameters<typeof emitOpenApi>[0]['manifest'],\n config: Omit<Parameters<typeof emitOpenApi>[0]['config'], 'outDir'>,\n): { document: unknown } {\n const tmp = mkdtempSync(join(tmpdir(), 'theokit-openapi-dryrun-'))\n try {\n const result = emitOpenApi({ manifest, config: { ...config, outDir: tmp } })\n return { document: result.document }\n } finally {\n rmSync(tmp, { recursive: true, force: true })\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAcA,SAAS,aAAa,cAAc;AACpC,SAAS,cAAc;AACvB,SAAS,MAAM,eAAe;AAO9B,IAAM,sBAAsB;AAS5B,eAAsB,eAAe,UAAiC,CAAC,GAAkB;AACvF,QAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;AACvC,QAAM,SAAS,MAAM,WAAW,GAAG;AAEnC,MAAI,OAAO,YAAY,QAAW;AAChC,YAAQ;AAAA,MACN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAQW,mBAAmB;AAAA;AAAA,IAChC;AACA,YAAQ,KAAK,CAAC;AACd;AAAA,EACF;AAEA,QAAM,YAAY,QAAQ,KAAK,OAAO,SAAS;AAC/C,QAAM,WAAW,iBAAiB,SAAS;AAC3C,QAAM,WAAW,MAAM,qBAAqB,EAAE,WAAW,QAAQ,SAAS,OAAO,CAAC;AAElF,MAAI,QAAQ,QAAQ;AAGlB,UAAMA,UAAS,oBAAoB,UAAU,OAAO,OAAO;AAC3D,YAAQ,IAAI;AAAA,mCAAiC,OAAO,SAAS,MAAM,CAAC,MAAM;AAC1E,YAAQ,IAAI,KAAK,UAAUA,QAAO,UAAU,MAAM,CAAC,CAAC;AACpD,YAAQ,IAAI;AAAA,UAAa,mBAAmB;AAAA,CAAI;AAChD;AAAA,EACF;AAEA,QAAM,UAAU,QAAQ,KAAK,OAAO,OAAO;AAC3C,QAAM,SAAS,YAAY;AAAA,IACzB,UAAU;AAAA,IACV,QAAQ,EAAE,GAAG,OAAO,SAAS,QAAQ,QAAQ;AAAA,EAC/C,CAAC;AACD,UAAQ;AAAA,IACN;AAAA,yBAAuB,OAAO,SAAS,MAAM,CAAC,eAAU,OAAO,IAAI;AAAA,UACtD,mBAAmB;AAAA;AAAA,EAClC;AACF;AAYA,SAAS,oBACP,UACA,QACuB;AACvB,QAAM,MAAM,YAAY,KAAK,OAAO,GAAG,yBAAyB,CAAC;AACjE,MAAI;AACF,UAAM,SAAS,YAAY,EAAE,UAAU,QAAQ,EAAE,GAAG,QAAQ,QAAQ,IAAI,EAAE,CAAC;AAC3E,WAAO,EAAE,UAAU,OAAO,SAAS;AAAA,EACrC,UAAE;AACA,WAAO,KAAK,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EAC9C;AACF;","names":["result"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "tsx/esm";
|
|
3
|
+
|
|
4
|
+
// src/adapters/registry.ts
|
|
5
|
+
var adapterRegistry = {
|
|
6
|
+
node: async () => (await import("./node-XVZAGVVO.js")).nodeAdapter,
|
|
7
|
+
vercel: async () => (await import("./vercel-6VWQTIUL.js")).vercelAdapter,
|
|
8
|
+
cloudflare: async () => (await import("./cloudflare-KHF7JJZ7.js")).cloudflareAdapter,
|
|
9
|
+
static: async () => (await import("./static-3JXPPDIH.js")).staticAdapter,
|
|
10
|
+
bun: async () => (await import("./bun-FUYLUP62.js")).bunAdapter,
|
|
11
|
+
"deno-deploy": async () => (await import("./deno-deploy-O4JP56BM.js")).denoDeployAdapter,
|
|
12
|
+
netlify: async () => (await import("./netlify-E63264H6.js")).netlifyAdapter,
|
|
13
|
+
"aws-lambda": async () => (await import("./aws-lambda-EC74AYN4.js")).awsLambdaAdapter,
|
|
14
|
+
"theo-cloud": async () => (await import("./theo-cloud-I3KQE3JN.js")).theoCloudAdapter
|
|
15
|
+
};
|
|
16
|
+
async function resolveAdapter(target) {
|
|
17
|
+
const factory = adapterRegistry[target];
|
|
18
|
+
return factory();
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
adapterRegistry,
|
|
22
|
+
resolveAdapter
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=registry-WHMRQG5F.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters/registry.ts"],"sourcesContent":["/**\n * Adapter Registry (T1.1 of architecture-medium-deferrals plan, ADR D1).\n *\n * Replaces the 9-case `switch (target)` in `cli/commands/build.ts` with a\n * declarative `Record<BuildTarget, () => Promise<DeployAdapter>>` map.\n *\n * Why a registry?\n * - **OCP** — adding a new adapter is now 1 line in this map + 1 string in\n * `VALID_TARGETS`. The CLI does not change.\n * - **Lazy import preserved** — each factory is `async () => (await import(...))`\n * so deps load only when the matching target is built.\n * - **TypeScript exhaustiveness** — `Record<BuildTarget, ...>` forces every\n * target in the union to have a registered factory. Adding a new target\n * without registering it is a compile error.\n *\n * The registry is the single source of truth for runtime adapter dispatch.\n * The string list `VALID_TARGETS` (in `./types.js`) is the source of truth\n * for CLI flag validation. A test (`test_valid_targets_matches_registry_keys`)\n * pins both lists to the same `BuildTarget` union to prevent drift.\n */\n\nimport type { BuildTarget, DeployAdapter } from './types.js'\n\nexport const adapterRegistry: Record<BuildTarget, () => Promise<DeployAdapter>> = {\n node: async () => (await import('./node.js')).nodeAdapter,\n vercel: async () => (await import('./vercel.js')).vercelAdapter,\n cloudflare: async () => (await import('./cloudflare.js')).cloudflareAdapter,\n static: async () => (await import('./static.js')).staticAdapter,\n bun: async () => (await import('./bun.js')).bunAdapter,\n 'deno-deploy': async () => (await import('./deno-deploy.js')).denoDeployAdapter,\n netlify: async () => (await import('./netlify.js')).netlifyAdapter,\n 'aws-lambda': async () => (await import('./aws-lambda.js')).awsLambdaAdapter,\n 'theo-cloud': async () => (await import('./theo-cloud.js')).theoCloudAdapter,\n}\n\n/**\n * Resolve a `DeployAdapter` for the given target. Throws actionable error\n * if the target is not registered (should be unreachable when callers use\n * the typed `BuildTarget` union, but guards against runtime drift).\n */\nexport async function resolveAdapter(target: BuildTarget): Promise<DeployAdapter> {\n // BuildTarget union guarantees the key exists at compile time. The runtime\n // accessor still returns the typed factory; we call it directly.\n const factory = adapterRegistry[target]\n return factory()\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/adapters/registry.ts"],"sourcesContent":["/**\n * Adapter Registry (T1.1 of architecture-medium-deferrals plan, ADR D1).\n *\n * Replaces the 9-case `switch (target)` in `cli/commands/build.ts` with a\n * declarative `Record<BuildTarget, () => Promise<DeployAdapter>>` map.\n *\n * Why a registry?\n * - **OCP** — adding a new adapter is now 1 line in this map + 1 string in\n * `VALID_TARGETS`. The CLI does not change.\n * - **Lazy import preserved** — each factory is `async () => (await import(...))`\n * so deps load only when the matching target is built.\n * - **TypeScript exhaustiveness** — `Record<BuildTarget, ...>` forces every\n * target in the union to have a registered factory. Adding a new target\n * without registering it is a compile error.\n *\n * The registry is the single source of truth for runtime adapter dispatch.\n * The string list `VALID_TARGETS` (in `./types.js`) is the source of truth\n * for CLI flag validation. A test (`test_valid_targets_matches_registry_keys`)\n * pins both lists to the same `BuildTarget` union to prevent drift.\n */\n\nimport type { BuildTarget, DeployAdapter } from './types.js'\n\nexport const adapterRegistry: Record<BuildTarget, () => Promise<DeployAdapter>> = {\n node: async () => (await import('./node.js')).nodeAdapter,\n vercel: async () => (await import('./vercel.js')).vercelAdapter,\n cloudflare: async () => (await import('./cloudflare.js')).cloudflareAdapter,\n static: async () => (await import('./static.js')).staticAdapter,\n bun: async () => (await import('./bun.js')).bunAdapter,\n 'deno-deploy': async () => (await import('./deno-deploy.js')).denoDeployAdapter,\n netlify: async () => (await import('./netlify.js')).netlifyAdapter,\n 'aws-lambda': async () => (await import('./aws-lambda.js')).awsLambdaAdapter,\n 'theo-cloud': async () => (await import('./theo-cloud.js')).theoCloudAdapter,\n}\n\n/**\n * Resolve a `DeployAdapter` for the given target. Throws actionable error\n * if the target is not registered (should be unreachable when callers use\n * the typed `BuildTarget` union, but guards against runtime drift).\n */\nexport async function resolveAdapter(target: BuildTarget): Promise<DeployAdapter> {\n // BuildTarget union guarantees the key exists at compile time. The runtime\n // accessor still returns the typed factory; we call it directly.\n const factory = adapterRegistry[target]\n return factory()\n}\n"],"mappings":";;;;AAuBO,IAAM,kBAAqE;AAAA,EAChF,MAAM,aAAa,MAAM,OAAO,oBAAW,GAAG;AAAA,EAC9C,QAAQ,aAAa,MAAM,OAAO,sBAAa,GAAG;AAAA,EAClD,YAAY,aAAa,MAAM,OAAO,0BAAiB,GAAG;AAAA,EAC1D,QAAQ,aAAa,MAAM,OAAO,sBAAa,GAAG;AAAA,EAClD,KAAK,aAAa,MAAM,OAAO,mBAAU,GAAG;AAAA,EAC5C,eAAe,aAAa,MAAM,OAAO,2BAAkB,GAAG;AAAA,EAC9D,SAAS,aAAa,MAAM,OAAO,uBAAc,GAAG;AAAA,EACpD,cAAc,aAAa,MAAM,OAAO,0BAAiB,GAAG;AAAA,EAC5D,cAAc,aAAa,MAAM,OAAO,0BAAiB,GAAG;AAC9D;AAOA,eAAsB,eAAe,QAA6C;AAGhF,QAAM,UAAU,gBAAgB,MAAM;AACtC,SAAO,QAAQ;AACjB;","names":[]}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "tsx/esm";
|
|
3
|
-
import "./chunk-KT3MWNZG.js";
|
|
4
3
|
|
|
5
4
|
// src/cli/commands/migrate/router.ts
|
|
6
5
|
import { execFileSync } from "child_process";
|
|
@@ -271,4 +270,4 @@ export {
|
|
|
271
270
|
isPortInUse,
|
|
272
271
|
routerMigrateCommand
|
|
273
272
|
};
|
|
274
|
-
//# sourceMappingURL=router-
|
|
273
|
+
//# sourceMappingURL=router-TLEAOFID.js.map
|
|
@@ -1 +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"]}
|
|
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"]}
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
scanServerRoutes,
|
|
14
14
|
scanWebSocketRoutes
|
|
15
15
|
} from "./chunk-6FYD34NX.js";
|
|
16
|
-
import "./chunk-KT3MWNZG.js";
|
|
17
16
|
|
|
18
17
|
// src/cli/commands/routes.ts
|
|
19
18
|
import { resolve, relative } from "path";
|
|
@@ -61,4 +60,4 @@ async function routesCommand() {
|
|
|
61
60
|
export {
|
|
62
61
|
routesCommand
|
|
63
62
|
};
|
|
64
|
-
//# sourceMappingURL=routes-
|
|
63
|
+
//# sourceMappingURL=routes-GHMCCV7V.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/commands/routes.ts"],"sourcesContent":["import { resolve, relative } from 'node:path'\n\nimport { loadConfig } from '../../config/load-config.js'\nimport { validateProjectStructure } from '../../config/validate-structure.js'\nimport { scanServerActions } from '../../server/scan/action-scan.js'\nimport { scanServerRoutes } from '../../server/scan/scan.js'\nimport { scanWebSocketRoutes } from '../../server/scan/ws-scan.js'\n\nexport async function routesCommand(): Promise<void> {\n const cwd = process.cwd()\n const config = await loadConfig(cwd)\n validateProjectStructure(cwd)\n\n const serverDir = resolve(cwd, config.serverDir)\n\n const apiRoutes = scanServerRoutes(serverDir)\n const actions = scanServerActions(serverDir)\n const wsRoutes = scanWebSocketRoutes(serverDir)\n\n const totalCount = apiRoutes.length + actions.length + wsRoutes.length\n\n if (totalCount === 0) {\n console.log('\\n No routes found.\\n')\n return\n }\n\n // API Routes\n if (apiRoutes.length > 0) {\n console.log('\\n API Routes')\n console.log(' ' + '─'.repeat(60))\n for (const route of apiRoutes) {\n const rel = relative(cwd, route.filePath)\n console.log(` GET/POST ${route.routePath.padEnd(30)} ${rel}`)\n }\n }\n\n // Actions\n if (actions.length > 0) {\n console.log('\\n Actions')\n console.log(' ' + '─'.repeat(60))\n for (const action of actions) {\n const rel = relative(cwd, action.filePath)\n console.log(` POST /api/__actions/${action.actionPath.padEnd(18)} ${rel}`)\n }\n }\n\n // WebSocket\n if (wsRoutes.length > 0) {\n console.log('\\n WebSocket')\n console.log(' ' + '─'.repeat(60))\n for (const route of wsRoutes) {\n const rel = relative(cwd, route.filePath)\n console.log(` WS ${route.wsPath.padEnd(30)} ${rel}`)\n }\n }\n\n console.log(`\\n Total: ${totalCount} endpoints\\n`)\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/routes.ts"],"sourcesContent":["import { resolve, relative } from 'node:path'\n\nimport { loadConfig } from '../../config/load-config.js'\nimport { validateProjectStructure } from '../../config/validate-structure.js'\nimport { scanServerActions } from '../../server/scan/action-scan.js'\nimport { scanServerRoutes } from '../../server/scan/scan.js'\nimport { scanWebSocketRoutes } from '../../server/scan/ws-scan.js'\n\nexport async function routesCommand(): Promise<void> {\n const cwd = process.cwd()\n const config = await loadConfig(cwd)\n validateProjectStructure(cwd)\n\n const serverDir = resolve(cwd, config.serverDir)\n\n const apiRoutes = scanServerRoutes(serverDir)\n const actions = scanServerActions(serverDir)\n const wsRoutes = scanWebSocketRoutes(serverDir)\n\n const totalCount = apiRoutes.length + actions.length + wsRoutes.length\n\n if (totalCount === 0) {\n console.log('\\n No routes found.\\n')\n return\n }\n\n // API Routes\n if (apiRoutes.length > 0) {\n console.log('\\n API Routes')\n console.log(' ' + '─'.repeat(60))\n for (const route of apiRoutes) {\n const rel = relative(cwd, route.filePath)\n console.log(` GET/POST ${route.routePath.padEnd(30)} ${rel}`)\n }\n }\n\n // Actions\n if (actions.length > 0) {\n console.log('\\n Actions')\n console.log(' ' + '─'.repeat(60))\n for (const action of actions) {\n const rel = relative(cwd, action.filePath)\n console.log(` POST /api/__actions/${action.actionPath.padEnd(18)} ${rel}`)\n }\n }\n\n // WebSocket\n if (wsRoutes.length > 0) {\n console.log('\\n WebSocket')\n console.log(' ' + '─'.repeat(60))\n for (const route of wsRoutes) {\n const rel = relative(cwd, route.filePath)\n console.log(` WS ${route.wsPath.padEnd(30)} ${rel}`)\n }\n }\n\n console.log(`\\n Total: ${totalCount} endpoints\\n`)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAAA,SAAS,SAAS,gBAAgB;AAQlC,eAAsB,gBAA+B;AACnD,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,SAAS,MAAM,WAAW,GAAG;AACnC,2BAAyB,GAAG;AAE5B,QAAM,YAAY,QAAQ,KAAK,OAAO,SAAS;AAE/C,QAAM,YAAY,iBAAiB,SAAS;AAC5C,QAAM,UAAU,kBAAkB,SAAS;AAC3C,QAAM,WAAW,oBAAoB,SAAS;AAE9C,QAAM,aAAa,UAAU,SAAS,QAAQ,SAAS,SAAS;AAEhE,MAAI,eAAe,GAAG;AACpB,YAAQ,IAAI,wBAAwB;AACpC;AAAA,EACF;AAGA,MAAI,UAAU,SAAS,GAAG;AACxB,YAAQ,IAAI,gBAAgB;AAC5B,YAAQ,IAAI,OAAO,SAAI,OAAO,EAAE,CAAC;AACjC,eAAW,SAAS,WAAW;AAC7B,YAAM,MAAM,SAAS,KAAK,MAAM,QAAQ;AACxC,cAAQ,IAAI,eAAe,MAAM,UAAU,OAAO,EAAE,CAAC,IAAI,GAAG,EAAE;AAAA,IAChE;AAAA,EACF;AAGA,MAAI,QAAQ,SAAS,GAAG;AACtB,YAAQ,IAAI,aAAa;AACzB,YAAQ,IAAI,OAAO,SAAI,OAAO,EAAE,CAAC;AACjC,eAAW,UAAU,SAAS;AAC5B,YAAM,MAAM,SAAS,KAAK,OAAO,QAAQ;AACzC,cAAQ,IAAI,8BAA8B,OAAO,WAAW,OAAO,EAAE,CAAC,IAAI,GAAG,EAAE;AAAA,IACjF;AAAA,EACF;AAGA,MAAI,SAAS,SAAS,GAAG;AACvB,YAAQ,IAAI,eAAe;AAC3B,YAAQ,IAAI,OAAO,SAAI,OAAO,EAAE,CAAC;AACjC,eAAW,SAAS,UAAU;AAC5B,YAAM,MAAM,SAAS,KAAK,MAAM,QAAQ;AACxC,cAAQ,IAAI,eAAe,MAAM,OAAO,OAAO,EAAE,CAAC,IAAI,GAAG,EAAE;AAAA,IAC7D;AAAA,EACF;AAEA,UAAQ,IAAI;AAAA,WAAc,UAAU;AAAA,CAAc;AACpD;","names":[]}
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
uploadSchema
|
|
15
15
|
} from "./chunk-BNGBG2SQ.js";
|
|
16
16
|
import "./chunk-CCVC6P6B.js";
|
|
17
|
-
import "./chunk-KT3MWNZG.js";
|
|
18
17
|
export {
|
|
19
18
|
cacheSchema,
|
|
20
19
|
corsSchema,
|
|
@@ -28,4 +27,4 @@ export {
|
|
|
28
27
|
theoConfigSchema,
|
|
29
28
|
uploadSchema
|
|
30
29
|
};
|
|
31
|
-
//# sourceMappingURL=schema-
|
|
30
|
+
//# sourceMappingURL=schema-7K4SFSA3.js.map
|
|
@@ -3,8 +3,7 @@ import "tsx/esm";
|
|
|
3
3
|
import {
|
|
4
4
|
serverErrorToEnvelope
|
|
5
5
|
} from "./chunk-WS67ONGI.js";
|
|
6
|
-
import "./chunk-KT3MWNZG.js";
|
|
7
6
|
export {
|
|
8
7
|
serverErrorToEnvelope
|
|
9
8
|
};
|
|
10
|
-
//# sourceMappingURL=server-error-to-envelope-
|
|
9
|
+
//# sourceMappingURL=server-error-to-envelope-OHQCV66V.js.map
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "tsx/esm";
|
|
3
|
-
import "./chunk-KT3MWNZG.js";
|
|
4
3
|
|
|
5
4
|
// src/vite-plugin/server-routes-hmr.ts
|
|
6
5
|
import { sep } from "path";
|
|
@@ -41,4 +40,4 @@ function serverRoutesHmrPlugin(opts) {
|
|
|
41
40
|
export {
|
|
42
41
|
serverRoutesHmrPlugin
|
|
43
42
|
};
|
|
44
|
-
//# sourceMappingURL=server-routes-hmr-
|
|
43
|
+
//# sourceMappingURL=server-routes-hmr-VZXOP5YT.js.map
|