theokit 0.1.0-alpha.8 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/dist/actions-virtual-module-E6PU47GV.js +175 -0
  2. package/dist/actions-virtual-module-E6PU47GV.js.map +1 -0
  3. package/dist/actions-virtual-module-E7IELVHL.js +174 -0
  4. package/dist/actions-virtual-module-E7IELVHL.js.map +1 -0
  5. package/dist/add-2ZFFGGE4.js +0 -0
  6. package/dist/app-typed-client-4L4L4BWD.js +289 -0
  7. package/dist/app-typed-client-4L4L4BWD.js.map +1 -0
  8. package/dist/app-typed-client-6PAT6VOL.js +289 -0
  9. package/dist/app-typed-client-6PAT6VOL.js.map +1 -0
  10. package/dist/aws-lambda-PONVF3UX.js +0 -0
  11. package/dist/broadcast-QOQGUNNK.js +0 -0
  12. package/dist/{build-IQQYCA3B.js → build-EWZDEDT3.js} +37 -11
  13. package/dist/build-EWZDEDT3.js.map +1 -0
  14. package/dist/build-request-body-preview-EOP2Y4F3.js +52 -0
  15. package/dist/build-request-body-preview-EOP2Y4F3.js.map +1 -0
  16. package/dist/build-request-body-preview-II2235E6.js +54 -0
  17. package/dist/build-request-body-preview-II2235E6.js.map +1 -0
  18. package/dist/bun-DITMZMKL.js +0 -0
  19. package/dist/check-PZR67OY5.js +0 -0
  20. package/dist/{chunk-GQSTRXXB.js → chunk-2BQYEBCK.js} +77 -68
  21. package/dist/chunk-2BQYEBCK.js.map +1 -0
  22. package/dist/{chunk-FRBYJANT.js → chunk-4W447H7M.js} +195 -41
  23. package/dist/chunk-4W447H7M.js.map +1 -0
  24. package/dist/chunk-5ODOE6EF.js +0 -0
  25. package/dist/chunk-7DHGV77J.js +140 -0
  26. package/dist/chunk-7DHGV77J.js.map +1 -0
  27. package/dist/{chunk-WQCPKA2L.js → chunk-C27AZN46.js} +570 -165
  28. package/dist/chunk-C27AZN46.js.map +1 -0
  29. package/dist/chunk-ERAZNHHR.js +73 -0
  30. package/dist/chunk-ERAZNHHR.js.map +1 -0
  31. package/dist/chunk-F3TG5FJV.js +97 -0
  32. package/dist/chunk-F3TG5FJV.js.map +1 -0
  33. package/dist/chunk-KJVEJILQ.js +109 -0
  34. package/dist/chunk-KJVEJILQ.js.map +1 -0
  35. package/dist/chunk-KT3MWNZG.js +0 -0
  36. package/dist/{chunk-MCP6Y5BA.js → chunk-M23FP2AS.js} +25 -1
  37. package/dist/chunk-M23FP2AS.js.map +1 -0
  38. package/dist/{chunk-S3JBIFKS.js → chunk-MG7H3LAW.js} +2 -2
  39. package/dist/chunk-MOT42NAH.js +165 -0
  40. package/dist/chunk-MOT42NAH.js.map +1 -0
  41. package/dist/chunk-O7335ZGH.js +0 -0
  42. package/dist/{chunk-OEB3OIYP.js → chunk-OJHO2X7C.js} +497 -168
  43. package/dist/chunk-OJHO2X7C.js.map +1 -0
  44. package/dist/chunk-QLGES2HC.js +189 -0
  45. package/dist/chunk-QLGES2HC.js.map +1 -0
  46. package/dist/{chunk-JZ5WEG3S.js → chunk-SEVYARIU.js} +9 -5
  47. package/dist/chunk-SEVYARIU.js.map +1 -0
  48. package/dist/{chunk-ZK5JCE5J.js → chunk-TFECHXR5.js} +201 -43
  49. package/dist/chunk-TFECHXR5.js.map +1 -0
  50. package/dist/chunk-WSJKACWB.js +17 -0
  51. package/dist/chunk-WSJKACWB.js.map +1 -0
  52. package/dist/chunk-WZ7CPVQB.js +0 -0
  53. package/dist/chunk-XMS5VRIK.js +0 -0
  54. package/dist/chunk-Y5KE6YFP.js +0 -0
  55. package/dist/{chunk-3Q36SEDK.js → chunk-ZBWROIIO.js} +43 -2
  56. package/dist/{chunk-3Q36SEDK.js.map → chunk-ZBWROIIO.js.map} +1 -1
  57. package/dist/chunk-ZOXNJV2O.js +358 -0
  58. package/dist/chunk-ZOXNJV2O.js.map +1 -0
  59. package/dist/cli/index.js +22 -7
  60. package/dist/cli/index.js.map +1 -1
  61. package/dist/client/index.d.ts +31 -1
  62. package/dist/client/index.js +100 -0
  63. package/dist/client/index.js.map +1 -1
  64. package/dist/cloudflare-RBANU3SE.js +0 -0
  65. package/dist/configure-agent-registry-6YGTJYBC.js +0 -0
  66. package/dist/deno-deploy-J7LI6HNL.js +0 -0
  67. package/dist/{dev-VN6WS3CM.js → dev-74KPI5C5.js} +15 -7
  68. package/dist/dev-74KPI5C5.js.map +1 -0
  69. package/dist/dev-emit-3CHM73TV.js +390 -0
  70. package/dist/dev-emit-3CHM73TV.js.map +1 -0
  71. package/dist/dev-emit-I3CKOHML.js +44 -0
  72. package/dist/dev-emit-I3CKOHML.js.map +1 -0
  73. package/dist/devtools/entry.js +653 -243
  74. package/dist/devtools/entry.js.map +1 -1
  75. package/dist/dispatcher-AQJGI3HU.js +8 -0
  76. package/dist/dispatcher-E6QV32BO.js +83 -0
  77. package/dist/dispatcher-E6QV32BO.js.map +1 -0
  78. package/dist/{dist-UZV7J3YI.js → dist-R2Q7GVDW.js} +10412 -11961
  79. package/dist/dist-R2Q7GVDW.js.map +1 -0
  80. package/dist/docker-EZDA5FT7.js +0 -0
  81. package/dist/{generate-SIO4JSKJ.js → generate-DT4IIAHF.js} +88 -22
  82. package/dist/generate-DT4IIAHF.js.map +1 -0
  83. package/dist/index.d.ts +108 -1
  84. package/dist/index.js +5 -2
  85. package/dist/index.js.map +1 -1
  86. package/dist/{info-B3UOXFUF.js → info-VM7EK4JC.js} +3 -3
  87. package/dist/lib-NST3PNZX.js +0 -0
  88. package/dist/netlify-IFY6ZGFV.js +0 -0
  89. package/dist/node-KWZI3CKV.js +0 -0
  90. package/dist/openapi-CBASKC7W.js +84 -0
  91. package/dist/openapi-CBASKC7W.js.map +1 -0
  92. package/dist/proper-lockfile-MVKMQGFK.js +0 -0
  93. package/dist/{registry-BB3UYAOG.js → registry-QGSNLJKJ.js} +2 -2
  94. package/dist/{routes-CAGFZKUK.js → routes-7O4NUDYS.js} +9 -7
  95. package/dist/{routes-CAGFZKUK.js.map → routes-7O4NUDYS.js.map} +1 -1
  96. package/dist/scan-C2BOKLSY.js +0 -0
  97. package/dist/{schema-Cxq2qqwh.d.ts → schema-CjVly9c_.d.ts} +43 -1
  98. package/dist/{schema-MVB2FVK6.js → schema-JAKF23GR.js} +2 -2
  99. package/dist/server/cost/index.d.ts +7 -0
  100. package/dist/server/cost/index.js +1 -1
  101. package/dist/server/index.d.ts +219 -47
  102. package/dist/server/index.js +101 -114
  103. package/dist/server/index.js.map +1 -1
  104. package/dist/services-typed-client-ZX5JUHH3.js +0 -0
  105. package/dist/sqlite-vec-54KB4RD3.js +0 -0
  106. package/dist/{start-CSLJQXV3.js → start-5JTFWF4T.js} +20 -14
  107. package/dist/start-5JTFWF4T.js.map +1 -0
  108. package/dist/{static-BH6JQNM2.js → static-6PZZBY7G.js} +4 -4
  109. package/dist/storage-manager-D5KBXXKB.js +0 -0
  110. package/dist/theo-cloud-3K4DGB3S.js +0 -0
  111. package/dist/{upgrade-readiness-OHL36FQL.js → upgrade-readiness-Q46KTG2J.js} +6 -1
  112. package/dist/upgrade-readiness-Q46KTG2J.js.map +1 -0
  113. package/dist/vercel-RHADE5IP.js +0 -0
  114. package/dist/vite-plugin/index.d.ts +25 -1
  115. package/dist/vite-plugin/index.js +5 -2
  116. package/dist/{vite-plugin-WA3G45YY.js → vite-plugin-3EQP2JYM.js} +7 -6
  117. package/dist/vite-plugin-3EQP2JYM.js.map +1 -0
  118. package/package.json +10 -8
  119. package/LICENSE +0 -201
  120. package/dist/build-IQQYCA3B.js.map +0 -1
  121. package/dist/chunk-FRBYJANT.js.map +0 -1
  122. package/dist/chunk-GQSTRXXB.js.map +0 -1
  123. package/dist/chunk-JZ5WEG3S.js.map +0 -1
  124. package/dist/chunk-MCP6Y5BA.js.map +0 -1
  125. package/dist/chunk-OEB3OIYP.js.map +0 -1
  126. package/dist/chunk-WQCPKA2L.js.map +0 -1
  127. package/dist/chunk-ZK5JCE5J.js.map +0 -1
  128. package/dist/dev-VN6WS3CM.js.map +0 -1
  129. package/dist/dist-UZV7J3YI.js.map +0 -1
  130. package/dist/generate-SIO4JSKJ.js.map +0 -1
  131. package/dist/start-CSLJQXV3.js.map +0 -1
  132. package/dist/upgrade-readiness-OHL36FQL.js.map +0 -1
  133. /package/dist/{chunk-S3JBIFKS.js.map → chunk-MG7H3LAW.js.map} +0 -0
  134. /package/dist/{schema-MVB2FVK6.js.map → dispatcher-AQJGI3HU.js.map} +0 -0
  135. /package/dist/{info-B3UOXFUF.js.map → info-VM7EK4JC.js.map} +0 -0
  136. /package/dist/{registry-BB3UYAOG.js.map → registry-QGSNLJKJ.js.map} +0 -0
  137. /package/dist/{vite-plugin-WA3G45YY.js.map → schema-JAKF23GR.js.map} +0 -0
  138. /package/dist/{static-BH6JQNM2.js.map → static-6PZZBY7G.js.map} +0 -0
