theokit 0.2.0 → 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.
- package/dist/actions-virtual-module-E6PU47GV.js +175 -0
- package/dist/actions-virtual-module-E6PU47GV.js.map +1 -0
- package/dist/actions-virtual-module-E7IELVHL.js +174 -0
- package/dist/actions-virtual-module-E7IELVHL.js.map +1 -0
- package/dist/app-typed-client-4L4L4BWD.js +289 -0
- package/dist/app-typed-client-4L4L4BWD.js.map +1 -0
- package/dist/app-typed-client-6PAT6VOL.js +289 -0
- package/dist/app-typed-client-6PAT6VOL.js.map +1 -0
- package/dist/{build-IQQYCA3B.js → build-EWZDEDT3.js} +37 -11
- package/dist/build-EWZDEDT3.js.map +1 -0
- package/dist/build-request-body-preview-EOP2Y4F3.js +52 -0
- package/dist/build-request-body-preview-EOP2Y4F3.js.map +1 -0
- package/dist/build-request-body-preview-II2235E6.js +54 -0
- package/dist/build-request-body-preview-II2235E6.js.map +1 -0
- package/dist/{chunk-GQSTRXXB.js → chunk-2BQYEBCK.js} +77 -68
- package/dist/chunk-2BQYEBCK.js.map +1 -0
- package/dist/{chunk-FRBYJANT.js → chunk-4W447H7M.js} +195 -41
- package/dist/chunk-4W447H7M.js.map +1 -0
- package/dist/chunk-7DHGV77J.js +140 -0
- package/dist/chunk-7DHGV77J.js.map +1 -0
- package/dist/{chunk-WQCPKA2L.js → chunk-C27AZN46.js} +570 -165
- package/dist/chunk-C27AZN46.js.map +1 -0
- package/dist/chunk-ERAZNHHR.js +73 -0
- package/dist/chunk-ERAZNHHR.js.map +1 -0
- package/dist/chunk-F3TG5FJV.js +97 -0
- package/dist/chunk-F3TG5FJV.js.map +1 -0
- package/dist/{chunk-OK2PJOLK.js → chunk-KJVEJILQ.js} +7 -3
- package/dist/chunk-KJVEJILQ.js.map +1 -0
- package/dist/{chunk-JBC7PFV4.js → chunk-M23FP2AS.js} +2 -2
- package/dist/chunk-M23FP2AS.js.map +1 -0
- package/dist/{chunk-S3JBIFKS.js → chunk-MG7H3LAW.js} +2 -2
- package/dist/chunk-MOT42NAH.js +165 -0
- package/dist/chunk-MOT42NAH.js.map +1 -0
- package/dist/{chunk-OEB3OIYP.js → chunk-OJHO2X7C.js} +497 -168
- package/dist/chunk-OJHO2X7C.js.map +1 -0
- package/dist/chunk-QLGES2HC.js +189 -0
- package/dist/chunk-QLGES2HC.js.map +1 -0
- package/dist/{chunk-JZ5WEG3S.js → chunk-SEVYARIU.js} +9 -5
- package/dist/chunk-SEVYARIU.js.map +1 -0
- package/dist/{chunk-ZK5JCE5J.js → chunk-TFECHXR5.js} +201 -43
- package/dist/chunk-TFECHXR5.js.map +1 -0
- package/dist/chunk-WSJKACWB.js +17 -0
- package/dist/chunk-WSJKACWB.js.map +1 -0
- package/dist/{chunk-3Q36SEDK.js → chunk-ZBWROIIO.js} +43 -2
- package/dist/{chunk-3Q36SEDK.js.map → chunk-ZBWROIIO.js.map} +1 -1
- package/dist/chunk-ZOXNJV2O.js +358 -0
- package/dist/chunk-ZOXNJV2O.js.map +1 -0
- package/dist/cli/index.js +22 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.d.ts +31 -1
- package/dist/client/index.js +100 -0
- package/dist/client/index.js.map +1 -1
- package/dist/{dev-VN6WS3CM.js → dev-74KPI5C5.js} +15 -7
- package/dist/dev-74KPI5C5.js.map +1 -0
- package/dist/dev-emit-3CHM73TV.js +390 -0
- package/dist/dev-emit-3CHM73TV.js.map +1 -0
- package/dist/dev-emit-I3CKOHML.js +44 -0
- package/dist/dev-emit-I3CKOHML.js.map +1 -0
- package/dist/devtools/entry.js +545 -181
- package/dist/devtools/entry.js.map +1 -1
- package/dist/{dispatcher-IK3FVMX5.js → dispatcher-AQJGI3HU.js} +2 -2
- package/dist/dispatcher-E6QV32BO.js +83 -0
- package/dist/dispatcher-E6QV32BO.js.map +1 -0
- package/dist/{dist-Q5VBFWVL.js → dist-R2Q7GVDW.js} +10357 -12068
- package/dist/dist-R2Q7GVDW.js.map +1 -0
- package/dist/{generate-SIO4JSKJ.js → generate-DT4IIAHF.js} +88 -22
- package/dist/generate-DT4IIAHF.js.map +1 -0
- package/dist/index.d.ts +108 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/{info-B3UOXFUF.js → info-VM7EK4JC.js} +3 -3
- package/dist/openapi-CBASKC7W.js +84 -0
- package/dist/openapi-CBASKC7W.js.map +1 -0
- package/dist/{registry-BB3UYAOG.js → registry-QGSNLJKJ.js} +2 -2
- package/dist/{routes-CAGFZKUK.js → routes-7O4NUDYS.js} +9 -7
- package/dist/{routes-CAGFZKUK.js.map → routes-7O4NUDYS.js.map} +1 -1
- package/dist/{schema-Cxq2qqwh.d.ts → schema-CjVly9c_.d.ts} +43 -1
- package/dist/{schema-MVB2FVK6.js → schema-JAKF23GR.js} +2 -2
- package/dist/server/cost/index.js +1 -1
- package/dist/server/index.d.ts +219 -47
- package/dist/server/index.js +35 -116
- package/dist/server/index.js.map +1 -1
- package/dist/{start-ZGWW5MOL.js → start-5JTFWF4T.js} +20 -14
- package/dist/start-5JTFWF4T.js.map +1 -0
- package/dist/{static-BH6JQNM2.js → static-6PZZBY7G.js} +4 -4
- package/dist/{upgrade-readiness-OHL36FQL.js → upgrade-readiness-Q46KTG2J.js} +6 -1
- package/dist/upgrade-readiness-Q46KTG2J.js.map +1 -0
- package/dist/vite-plugin/index.d.ts +25 -1
- package/dist/vite-plugin/index.js +5 -2
- package/dist/{vite-plugin-WA3G45YY.js → vite-plugin-3EQP2JYM.js} +7 -6
- package/package.json +4 -2
- package/dist/build-IQQYCA3B.js.map +0 -1
- package/dist/chunk-FRBYJANT.js.map +0 -1
- package/dist/chunk-GQSTRXXB.js.map +0 -1
- package/dist/chunk-JBC7PFV4.js.map +0 -1
- package/dist/chunk-JZ5WEG3S.js.map +0 -1
- package/dist/chunk-OEB3OIYP.js.map +0 -1
- package/dist/chunk-OK2PJOLK.js.map +0 -1
- package/dist/chunk-WQCPKA2L.js.map +0 -1
- package/dist/chunk-ZK5JCE5J.js.map +0 -1
- package/dist/dev-VN6WS3CM.js.map +0 -1
- package/dist/dist-Q5VBFWVL.js.map +0 -1
- package/dist/generate-SIO4JSKJ.js.map +0 -1
- package/dist/start-ZGWW5MOL.js.map +0 -1
- package/dist/upgrade-readiness-OHL36FQL.js.map +0 -1
- /package/dist/{chunk-S3JBIFKS.js.map → chunk-MG7H3LAW.js.map} +0 -0
- /package/dist/{dispatcher-IK3FVMX5.js.map → dispatcher-AQJGI3HU.js.map} +0 -0
- /package/dist/{info-B3UOXFUF.js.map → info-VM7EK4JC.js.map} +0 -0
- /package/dist/{registry-BB3UYAOG.js.map → registry-QGSNLJKJ.js.map} +0 -0
- /package/dist/{schema-MVB2FVK6.js.map → schema-JAKF23GR.js.map} +0 -0
- /package/dist/{static-BH6JQNM2.js.map → static-6PZZBY7G.js.map} +0 -0
- /package/dist/{vite-plugin-WA3G45YY.js.map → vite-plugin-3EQP2JYM.js.map} +0 -0
|
@@ -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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
11
|
+
} from "./chunk-4W447H7M.js";
|
|
12
12
|
import "./chunk-7BIM27LV.js";
|
|
13
13
|
import "./chunk-O4BLVPML.js";
|
|
14
|
-
import "./chunk-
|
|
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":"
|
|
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-
|
|
6
|
-
import "./chunk-
|
|
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-
|
|
104
|
+
//# sourceMappingURL=info-VM7EK4JC.js.map
|
|
@@ -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"]}
|
|
@@ -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-
|
|
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-
|
|
25
|
+
//# sourceMappingURL=registry-QGSNLJKJ.js.map
|
|
@@ -4,15 +4,17 @@ import {
|
|
|
4
4
|
validateProjectStructure
|
|
5
5
|
} from "./chunk-XMS5VRIK.js";
|
|
6
6
|
import {
|
|
7
|
-
|
|
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-
|
|
14
|
+
} from "./chunk-QLGES2HC.js";
|
|
11
15
|
import {
|
|
12
|
-
|
|
13
|
-
} from "./chunk-
|
|
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-
|
|
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":"
|
|
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":[]}
|
|
@@ -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
|
|
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-
|
|
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-
|
|
29
|
+
//# sourceMappingURL=schema-JAKF23GR.js.map
|