File without changes
@@ -9,6 +9,25 @@ var VALID_TYPES = ["route", "action", "page", "ws"];
9
9
  function toKebabCase(name) {
10
10
  return /^[a-z][a-z0-9/-]*$/.test(name);
11
11
  }
12
+ var RESERVED_BASENAMES = /* @__PURE__ */ new Set([
13
+ "index",
14
+ "constructor",
15
+ "__proto__",
16
+ "prototype",
17
+ "hasOwnProperty"
18
+ ]);
19
+ function hasReservedSegment(name) {
20
+ const basename = name.includes("/") ? name.split("/").pop() ?? name : name;
21
+ return RESERVED_BASENAMES.has(basename);
22
+ }
23
+ function isPathInside(cwd, targetSubpath) {
24
+ if (targetSubpath.includes("\0")) return false;
25
+ if (targetSubpath.startsWith("/") || targetSubpath.startsWith("\\")) return false;
26
+ const resolved = resolve(cwd, targetSubpath);
27
+ const cwdResolved = resolve(cwd);
28
+ const sep = process.platform === "win32" ? "\\" : "/";
29
+ return resolved === cwdResolved || resolved.startsWith(cwdResolved + sep);
30
+ }
12
31
  function toPascalCase(name) {
13
32
  return name.split(/[-/]/).map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
14
33
  }
@@ -44,6 +63,51 @@ function generateActionTemplate(name) {
44
63
  ``
45
64
  ].join("\n");
46
65
  }
66
+ function resolveTemplate(cwd, type, name) {
67
+ switch (type) {
68
+ case "route":
69
+ return {
70
+ filePath: resolve(cwd, "server/routes", `${name}.ts`),
71
+ content: generateRouteTemplate(name)
72
+ };
73
+ case "action":
74
+ return {
75
+ filePath: resolve(cwd, "server/actions", `${name}.ts`),
76
+ content: generateActionTemplate(name)
77
+ };
78
+ case "page":
79
+ return { filePath: resolve(cwd, `app/${name}/page.tsx`), content: generatePageTemplate(name) };
80
+ case "ws":
81
+ return {
82
+ filePath: resolve(cwd, "server/ws", `${name}.ts`),
83
+ content: generateWsTemplate(name)
84
+ };
85
+ default:
86
+ return null;
87
+ }
88
+ }
89
+ function generateActionTestTemplate(name) {
90
+ const camel = toCamelCase(name);
91
+ return [
92
+ `import { describe, it, expect } from 'vitest'`,
93
+ ``,
94
+ `import { ${camel} } from './${name.split("/").pop()}.js'`,
95
+ ``,
96
+ `describe('${name} action', () => {`,
97
+ ` it('should accept a valid input shape', () => {`,
98
+ ` expect(${camel}.input).toBeDefined()`,
99
+ ` expect(typeof ${camel}.handler).toBe('function')`,
100
+ ` })`,
101
+ ``,
102
+ ` it('should reject invalid input via zod schema', () => {`,
103
+ ` const parsed = ${camel}.input.safeParse({ __invalid: true })`,
104
+ ` // Empty object schema accepts {}; tighten this assertion when adding fields.`,
105
+ ` expect(parsed.success).toBe(true)`,
106
+ ` })`,
107
+ `})`,
108
+ ``
109
+ ].join("\n");
110
+ }
47
111
  function generatePageTemplate(name) {
48
112
  const pascal = toPascalCase(name);
49
113
  return [`export default function ${pascal}Page() {`, ` return <h1>${pascal}</h1>`, `}`, ``].join(
@@ -82,33 +146,35 @@ async function generate(opts) {
82
146
  message: `Invalid name "${name}". Use kebab-case: lowercase letters, numbers, hyphens.`
83
147
  };
84
148
  }
85
- let filePath;
86
- let content;
87
- switch (type) {
88
- case "route":
89
- filePath = resolve(cwd, "server/routes", `${name}.ts`);
90
- content = generateRouteTemplate(name);
91
- break;
92
- case "action":
93
- filePath = resolve(cwd, "server/actions", `${name}.ts`);
94
- content = generateActionTemplate(name);
95
- break;
96
- case "page":
97
- filePath = resolve(cwd, `app/${name}/page.tsx`);
98
- content = generatePageTemplate(name);
99
- break;
100
- case "ws":
101
- filePath = resolve(cwd, "server/ws", `${name}.ts`);
102
- content = generateWsTemplate(name);
103
- break;
104
- default:
105
- return { status: "invalid_kind", message: `Unknown type: ${type}` };
149
+ if (hasReservedSegment(name)) {
150
+ return {
151
+ status: "invalid_name",
152
+ message: `Reserved name "${name}" \u2014 basename collides with built-in identifier (index/constructor/__proto__/prototype/hasOwnProperty).`
153
+ };
154
+ }
155
+ const resolved = resolveTemplate(cwd, type, name);
156
+ if (resolved === null) {
157
+ return { status: "invalid_kind", message: `Unknown type: ${type}` };
158
+ }
159
+ const { filePath, content } = resolved;
160
+ const relativeFromCwd = filePath.startsWith(resolve(cwd)) ? filePath.slice(resolve(cwd).length + 1) : filePath;
161
+ if (!isPathInside(cwd, relativeFromCwd)) {
162
+ return {
163
+ status: "invalid_name",
164
+ message: `Path traversal denied: "${name}" would escape project root.`
165
+ };
106
166
  }
107
167
  if (existsSync(filePath)) {
108
168
  return { status: "already_exists", filePath, kind: type, name };
109
169
  }
110
170
  mkdirSync(dirname(filePath), { recursive: true });
111
171
  writeFileSync(filePath, content);
172
+ if (type === "action") {
173
+ const testPath = filePath.replace(/\.ts$/, ".test.ts");
174
+ if (!existsSync(testPath)) {
175
+ writeFileSync(testPath, generateActionTestTemplate(name));
176
+ }
177
+ }
112
178
  return { status: "created", filePath, kind: type, name };
113
179
  }
114
180
  async function generateCommand(type, name) {
@@ -139,4 +205,4 @@ export {
139
205
  generate,
140
206
  generateCommand
141
207
  };
142
- //# sourceMappingURL=generate-SIO4JSKJ.js.map
208
+ //# sourceMappingURL=generate-DT4IIAHF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/generate.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * CLI `theo generate`. Writes scaffolded files under `cwd` + a generator\n * name from CLI args. Build-time tool. No HTTP input.\n */\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\n\nexport const VALID_TYPES = ['route', 'action', 'page', 'ws'] as const\nexport type GeneratorType = (typeof VALID_TYPES)[number]\n\nexport interface GenerateOptions {\n cwd: string\n type: string\n name: 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\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 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 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 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 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(type: string, name: string): Promise<void> {\n const result = await generate({ cwd: process.cwd(), type, name })\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"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  export { theoPlugin } from './vite-plugin/index.js';
3
3
  import 'vite';
4
- import './schema-Cxq2qqwh.js';
4
+ import './schema-CjVly9c_.js';
5
5
  import 'node:http';
6
6
  import './rate-limit-store-BEJnhWdw.js';
7
7
 
@@ -412,6 +412,21 @@ declare const theoConfigSchema: z.ZodEffects<z.ZodObject<{
412
412
  * matching `--target`, `theokit build` emits a cross-reference note.
413
413
  */
414
414
  adapters: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
415
+ /**
416
+ * Extra package names to pre-bundle via Vite's `optimizeDeps.include`.
417
+ *
418
+ * Framework auto-includes `@usetheo/ui` and `lucide-react` when present.
419
+ * Apps adding plugin peer-deps that rely on a runtime `import('<pkg>')`
420
+ * (dynamic specifier) — e.g. `@theokit/plugin-canvas` consumers
421
+ * installing `mermaid` for Mermaid SVG rendering — must declare those
422
+ * here so Vite can resolve the literal in dev mode without
423
+ * "Failed to resolve module specifier" errors. Production builds use
424
+ * tsup/esbuild bundling and are not affected.
425
+ *
426
+ * Example:
427
+ * viteOptimizeDeps: ['mermaid']
428
+ */
429
+ viteOptimizeDeps: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
415
430
  /**
416
431
  * Jobs backend (ADR D3 + T2.1). When configured, every request gets
417
432
  * `ctx.queue.enqueue` auto-wired via the outbox lifecycle. Pass a
@@ -771,6 +786,54 @@ declare const theoConfigSchema: z.ZodEffects<z.ZodObject<{
771
786
  dependsOn?: string[] | undefined;
772
787
  passSetCookie?: boolean | undefined;
773
788
  }> | undefined>;
789
+ /**
790
+ * G2 — OpenAPI emit (build-time only).
791
+ *
792
+ * When present, `theokit build` writes a generated `openapi.json` from
793
+ * every `defineRoute()` body/query/params Zod schema. `undefined` keeps
794
+ * the framework backward-compatible (no emit). Pass `openapi: {}` to
795
+ * opt in with defaults.
796
+ *
797
+ * Defaults: spec 3.1.0 · servers `http://localhost:3000` · title
798
+ * "TheoKit App" · version "0.0.0" · outDir ".theo" (next to dist).
799
+ *
800
+ * The env var `THEOKIT_OPENAPI_SERVERS` (CSV of URLs) overrides
801
+ * `servers[].url` at emit time without rebuilding the config.
802
+ */
803
+ openapi: z.ZodOptional<z.ZodObject<{
804
+ servers: z.ZodDefault<z.ZodArray<z.ZodObject<{
805
+ url: z.ZodString;
806
+ description: z.ZodOptional<z.ZodString>;
807
+ }, "strip", z.ZodTypeAny, {
808
+ url: string;
809
+ description?: string | undefined;
810
+ }, {
811
+ url: string;
812
+ description?: string | undefined;
813
+ }>, "many">>;
814
+ specVersion: z.ZodDefault<z.ZodEnum<["3.1.0", "3.0.3"]>>;
815
+ title: z.ZodDefault<z.ZodString>;
816
+ version: z.ZodDefault<z.ZodString>;
817
+ outDir: z.ZodDefault<z.ZodString>;
818
+ }, "strip", z.ZodTypeAny, {
819
+ version: string;
820
+ servers: {
821
+ url: string;
822
+ description?: string | undefined;
823
+ }[];
824
+ specVersion: "3.1.0" | "3.0.3";
825
+ title: string;
826
+ outDir: string;
827
+ }, {
828
+ version?: string | undefined;
829
+ servers?: {
830
+ url: string;
831
+ description?: string | undefined;
832
+ }[] | undefined;
833
+ specVersion?: "3.1.0" | "3.0.3" | undefined;
834
+ title?: string | undefined;
835
+ outDir?: string | undefined;
836
+ }>>;
774
837
  }, "strip", z.ZodTypeAny, {
775
838
  port: number;
776
839
  services: Record<string, {
@@ -793,6 +856,16 @@ declare const theoConfigSchema: z.ZodEffects<z.ZodObject<{
793
856
  ssr: boolean;
794
857
  ssrStreaming: boolean;
795
858
  serialization: "json" | "superjson";
859
+ openapi?: {
860
+ version: string;
861
+ servers: {
862
+ url: string;
863
+ description?: string | undefined;
864
+ }[];
865
+ specVersion: "3.1.0" | "3.0.3";
866
+ title: string;
867
+ outDir: string;
868
+ } | undefined;
796
869
  storage?: {
797
870
  redis?: Record<string, {
798
871
  host: string;
@@ -921,11 +994,22 @@ declare const theoConfigSchema: z.ZodEffects<z.ZodObject<{
921
994
  position?: "top-left" | "top-right" | "bottom-left" | "bottom-right" | undefined;
922
995
  } | undefined;
923
996
  adapters?: string[] | undefined;
997
+ viteOptimizeDeps?: string[] | undefined;
924
998
  jobs?: {
925
999
  backend?: unknown;
926
1000
  } | undefined;
927
1001
  }, {
928
1002
  port?: number | undefined;
1003
+ openapi?: {
1004
+ version?: string | undefined;
1005
+ servers?: {
1006
+ url: string;
1007
+ description?: string | undefined;
1008
+ }[] | undefined;
1009
+ specVersion?: "3.1.0" | "3.0.3" | undefined;
1010
+ title?: string | undefined;
1011
+ outDir?: string | undefined;
1012
+ } | undefined;
929
1013
  services?: Record<string, {
930
1014
  runtime: "python" | "node";
931
1015
  port: number;
@@ -1074,6 +1158,7 @@ declare const theoConfigSchema: z.ZodEffects<z.ZodObject<{
1074
1158
  position?: "top-left" | "top-right" | "bottom-left" | "bottom-right" | undefined;
1075
1159
  } | undefined;
1076
1160
  adapters?: string[] | undefined;
1161
+ viteOptimizeDeps?: string[] | undefined;
1077
1162
  jobs?: {
1078
1163
  backend?: unknown;
1079
1164
  } | undefined;
@@ -1099,6 +1184,16 @@ declare const theoConfigSchema: z.ZodEffects<z.ZodObject<{
1099
1184
  ssr: boolean;
1100
1185
  ssrStreaming: boolean;
1101
1186
  serialization: "json" | "superjson";
1187
+ openapi?: {
1188
+ version: string;
1189
+ servers: {
1190
+ url: string;
1191
+ description?: string | undefined;
1192
+ }[];
1193
+ specVersion: "3.1.0" | "3.0.3";
1194
+ title: string;
1195
+ outDir: string;
1196
+ } | undefined;
1102
1197
  storage?: {
1103
1198
  redis?: Record<string, {
1104
1199
  host: string;
@@ -1227,11 +1322,22 @@ declare const theoConfigSchema: z.ZodEffects<z.ZodObject<{
1227
1322
  position?: "top-left" | "top-right" | "bottom-left" | "bottom-right" | undefined;
1228
1323
  } | undefined;
1229
1324
  adapters?: string[] | undefined;
1325
+ viteOptimizeDeps?: string[] | undefined;
1230
1326
  jobs?: {
1231
1327
  backend?: unknown;
1232
1328
  } | undefined;
1233
1329
  }, {
1234
1330
  port?: number | undefined;
1331
+ openapi?: {
1332
+ version?: string | undefined;
1333
+ servers?: {
1334
+ url: string;
1335
+ description?: string | undefined;
1336
+ }[] | undefined;
1337
+ specVersion?: "3.1.0" | "3.0.3" | undefined;
1338
+ title?: string | undefined;
1339
+ outDir?: string | undefined;
1340
+ } | undefined;
1235
1341
  services?: Record<string, {
1236
1342
  runtime: "python" | "node";
1237
1343
  port: number;
@@ -1380,6 +1486,7 @@ declare const theoConfigSchema: z.ZodEffects<z.ZodObject<{
1380
1486
  position?: "top-left" | "top-right" | "bottom-left" | "bottom-right" | undefined;
1381
1487
  } | undefined;
1382
1488
  adapters?: string[] | undefined;
1489
+ viteOptimizeDeps?: string[] | undefined;
1383
1490
  jobs?: {
1384
1491
  backend?: unknown;
1385
1492
  } | undefined;
package/dist/index.js CHANGED
@@ -8,13 +8,16 @@ import {
8
8
  scanRoutes,
9
9
  theoConfigSchema,
10
10
  theoPlugin
11
- } from "./chunk-FRBYJANT.js";
11
+ } from "./chunk-4W447H7M.js";
12
12
  import "./chunk-7BIM27LV.js";
13
13
  import "./chunk-O4BLVPML.js";
14
- import "./chunk-OEB3OIYP.js";
14
+ import "./chunk-OJHO2X7C.js";
15
+ import "./chunk-2BQYEBCK.js";
16
+ import "./chunk-7DHGV77J.js";
15
17
  import "./chunk-PB4GR7EP.js";
16
18
  import "./chunk-GY5Q27BJ.js";
17
19
  import "./chunk-X2VVCJ4V.js";
20
+ import "./chunk-WSJKACWB.js";
18
21
  import "./chunk-DGUM43GV.js";
19
22
 
20
23
  // src/config/define-config.ts
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config/define-config.ts","../src/core/validate-structure.ts","../src/core/errors.ts"],"sourcesContent":["import type { TheoConfig } from './schema.js'\n\n/**\n * Define Theo framework configuration.\n * Identity function — provides type inference for theo.config.ts.\n * Runtime validation happens in loadConfig(), not here.\n */\nexport function defineConfig(config: Partial<TheoConfig>): Partial<TheoConfig> {\n return config\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Project-structure validator. Reads paths joined onto `projectRoot`,\n * with names from a fixed `ValidationRule[]` table. No HTTP input.\n */\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport { TheoProjectError } from './errors.js'\n\ninterface ValidationRule {\n path: string\n errorMessage: string\n}\n\nconst REQUIRED_DIRS: ValidationRule[] = [\n {\n path: 'app',\n errorMessage: 'Missing required directory: app/',\n },\n]\n\nconst REQUIRED_FILES: ValidationRule[] = [\n {\n path: 'theo.config.ts',\n errorMessage: 'Missing required file: theo.config.ts',\n },\n {\n path: 'package.json',\n errorMessage: 'Missing required file: package.json',\n },\n]\n\nexport function validateProjectStructure(rootDir: string): void {\n if (!existsSync(rootDir)) {\n throw new TheoProjectError([`Project directory does not exist: ${rootDir}`], rootDir)\n }\n\n const errors: string[] = []\n\n for (const rule of REQUIRED_DIRS) {\n if (!existsSync(join(rootDir, rule.path))) {\n errors.push(rule.errorMessage)\n }\n }\n\n for (const rule of REQUIRED_FILES) {\n if (!existsSync(join(rootDir, rule.path))) {\n errors.push(rule.errorMessage)\n }\n }\n\n if (errors.length > 0) {\n throw new TheoProjectError(errors, rootDir)\n }\n}\n","export class TheoProjectError extends Error {\n public readonly errors: string[]\n public readonly rootDir: string\n\n constructor(errors: string[], rootDir: string) {\n const errorLines = errors.map((e) => ` - ${e}`).join('\\n')\n\n super(\n `Invalid Theo project structure\\n\\n` +\n ` Root: ${rootDir}\\n\\n` +\n (errorLines ? ` Errors:\\n${errorLines}\\n` : ''),\n )\n\n this.name = 'TheoProjectError'\n this.errors = errors\n this.rootDir = rootDir\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAOO,SAAS,aAAa,QAAkD;AAC7E,SAAO;AACT;;;ACLA,SAAS,kBAAkB;AAC3B,SAAS,YAAY;;;ACLd,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAC1B;AAAA,EACA;AAAA,EAEhB,YAAY,QAAkB,SAAiB;AAC7C,UAAM,aAAa,OAAO,IAAI,CAAC,MAAM,OAAO,CAAC,EAAE,EAAE,KAAK,IAAI;AAE1D;AAAA,MACE;AAAA;AAAA,UACa,OAAO;AAAA;AAAA,KACjB,aAAa;AAAA,EAAc,UAAU;AAAA,IAAO;AAAA,IACjD;AAEA,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,UAAU;AAAA,EACjB;AACF;;;ADHA,IAAM,gBAAkC;AAAA,EACtC;AAAA,IACE,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AACF;AAEA,IAAM,iBAAmC;AAAA,EACvC;AAAA,IACE,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AACF;AAEO,SAAS,yBAAyB,SAAuB;AAC9D,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,UAAM,IAAI,iBAAiB,CAAC,qCAAqC,OAAO,EAAE,GAAG,OAAO;AAAA,EACtF;AAEA,QAAM,SAAmB,CAAC;AAE1B,aAAW,QAAQ,eAAe;AAChC,QAAI,CAAC,WAAW,KAAK,SAAS,KAAK,IAAI,CAAC,GAAG;AACzC,aAAO,KAAK,KAAK,YAAY;AAAA,IAC/B;AAAA,EACF;AAEA,aAAW,QAAQ,gBAAgB;AACjC,QAAI,CAAC,WAAW,KAAK,SAAS,KAAK,IAAI,CAAC,GAAG;AACzC,aAAO,KAAK,KAAK,YAAY;AAAA,IAC/B;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,GAAG;AACrB,UAAM,IAAI,iBAAiB,QAAQ,OAAO;AAAA,EAC5C;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/config/define-config.ts","../src/core/validate-structure.ts","../src/core/errors.ts"],"sourcesContent":["import type { TheoConfig } from './schema.js'\n\n/**\n * Define Theo framework configuration.\n * Identity function — provides type inference for theo.config.ts.\n * Runtime validation happens in loadConfig(), not here.\n */\nexport function defineConfig(config: Partial<TheoConfig>): Partial<TheoConfig> {\n return config\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Project-structure validator. Reads paths joined onto `projectRoot`,\n * with names from a fixed `ValidationRule[]` table. No HTTP input.\n */\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport { TheoProjectError } from './errors.js'\n\ninterface ValidationRule {\n path: string\n errorMessage: string\n}\n\nconst REQUIRED_DIRS: ValidationRule[] = [\n {\n path: 'app',\n errorMessage: 'Missing required directory: app/',\n },\n]\n\nconst REQUIRED_FILES: ValidationRule[] = [\n {\n path: 'theo.config.ts',\n errorMessage: 'Missing required file: theo.config.ts',\n },\n {\n path: 'package.json',\n errorMessage: 'Missing required file: package.json',\n },\n]\n\nexport function validateProjectStructure(rootDir: string): void {\n if (!existsSync(rootDir)) {\n throw new TheoProjectError([`Project directory does not exist: ${rootDir}`], rootDir)\n }\n\n const errors: string[] = []\n\n for (const rule of REQUIRED_DIRS) {\n if (!existsSync(join(rootDir, rule.path))) {\n errors.push(rule.errorMessage)\n }\n }\n\n for (const rule of REQUIRED_FILES) {\n if (!existsSync(join(rootDir, rule.path))) {\n errors.push(rule.errorMessage)\n }\n }\n\n if (errors.length > 0) {\n throw new TheoProjectError(errors, rootDir)\n }\n}\n","export class TheoProjectError extends Error {\n public readonly errors: string[]\n public readonly rootDir: string\n\n constructor(errors: string[], rootDir: string) {\n const errorLines = errors.map((e) => ` - ${e}`).join('\\n')\n\n super(\n `Invalid Theo project structure\\n\\n` +\n ` Root: ${rootDir}\\n\\n` +\n (errorLines ? ` Errors:\\n${errorLines}\\n` : ''),\n )\n\n this.name = 'TheoProjectError'\n this.errors = errors\n this.rootDir = rootDir\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAOO,SAAS,aAAa,QAAkD;AAC7E,SAAO;AACT;;;ACLA,SAAS,kBAAkB;AAC3B,SAAS,YAAY;;;ACLd,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAC1B;AAAA,EACA;AAAA,EAEhB,YAAY,QAAkB,SAAiB;AAC7C,UAAM,aAAa,OAAO,IAAI,CAAC,MAAM,OAAO,CAAC,EAAE,EAAE,KAAK,IAAI;AAE1D;AAAA,MACE;AAAA;AAAA,UACa,OAAO;AAAA;AAAA,KACjB,aAAa;AAAA,EAAc,UAAU;AAAA,IAAO;AAAA,IACjD;AAEA,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,UAAU;AAAA,EACjB;AACF;;;ADHA,IAAM,gBAAkC;AAAA,EACtC;AAAA,IACE,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AACF;AAEA,IAAM,iBAAmC;AAAA,EACvC;AAAA,IACE,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AACF;AAEO,SAAS,yBAAyB,SAAuB;AAC9D,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,UAAM,IAAI,iBAAiB,CAAC,qCAAqC,OAAO,EAAE,GAAG,OAAO;AAAA,EACtF;AAEA,QAAM,SAAmB,CAAC;AAE1B,aAAW,QAAQ,eAAe;AAChC,QAAI,CAAC,WAAW,KAAK,SAAS,KAAK,IAAI,CAAC,GAAG;AACzC,aAAO,KAAK,KAAK,YAAY;AAAA,IAC/B;AAAA,EACF;AAEA,aAAW,QAAQ,gBAAgB;AACjC,QAAI,CAAC,WAAW,KAAK,SAAS,KAAK,IAAI,CAAC,GAAG;AACzC,aAAO,KAAK,KAAK,YAAY;AAAA,IAC/B;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,GAAG;AACrB,UAAM,IAAI,iBAAiB,QAAQ,OAAO;AAAA,EAC5C;AACF;","names":[]}
@@ -2,8 +2,8 @@
2
2
  import "tsx/esm";
3
3
  import {
4
4
  loadConfig
5
- } from "./chunk-S3JBIFKS.js";
6
- import "./chunk-3Q36SEDK.js";
5
+ } from "./chunk-MG7H3LAW.js";
6
+ import "./chunk-ZBWROIIO.js";
7
7
  import {
8
8
  scanRoutes
9
9
  } from "./chunk-O7335ZGH.js";
@@ -101,4 +101,4 @@ export {
101
101
  buildInfo,
102
102
  infoCommand
103
103
  };
104
- //# sourceMappingURL=info-B3UOXFUF.js.map
104
+ //# sourceMappingURL=info-VM7EK4JC.js.map
File without changes
File without changes
File without changes
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ import "tsx/esm";
3
+ import {
4
+ emitOpenApi,
5
+ loadRoutesForOpenApi
6
+ } from "./chunk-ZOXNJV2O.js";
7
+ import {
8
+ loadConfig
9
+ } from "./chunk-MG7H3LAW.js";
10
+ import "./chunk-ZBWROIIO.js";
11
+ import "./chunk-WZ7CPVQB.js";
12
+ import {
13
+ generateManifest
14
+ } from "./chunk-SEVYARIU.js";
15
+ import "./chunk-QLGES2HC.js";
16
+ import "./chunk-MOT42NAH.js";
17
+ import "./chunk-KT3MWNZG.js";
18
+
19
+ // src/cli/commands/openapi.ts
20
+ import { mkdtempSync, rmSync } from "fs";
21
+ import { tmpdir } from "os";
22
+ import { join, resolve } from "path";
23
+ var MIGRATION_GUIDE_URL = "docs/concepts/openapi.md";
24
+ async function openapiCommand(options = {}) {
25
+ const cwd = options.cwd ?? process.cwd();
26
+ const config = await loadConfig(cwd);
27
+ if (config.openapi === void 0) {
28
+ console.error(
29
+ `
30
+ \u2717 openapi: not configured in theo.config.ts
31
+
32
+ Add the \`openapi\` block to opt in:
33
+
34
+ export default defineConfig({
35
+ openapi: {
36
+ title: 'My App',
37
+ version: '1.0.0',
38
+ },
39
+ })
40
+
41
+ See ${MIGRATION_GUIDE_URL} for the full reference.
42
+ `
43
+ );
44
+ process.exit(1);
45
+ return;
46
+ }
47
+ const serverDir = resolve(cwd, config.serverDir);
48
+ const manifest = generateManifest(serverDir);
49
+ const hydrated = await loadRoutesForOpenApi({ serverDir, routes: manifest.routes });
50
+ if (options.dryRun) {
51
+ const result2 = emitOpenApiInMemory(hydrated, config.openapi);
52
+ console.log(`
53
+ \u2713 openapi.json (dry-run): ${String(hydrated.length)} ops`);
54
+ console.log(JSON.stringify(result2.document, null, 2));
55
+ console.log(`
56
+ Docs: ${MIGRATION_GUIDE_URL}
57
+ `);
58
+ return;
59
+ }
60
+ const distDir = resolve(cwd, config.distDir);
61
+ const result = emitOpenApi({
62
+ manifest: hydrated,
63
+ config: { ...config.openapi, outDir: distDir }
64
+ });
65
+ console.log(
66
+ `
67
+ \u2713 openapi.json: ${String(hydrated.length)} ops \u2192 ${result.path}
68
+ Docs: ${MIGRATION_GUIDE_URL}
69
+ `
70
+ );
71
+ }
72
+ function emitOpenApiInMemory(manifest, config) {
73
+ const tmp = mkdtempSync(join(tmpdir(), "theokit-openapi-dryrun-"));
74
+ try {
75
+ const result = emitOpenApi({ manifest, config: { ...config, outDir: tmp } });
76
+ return { document: result.document };
77
+ } finally {
78
+ rmSync(tmp, { recursive: true, force: true });
79
+ }
80
+ }
81
+ export {
82
+ openapiCommand
83
+ };
84
+ //# sourceMappingURL=openapi-CBASKC7W.js.map
@@ -0,0 +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":";;;;;;;;;;;;;;;;;;;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"]}
File without changes
@@ -7,7 +7,7 @@ var adapterRegistry = {
7
7
  node: async () => (await import("./node-KWZI3CKV.js")).nodeAdapter,
8
8
  vercel: async () => (await import("./vercel-RHADE5IP.js")).vercelAdapter,
9
9
  cloudflare: async () => (await import("./cloudflare-RBANU3SE.js")).cloudflareAdapter,
10
- static: async () => (await import("./static-BH6JQNM2.js")).staticAdapter,
10
+ static: async () => (await import("./static-6PZZBY7G.js")).staticAdapter,
11
11
  bun: async () => (await import("./bun-DITMZMKL.js")).bunAdapter,
12
12
  "deno-deploy": async () => (await import("./deno-deploy-J7LI6HNL.js")).denoDeployAdapter,
13
13
  netlify: async () => (await import("./netlify-IFY6ZGFV.js")).netlifyAdapter,
@@ -22,4 +22,4 @@ export {
22
22
  adapterRegistry,
23
23
  resolveAdapter
24
24
  };
25
- //# sourceMappingURL=registry-BB3UYAOG.js.map
25
+ //# sourceMappingURL=registry-QGSNLJKJ.js.map
@@ -4,15 +4,17 @@ import {
4
4
  validateProjectStructure
5
5
  } from "./chunk-XMS5VRIK.js";
6
6
  import {
7
- scanServerActions,
7
+ loadConfig
8
+ } from "./chunk-MG7H3LAW.js";
9
+ import "./chunk-ZBWROIIO.js";
10
+ import "./chunk-WZ7CPVQB.js";
11
+ import {
8
12
  scanServerRoutes,
9
13
  scanWebSocketRoutes
10
- } from "./chunk-GQSTRXXB.js";
14
+ } from "./chunk-QLGES2HC.js";
11
15
  import {
12
- loadConfig
13
- } from "./chunk-S3JBIFKS.js";
14
- import "./chunk-3Q36SEDK.js";
15
- import "./chunk-WZ7CPVQB.js";
16
+ scanServerActions
17
+ } from "./chunk-MOT42NAH.js";
16
18
  import "./chunk-KT3MWNZG.js";
17
19
 
18
20
  // src/cli/commands/routes.ts
@@ -61,4 +63,4 @@ async function routesCommand() {
61
63
  export {
62
64
  routesCommand
63
65
  };
64
- //# sourceMappingURL=routes-CAGFZKUK.js.map
66
+ //# sourceMappingURL=routes-7O4NUDYS.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 '../../core/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":[]}
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 '../../core/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":[]}
File without changes
@@ -32,6 +32,48 @@ declare function createRateLimiter(config: RateLimitConfig, opts?: {
32
32
  store?: RateLimitStore;
33
33
  }): (req: IncomingMessage) => RateLimitResult;
34
34
 
35
+ /**
36
+ * T2.2 — In-memory bounded counter for CSRF warn events.
37
+ *
38
+ * Aggregates `csrf.warn` payloads by `(method, path, reason)` triple.
39
+ * Used by the `/__theo/csrf-readiness` endpoint to expose a summary the
40
+ * developer (or devtools tab) can inspect, so users do NOT need to grep
41
+ * stdout to know which endpoints will break under 0.3.0 strict CSRF.
42
+ *
43
+ * Bounded at 1000 distinct keys (EC-22). Eviction is LRU-by-insertion —
44
+ * when the cap is hit, the oldest key is dropped and a re-record of an
45
+ * evicted key starts fresh (count: 1).
46
+ *
47
+ * Single-threaded by virtue of the JS event loop; Map operations are
48
+ * atomic. NOT shared across processes — each worker has its own store.
49
+ */
50
+ interface CsrfWarnRecord {
51
+ method: string;
52
+ path: string;
53
+ reason: string;
54
+ }
55
+ interface CsrfReadinessRouteSummary {
56
+ method: string;
57
+ path: string;
58
+ reason: string;
59
+ count: number;
60
+ firstSeen: string;
61
+ lastSeen: string;
62
+ }
63
+ interface CsrfReadinessSummary {
64
+ generatedAt: string;
65
+ totalEvents: number;
66
+ routes: CsrfReadinessRouteSummary[];
67
+ }
68
+ declare class CsrfReadinessStore {
69
+ static readonly MAX_ENTRIES = 1000;
70
+ private readonly entries;
71
+ private keyFor;
72
+ record(event: CsrfWarnRecord): void;
73
+ summary(): CsrfReadinessSummary;
74
+ reset(): void;
75
+ }
76
+
35
77
  /**
36
78
  * Wave 2 — Polyglot Services Orchestration (T1.1).
37
79
  *
@@ -229,4 +271,4 @@ declare const servicesConfigSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Zod
229
271
  type ServicesConfigInput = z.input<typeof servicesConfigSchema>;
230
272
  type ServicesConfigOutput = z.output<typeof servicesConfigSchema>;
231
273
 
232
- export { type RateLimitConfig as R, type ServicesConfig as S, type RateLimitResult as a, type ServiceDefinition as b, type ServicesConfigInput as c, type ServicesConfigOutput as d, createRateLimiter as e };
274
+ export { CsrfReadinessStore as C, type RateLimitConfig as R, type ServicesConfig as S, type RateLimitResult as a, type CsrfReadinessRouteSummary as b, type CsrfReadinessSummary as c, type CsrfWarnRecord as d, type ServiceDefinition as e, type ServicesConfigInput as f, type ServicesConfigOutput as g, createRateLimiter as h };
@@ -11,7 +11,7 @@ import {
11
11
  storageSchema,
12
12
  theoConfigSchema,
13
13
  uploadSchema
14
- } from "./chunk-3Q36SEDK.js";
14
+ } from "./chunk-ZBWROIIO.js";
15
15
  import "./chunk-WZ7CPVQB.js";
16
16
  import "./chunk-KT3MWNZG.js";
17
17
  export {
@@ -26,4 +26,4 @@ export {
26
26
  theoConfigSchema,
27
27
  uploadSchema
28
28
  };
29
- //# sourceMappingURL=schema-MVB2FVK6.js.map
29
+ //# sourceMappingURL=schema-JAKF23GR.js.map