theokit 0.19.3 → 0.19.4
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/{agent-LFS3MXL7.js → agent-76WK2CZJ.js} +2 -2
- package/dist/{agents-typed-client-DPFVM2OY.js → agents-typed-client-DCPSXUU5.js} +5 -2
- package/dist/{agents-typed-client-DPFVM2OY.js.map → agents-typed-client-DCPSXUU5.js.map} +1 -1
- package/dist/{agents-typed-client-2K3UAOUF.js → agents-typed-client-RIJVHSX4.js} +5 -2
- package/dist/{agents-typed-client-2K3UAOUF.js.map → agents-typed-client-RIJVHSX4.js.map} +1 -1
- package/dist/{build-B675HLFR.js → build-6UDZJIHD.js} +16 -6
- package/dist/build-6UDZJIHD.js.map +1 -0
- package/dist/{chunk-5YBUPJC2.js → chunk-23T4CO4M.js} +2 -2
- package/dist/{chunk-UGK6KR4D.js → chunk-3WOODHXK.js} +7 -5
- package/dist/chunk-3WOODHXK.js.map +1 -0
- package/dist/{chunk-QSLNS5EZ.js → chunk-7PFHQ56Q.js} +7 -5
- package/dist/chunk-7PFHQ56Q.js.map +1 -0
- package/dist/{chunk-JQSFBJR5.js → chunk-GEQJAFSV.js} +6 -9
- package/dist/chunk-GEQJAFSV.js.map +1 -0
- package/dist/cli/index.js +5 -5
- package/dist/{dev-ORWXOTNX.js → dev-NHHTXOA4.js} +6 -4
- package/dist/dev-NHHTXOA4.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -9
- package/dist/index.js.map +1 -1
- package/dist/{mcp-MQTG6IS4.js → mcp-HP5RVOOZ.js} +2 -2
- package/dist/{registry-XJUYD2OU.js → registry-XSRSTH33.js} +2 -2
- package/dist/{routes-TJQIXCOS.js → routes-XNS3UM3B.js} +3 -3
- package/dist/routes-XNS3UM3B.js.map +1 -0
- package/dist/{static-7ARBVDJF.js → static-CYG2WGYP.js} +2 -2
- package/dist/static-CYG2WGYP.js.map +1 -0
- package/dist/vite-plugin/index.d.ts +6 -0
- package/dist/vite-plugin/index.js +1 -1
- package/dist/{vite-plugin-WIJ5RMQQ.js → vite-plugin-3UDOJGO5.js} +2 -2
- package/package.json +1 -1
- package/dist/build-B675HLFR.js.map +0 -1
- package/dist/chunk-JQSFBJR5.js.map +0 -1
- package/dist/chunk-QSLNS5EZ.js.map +0 -1
- package/dist/chunk-UGK6KR4D.js.map +0 -1
- package/dist/dev-ORWXOTNX.js.map +0 -1
- package/dist/routes-TJQIXCOS.js.map +0 -1
- package/dist/static-7ARBVDJF.js.map +0 -1
- /package/dist/{agent-LFS3MXL7.js.map → agent-76WK2CZJ.js.map} +0 -0
- /package/dist/{chunk-5YBUPJC2.js.map → chunk-23T4CO4M.js.map} +0 -0
- /package/dist/{mcp-MQTG6IS4.js.map → mcp-HP5RVOOZ.js.map} +0 -0
- /package/dist/{registry-XJUYD2OU.js.map → registry-XSRSTH33.js.map} +0 -0
- /package/dist/{vite-plugin-WIJ5RMQQ.js.map → vite-plugin-3UDOJGO5.js.map} +0 -0
|
@@ -27,12 +27,6 @@ ${errorLines}
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
// src/config/validate-structure.ts
|
|
30
|
-
var REQUIRED_DIRS = [
|
|
31
|
-
{
|
|
32
|
-
path: "app",
|
|
33
|
-
errorMessage: "Missing required directory: app/"
|
|
34
|
-
}
|
|
35
|
-
];
|
|
36
30
|
var REQUIRED_FILES = [
|
|
37
31
|
{
|
|
38
32
|
path: "theo.config.ts",
|
|
@@ -43,12 +37,15 @@ var REQUIRED_FILES = [
|
|
|
43
37
|
errorMessage: "Missing required file: package.json"
|
|
44
38
|
}
|
|
45
39
|
];
|
|
46
|
-
function validateProjectStructure(rootDir) {
|
|
40
|
+
function validateProjectStructure(rootDir, appDir = "app") {
|
|
47
41
|
if (!existsSync(rootDir)) {
|
|
48
42
|
throw new TheoProjectError([`Project directory does not exist: ${rootDir}`], rootDir);
|
|
49
43
|
}
|
|
50
44
|
const errors = [];
|
|
51
|
-
|
|
45
|
+
const requiredDirs = [
|
|
46
|
+
{ path: appDir, errorMessage: `Missing required directory: ${appDir}/` }
|
|
47
|
+
];
|
|
48
|
+
for (const rule of requiredDirs) {
|
|
52
49
|
if (!existsSync(join(rootDir, rule.path))) {
|
|
53
50
|
errors.push(rule.errorMessage);
|
|
54
51
|
}
|
|
@@ -66,4 +63,4 @@ function validateProjectStructure(rootDir) {
|
|
|
66
63
|
export {
|
|
67
64
|
validateProjectStructure
|
|
68
65
|
};
|
|
69
|
-
//# sourceMappingURL=chunk-
|
|
66
|
+
//# sourceMappingURL=chunk-GEQJAFSV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/validate-structure.ts","../src/core/errors.ts"],"sourcesContent":["/* 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 '../core/errors.js'\n\ninterface ValidationRule {\n path: string\n errorMessage: string\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\n// #95 — honor config `appDir` (default \"app\") so a project that groups its frontend under a custom\n// directory (e.g. `apps/web`) is not rejected by a hardcoded `app/` requirement.\nexport function validateProjectStructure(rootDir: string, appDir = 'app'): void {\n if (!existsSync(rootDir)) {\n throw new TheoProjectError([`Project directory does not exist: ${rootDir}`], rootDir)\n }\n\n const errors: string[] = []\n\n const requiredDirs: ValidationRule[] = [\n { path: appDir, errorMessage: `Missing required directory: ${appDir}/` },\n ]\n\n for (const rule of requiredDirs) {\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":";;;;AAIA,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,iBAAmC;AAAA,EACvC;AAAA,IACE,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AACF;AAIO,SAAS,yBAAyB,SAAiB,SAAS,OAAa;AAC9E,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,UAAM,IAAI,iBAAiB,CAAC,qCAAqC,OAAO,EAAE,GAAG,OAAO;AAAA,EACtF;AAEA,QAAM,SAAmB,CAAC;AAE1B,QAAM,eAAiC;AAAA,IACrC,EAAE,MAAM,QAAQ,cAAc,+BAA+B,MAAM,IAAI;AAAA,EACzE;AAEA,aAAW,QAAQ,cAAc;AAC/B,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":[]}
|
package/dist/cli/index.js
CHANGED
|
@@ -5,12 +5,12 @@ import "tsx/esm";
|
|
|
5
5
|
import cac from "cac";
|
|
6
6
|
var cli = cac("theokit");
|
|
7
7
|
cli.command("dev", "Start development server").option("--port <port>", "Port number").action(async (options) => {
|
|
8
|
-
const { devCommand } = await import("../dev-
|
|
8
|
+
const { devCommand } = await import("../dev-NHHTXOA4.js");
|
|
9
9
|
await devCommand({ port: options.port ? Number(options.port) : void 0 });
|
|
10
10
|
});
|
|
11
11
|
cli.command("build", "Build for production").option("--target <target>", "Deploy target (node, vercel, cloudflare)").action(async (options) => {
|
|
12
12
|
try {
|
|
13
|
-
const { buildCommand } = await import("../build-
|
|
13
|
+
const { buildCommand } = await import("../build-6UDZJIHD.js");
|
|
14
14
|
await buildCommand({ target: options.target });
|
|
15
15
|
} catch (err) {
|
|
16
16
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -52,7 +52,7 @@ cli.command(
|
|
|
52
52
|
"Run an agent in the terminal (stream + tool calls + approval)"
|
|
53
53
|
).action(async (name, message) => {
|
|
54
54
|
try {
|
|
55
|
-
const { agentCommand } = await import("../agent-
|
|
55
|
+
const { agentCommand } = await import("../agent-76WK2CZJ.js");
|
|
56
56
|
const { sawError } = await agentCommand(name, message);
|
|
57
57
|
if (sawError) process.exit(1);
|
|
58
58
|
} catch (err) {
|
|
@@ -65,7 +65,7 @@ cli.command(
|
|
|
65
65
|
});
|
|
66
66
|
cli.command("mcp <agent>", "Serve an agent as an MCP server over stdio (for desktop MCP clients)").action(async (agent) => {
|
|
67
67
|
try {
|
|
68
|
-
const { mcpCommand } = await import("../mcp-
|
|
68
|
+
const { mcpCommand } = await import("../mcp-HP5RVOOZ.js");
|
|
69
69
|
await mcpCommand(agent);
|
|
70
70
|
} catch (err) {
|
|
71
71
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -77,7 +77,7 @@ cli.command("mcp <agent>", "Serve an agent as an MCP server over stdio (for desk
|
|
|
77
77
|
});
|
|
78
78
|
cli.command("routes", "List all routes, actions, and WebSocket endpoints").action(async () => {
|
|
79
79
|
try {
|
|
80
|
-
const { routesCommand } = await import("../routes-
|
|
80
|
+
const { routesCommand } = await import("../routes-XNS3UM3B.js");
|
|
81
81
|
await routesCommand();
|
|
82
82
|
} catch (err) {
|
|
83
83
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
import "tsx/esm";
|
|
3
3
|
import {
|
|
4
4
|
validateProjectStructure
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-GEQJAFSV.js";
|
|
6
6
|
import {
|
|
7
7
|
preflightNodeAndBindings
|
|
8
8
|
} from "./chunk-HNBWZKIQ.js";
|
|
9
9
|
import {
|
|
10
10
|
theoPluginAsync
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-3WOODHXK.js";
|
|
12
12
|
import "./chunk-DXF64KS5.js";
|
|
13
13
|
import "./chunk-DMC6HYUR.js";
|
|
14
14
|
import "./chunk-M6NMS5LR.js";
|
|
@@ -39,7 +39,7 @@ async function startDevServer(cwd, options) {
|
|
|
39
39
|
preflightNodeAndBindings(cwd);
|
|
40
40
|
loadEnv({ cwd, mode: "development" });
|
|
41
41
|
const config = await loadConfig(cwd);
|
|
42
|
-
validateProjectStructure(cwd);
|
|
42
|
+
validateProjectStructure(cwd, config.appDir);
|
|
43
43
|
const orchestration = await orchestrateDev({
|
|
44
44
|
cwd,
|
|
45
45
|
services: config.services
|
|
@@ -62,6 +62,8 @@ async function startDevServer(cwd, options) {
|
|
|
62
62
|
serverDir: config.serverDir,
|
|
63
63
|
// #95 follow-up — honor config `agentsDir` (default "agents") for the agent scan/middleware.
|
|
64
64
|
agentsDir: config.agentsDir,
|
|
65
|
+
// #95 follow-up — honor config `appDir` (default "app") for the file-based router + SSR/client entry.
|
|
66
|
+
appDir: config.appDir,
|
|
65
67
|
// Wave 2 (T3.1) — wire typed-client plugin when services declared.
|
|
66
68
|
services: config.services,
|
|
67
69
|
// T4.1 (canvas-ecosystem-refactor / ADR D6) — passthrough peer-deps
|
|
@@ -109,4 +111,4 @@ export {
|
|
|
109
111
|
devCommand,
|
|
110
112
|
startDevServer
|
|
111
113
|
};
|
|
112
|
-
//# sourceMappingURL=dev-
|
|
114
|
+
//# sourceMappingURL=dev-NHHTXOA4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/dev.ts"],"sourcesContent":["import react from '@vitejs/plugin-react'\nimport { createServer, type Plugin, type ViteDevServer } from 'vite'\n\nimport { loadConfig } from '../../config/load-config.js'\nimport { loadEnv } from '../../config/load-env.js'\nimport { validateProjectStructure } from '../../config/validate-structure.js'\nimport { orchestrateDev } from '../../services/index.js'\nimport { theoPluginAsync } from '../../vite-plugin/index.js'\nimport { preflightNodeAndBindings } from '../preflight-node-version.js'\n\ninterface DevOptions {\n port?: number\n}\n\nexport async function startDevServer(cwd: string, options?: DevOptions): Promise<ViteDevServer> {\n // Preflight (FIRST — BEFORE anything that touches native bindings):\n // verifies Node version + native binding ABI match. Emits actionable\n // error referencing `nvm use` + `pnpm rebuild` when the dev server\n // was spawned under the wrong Node (common gotcha with `nohup env ...`\n // stripping the nvm PATH prefix).\n preflightNodeAndBindings(cwd)\n\n // Phase 1 (T1.2) — Load .env files into process.env BEFORE any module\n // reads them. Must run before loadConfig() so theo.config.ts functions\n // referencing process.env.* resolve correctly.\n loadEnv({ cwd, mode: 'development' })\n\n const config = await loadConfig(cwd)\n // #95 — pass config.appDir so the structure gate honors a custom frontend dir (e.g. apps/web).\n validateProjectStructure(cwd, config.appDir)\n\n // Wave 2 (T1.1) — spawn polyglot services BEFORE Vite. Healthcheck gates\n // readiness. Empty `services: {}` → early return (Wave 1 BC preserved).\n const orchestration = await orchestrateDev({\n cwd,\n services: config.services,\n })\n if (!orchestration.allHealthy) {\n await orchestration.stop()\n throw new Error(\n `[services] services failed healthcheck: ${orchestration.unhealthy.join(', ')}. ` +\n `Check that each declared service binds its port and responds 200 on its ` +\n `healthcheck path within 30s.`,\n )\n }\n\n // Phase 7 — legacy fs-mtime sweep removed: SDK's Agent.registry handles\n // LRU + idle eviction natively (configurable via theo.config.ts >\n // agents.registry; lazy-configured on first request).\n\n const port = options?.port ?? config.port\n // Narrow the rateLimit union: only the legacy flat shape (windowMs+max)\n // flows into the theoPlugin's RateLimitConfig parameter. The per-route\n // variant is consumed inside the api-middleware via the loaded user\n // config separately.\n const flatRateLimit =\n config.rateLimit && 'windowMs' in config.rateLimit && 'max' in config.rateLimit\n ? config.rateLimit\n : undefined\n // theoPluginAsync auto-chains @theokit/ui + @tailwindcss/vite via\n // top-level Plugin[] return (sync `theoPlugin()` factory's config()\n // hook returning {plugins:[...]} is silently dropped by Vite — see\n // commit history). Spread the result so Vite flattens them.\n let server: ViteDevServer\n try {\n const theoPlugins = await theoPluginAsync({\n root: cwd,\n rateLimit: flatRateLimit,\n ssr: config.ssr,\n // #95 — honor config `serverDir` (default \"server\") so route/action discovery scans it.\n serverDir: config.serverDir,\n // #95 follow-up — honor config `agentsDir` (default \"agents\") for the agent scan/middleware.\n agentsDir: config.agentsDir,\n // #95 follow-up — honor config `appDir` (default \"app\") for the file-based router + SSR/client entry.\n appDir: config.appDir,\n // Wave 2 (T3.1) — wire typed-client plugin when services declared.\n services: config.services,\n // T4.1 (canvas-ecosystem-refactor / ADR D6) — passthrough peer-deps\n // that plugins import dynamically (e.g., mermaid via plugin-canvas).\n viteOptimizeDeps: config.viteOptimizeDeps,\n })\n server = await createServer({\n root: cwd,\n plugins: [react(), ...theoPlugins] as Plugin[],\n server: {\n port,\n host: config.host,\n open: config.open,\n strictPort: config.strictPort,\n warmup: config.warmup ?? {\n clientFiles: ['./app/**/*.tsx', './app/**/*.ts'],\n },\n },\n logLevel: options?.port === 0 ? 'silent' : undefined,\n })\n\n await server.listen()\n } catch (err) {\n // EC-1 / try-finally pattern: if Vite create/listen fails AFTER services\n // started, stop them so we don't leak child processes.\n await orchestration.stop()\n throw err\n }\n\n // EC-1 fix: attach orchestration.stop() to the underlying HTTP server's\n // close event via Node-native API. We do NOT mutate `server.close`\n // (fragile across Vite upgrades). httpServer is `http.Server | null`.\n server.httpServer?.on('close', () => {\n void orchestration.stop()\n })\n\n return server\n}\n\nexport async function devCommand(options: DevOptions): Promise<void> {\n try {\n const cwd = process.cwd()\n const server = await startDevServer(cwd, options)\n server.printUrls()\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n console.error(`\\n ✗ ${msg}\\n`)\n process.exit(1)\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAO,WAAW;AAClB,SAAS,oBAAqD;AAa9D,eAAsB,eAAe,KAAa,SAA8C;AAM9F,2BAAyB,GAAG;AAK5B,UAAQ,EAAE,KAAK,MAAM,cAAc,CAAC;AAEpC,QAAM,SAAS,MAAM,WAAW,GAAG;AAEnC,2BAAyB,KAAK,OAAO,MAAM;AAI3C,QAAM,gBAAgB,MAAM,eAAe;AAAA,IACzC;AAAA,IACA,UAAU,OAAO;AAAA,EACnB,CAAC;AACD,MAAI,CAAC,cAAc,YAAY;AAC7B,UAAM,cAAc,KAAK;AACzB,UAAM,IAAI;AAAA,MACR,2CAA2C,cAAc,UAAU,KAAK,IAAI,CAAC;AAAA,IAG/E;AAAA,EACF;AAMA,QAAM,OAAO,SAAS,QAAQ,OAAO;AAKrC,QAAM,gBACJ,OAAO,aAAa,cAAc,OAAO,aAAa,SAAS,OAAO,YAClE,OAAO,YACP;AAKN,MAAI;AACJ,MAAI;AACF,UAAM,cAAc,MAAM,gBAAgB;AAAA,MACxC,MAAM;AAAA,MACN,WAAW;AAAA,MACX,KAAK,OAAO;AAAA;AAAA,MAEZ,WAAW,OAAO;AAAA;AAAA,MAElB,WAAW,OAAO;AAAA;AAAA,MAElB,QAAQ,OAAO;AAAA;AAAA,MAEf,UAAU,OAAO;AAAA;AAAA;AAAA,MAGjB,kBAAkB,OAAO;AAAA,IAC3B,CAAC;AACD,aAAS,MAAM,aAAa;AAAA,MAC1B,MAAM;AAAA,MACN,SAAS,CAAC,MAAM,GAAG,GAAG,WAAW;AAAA,MACjC,QAAQ;AAAA,QACN;AAAA,QACA,MAAM,OAAO;AAAA,QACb,MAAM,OAAO;AAAA,QACb,YAAY,OAAO;AAAA,QACnB,QAAQ,OAAO,UAAU;AAAA,UACvB,aAAa,CAAC,kBAAkB,eAAe;AAAA,QACjD;AAAA,MACF;AAAA,MACA,UAAU,SAAS,SAAS,IAAI,WAAW;AAAA,IAC7C,CAAC;AAED,UAAM,OAAO,OAAO;AAAA,EACtB,SAAS,KAAK;AAGZ,UAAM,cAAc,KAAK;AACzB,UAAM;AAAA,EACR;AAKA,SAAO,YAAY,GAAG,SAAS,MAAM;AACnC,SAAK,cAAc,KAAK;AAAA,EAC1B,CAAC;AAED,SAAO;AACT;AAEA,eAAsB,WAAW,SAAoC;AACnE,MAAI;AACF,UAAM,MAAM,QAAQ,IAAI;AACxB,UAAM,SAAS,MAAM,eAAe,KAAK,OAAO;AAChD,WAAO,UAAU;AAAA,EACnB,SAAS,KAAK;AACZ,UAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,YAAQ,MAAM;AAAA,WAAS,GAAG;AAAA,CAAI;AAC9B,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -305,7 +305,7 @@ declare class TheoConfigError extends Error {
|
|
|
305
305
|
constructor(issues: ConfigIssue[], configPath: string);
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
declare function validateProjectStructure(rootDir: string): void;
|
|
308
|
+
declare function validateProjectStructure(rootDir: string, appDir?: string): void;
|
|
309
309
|
|
|
310
310
|
declare class TheoProjectError extends Error {
|
|
311
311
|
readonly errors: string[];
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
scanRoutes,
|
|
9
9
|
theoConfigSchema,
|
|
10
10
|
theoPlugin
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-7PFHQ56Q.js";
|
|
12
12
|
import "./chunk-D7ZH7DTG.js";
|
|
13
13
|
import "./chunk-RSVN727G.js";
|
|
14
14
|
import "./chunk-7BIM27LV.js";
|
|
@@ -65,12 +65,6 @@ ${errorLines}
|
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
// src/config/validate-structure.ts
|
|
68
|
-
var REQUIRED_DIRS = [
|
|
69
|
-
{
|
|
70
|
-
path: "app",
|
|
71
|
-
errorMessage: "Missing required directory: app/"
|
|
72
|
-
}
|
|
73
|
-
];
|
|
74
68
|
var REQUIRED_FILES = [
|
|
75
69
|
{
|
|
76
70
|
path: "theo.config.ts",
|
|
@@ -81,12 +75,15 @@ var REQUIRED_FILES = [
|
|
|
81
75
|
errorMessage: "Missing required file: package.json"
|
|
82
76
|
}
|
|
83
77
|
];
|
|
84
|
-
function validateProjectStructure(rootDir) {
|
|
78
|
+
function validateProjectStructure(rootDir, appDir = "app") {
|
|
85
79
|
if (!existsSync(rootDir)) {
|
|
86
80
|
throw new TheoProjectError([`Project directory does not exist: ${rootDir}`], rootDir);
|
|
87
81
|
}
|
|
88
82
|
const errors = [];
|
|
89
|
-
|
|
83
|
+
const requiredDirs = [
|
|
84
|
+
{ path: appDir, errorMessage: `Missing required directory: ${appDir}/` }
|
|
85
|
+
];
|
|
86
|
+
for (const rule of requiredDirs) {
|
|
90
87
|
if (!existsSync(join(rootDir, rule.path))) {
|
|
91
88
|
errors.push(rule.errorMessage);
|
|
92
89
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config/define-config.ts","../src/config/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 '../core/errors.js'\n\ninterface ValidationRule {\n path: string\n errorMessage: string\n}\n\nconst
|
|
1
|
+
{"version":3,"sources":["../src/config/define-config.ts","../src/config/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 '../core/errors.js'\n\ninterface ValidationRule {\n path: string\n errorMessage: string\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\n// #95 — honor config `appDir` (default \"app\") so a project that groups its frontend under a custom\n// directory (e.g. `apps/web`) is not rejected by a hardcoded `app/` requirement.\nexport function validateProjectStructure(rootDir: string, appDir = 'app'): void {\n if (!existsSync(rootDir)) {\n throw new TheoProjectError([`Project directory does not exist: ${rootDir}`], rootDir)\n }\n\n const errors: string[] = []\n\n const requiredDirs: ValidationRule[] = [\n { path: appDir, errorMessage: `Missing required directory: ${appDir}/` },\n ]\n\n for (const rule of requiredDirs) {\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,iBAAmC;AAAA,EACvC;AAAA,IACE,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,cAAc;AAAA,EAChB;AACF;AAIO,SAAS,yBAAyB,SAAiB,SAAS,OAAa;AAC9E,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,UAAM,IAAI,iBAAiB,CAAC,qCAAqC,OAAO,EAAE,GAAG,OAAO;AAAA,EACtF;AAEA,QAAM,SAAmB,CAAC;AAE1B,QAAM,eAAiC;AAAA,IACrC,EAAE,MAAM,QAAQ,cAAc,+BAA+B,MAAM,IAAI;AAAA,EACzE;AAEA,aAAW,QAAQ,cAAc;AAC/B,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,7 +2,7 @@
|
|
|
2
2
|
import "tsx/esm";
|
|
3
3
|
import {
|
|
4
4
|
createAgentSsrLoader
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-23T4CO4M.js";
|
|
6
6
|
import {
|
|
7
7
|
extractAppResources,
|
|
8
8
|
handleMcpJsonRpc
|
|
@@ -75,4 +75,4 @@ async function mcpCommand(name, deps = {}) {
|
|
|
75
75
|
export {
|
|
76
76
|
mcpCommand
|
|
77
77
|
};
|
|
78
|
-
//# sourceMappingURL=mcp-
|
|
78
|
+
//# sourceMappingURL=mcp-HP5RVOOZ.js.map
|
|
@@ -6,7 +6,7 @@ var adapterRegistry = {
|
|
|
6
6
|
node: async () => (await import("./node-BPJ3Z4DT.js")).nodeAdapter,
|
|
7
7
|
vercel: async () => (await import("./vercel-J6G7ZHYQ.js")).vercelAdapter,
|
|
8
8
|
cloudflare: async () => (await import("./cloudflare-C6E5SPAE.js")).cloudflareAdapter,
|
|
9
|
-
static: async () => (await import("./static-
|
|
9
|
+
static: async () => (await import("./static-CYG2WGYP.js")).staticAdapter,
|
|
10
10
|
bun: async () => (await import("./bun-KP2KES6S.js")).bunAdapter,
|
|
11
11
|
"deno-deploy": async () => (await import("./deno-deploy-RFZN56X4.js")).denoDeployAdapter,
|
|
12
12
|
netlify: async () => (await import("./netlify-PMLHVPN4.js")).netlifyAdapter,
|
|
@@ -21,4 +21,4 @@ export {
|
|
|
21
21
|
adapterRegistry,
|
|
22
22
|
resolveAdapter
|
|
23
23
|
};
|
|
24
|
-
//# sourceMappingURL=registry-
|
|
24
|
+
//# sourceMappingURL=registry-XSRSTH33.js.map
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import "tsx/esm";
|
|
3
3
|
import {
|
|
4
4
|
validateProjectStructure
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-GEQJAFSV.js";
|
|
6
6
|
import {
|
|
7
7
|
loadConfig
|
|
8
8
|
} from "./chunk-AFVTEAES.js";
|
|
@@ -21,7 +21,7 @@ import { resolve, relative } from "path";
|
|
|
21
21
|
async function routesCommand() {
|
|
22
22
|
const cwd = process.cwd();
|
|
23
23
|
const config = await loadConfig(cwd);
|
|
24
|
-
validateProjectStructure(cwd);
|
|
24
|
+
validateProjectStructure(cwd, config.appDir);
|
|
25
25
|
const serverDir = resolve(cwd, config.serverDir);
|
|
26
26
|
const apiRoutes = scanServerRoutes(serverDir);
|
|
27
27
|
const actions = scanServerActions(serverDir);
|
|
@@ -62,4 +62,4 @@ async function routesCommand() {
|
|
|
62
62
|
export {
|
|
63
63
|
routesCommand
|
|
64
64
|
};
|
|
65
|
-
//# sourceMappingURL=routes-
|
|
65
|
+
//# sourceMappingURL=routes-XNS3UM3B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/routes.ts"],"sourcesContent":["import { resolve, relative } from 'node:path'\n\nimport { loadConfig } from '../../config/load-config.js'\nimport { validateProjectStructure } from '../../config/validate-structure.js'\nimport { scanServerActions } from '../../server/scan/action-scan.js'\nimport { scanServerRoutes } from '../../server/scan/scan.js'\nimport { scanWebSocketRoutes } from '../../server/scan/ws-scan.js'\n\nexport async function routesCommand(): Promise<void> {\n const cwd = process.cwd()\n const config = await loadConfig(cwd)\n // #95 — honor config.appDir so a custom frontend dir (e.g. apps/web) passes the structure gate.\n validateProjectStructure(cwd, config.appDir)\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;AAEnC,2BAAyB,KAAK,OAAO,MAAM;AAE3C,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":[]}
|
|
@@ -190,7 +190,7 @@ async function buildStatic(config, cwd, deps = {}, ctx) {
|
|
|
190
190
|
const runNodeBuild = deps.runNodeBuild ?? nodeAdapter.build.bind(nodeAdapter);
|
|
191
191
|
await runNodeBuild(config, cwd, ctx);
|
|
192
192
|
const scan = deps.scanAppRoutes ?? scanRoutes;
|
|
193
|
-
const appDir = resolve(cwd,
|
|
193
|
+
const appDir = resolve(cwd, config.appDir);
|
|
194
194
|
const tree = scan(appDir);
|
|
195
195
|
const loadStaticPaths = deps.loadStaticPaths ?? defaultLoadStaticPaths;
|
|
196
196
|
const collect = deps.collectPaths ?? collectStaticPaths;
|
|
@@ -225,4 +225,4 @@ export {
|
|
|
225
225
|
detectApiRoutes,
|
|
226
226
|
staticAdapter
|
|
227
227
|
};
|
|
228
|
-
//# sourceMappingURL=static-
|
|
228
|
+
//# sourceMappingURL=static-CYG2WGYP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/static.ts","../src/adapters/static-paths.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * All filesystem inputs in this file are derived from:\n * - `process.cwd()` resolved at build time, OR\n * - directory tree walks rooted in `serverDir`/`appDir` (themselves\n * resolved from cwd), OR\n * - file paths produced by `collectStaticPaths` (a controlled router\n * scanner).\n * No path here originates from HTTP input, environment variables, or\n * deserialized user data. The path-traversal vector the rule guards\n * against does not apply — this is a build-time tool, not a request\n * handler.\n */\nimport { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'\nimport { dirname, resolve, relative } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\nimport type { TheoConfig } from '../config/schema.js'\nimport { scanRoutes } from '../router/scan.js'\nimport type { RouteNode } from '../router/types.js'\nimport { assertServicesUnsupported, readManifest } from '../services/index.js'\n\nimport { nodeAdapter } from './node.js'\nimport {\n collectStaticPaths,\n type CollectOptions,\n type LoadStaticPaths,\n type ResolvedPath,\n} from './static-paths.js'\nimport type { AdapterBuildContext, DeployAdapter } from './types.js'\n\nexport class StaticApiRoutesDetectedError extends Error {\n constructor(routes: string[]) {\n super(\n `Static adapter cannot build a project with API routes. Found: ${routes.join(\n ', ',\n )}. Use 'node', 'vercel', 'cloudflare' or another runtime adapter.`,\n )\n this.name = 'StaticApiRoutesDetectedError'\n }\n}\n\nexport class StaticRenderError extends Error {\n constructor(url: string, cause: unknown) {\n const causeMsg = cause instanceof Error ? cause.message : String(cause)\n super(`SSR render failed for ${url}: ${causeMsg}`)\n this.name = 'StaticRenderError'\n if (cause instanceof Error) {\n this.cause = cause\n }\n }\n}\n\nexport interface StaticBuildDeps {\n detectApiRoutes?: (serverDir: string) => string[]\n scanAppRoutes?: (appDir: string) => RouteNode\n collectPaths?: (tree: RouteNode, options: CollectOptions) => Promise<ResolvedPath[]>\n loadStaticPaths?: LoadStaticPaths\n renderHtml?: (url: string, cwd: string) => Promise<string>\n ensureDir?: (path: string) => Promise<void>\n writeFile?: (path: string, content: string) => Promise<void>\n runNodeBuild?: (config: TheoConfig, cwd: string, ctx?: AdapterBuildContext) => Promise<void>\n}\n\nexport function detectApiRoutes(serverDir: string): string[] {\n const routesDir = resolve(serverDir, 'routes')\n if (!existsSync(routesDir)) return []\n if (!statSync(routesDir).isDirectory()) return []\n\n const out: string[] = []\n walkRoutes(routesDir, routesDir, out)\n return out\n}\n\nfunction walkRoutes(root: string, dir: string, out: string[]): void {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name.startsWith('.') || entry.name.startsWith('_')) continue\n const full = resolve(dir, entry.name)\n if (entry.isDirectory()) {\n walkRoutes(root, full, out)\n } else if (entry.isFile() && /\\.(ts|tsx|js|jsx|mjs)$/.test(entry.name)) {\n const rel = relative(root, full).split(/[\\\\/]/).join('/')\n out.push(rel)\n }\n }\n}\n\n/* eslint-disable @typescript-eslint/require-await -- DI contract is async; default impl uses sync fs */\n\n// The DI surface exposes async helpers (so consumers can override with\n// real async IO — e.g. S3 uploads). The defaults use sync fs because\n// every caller awaits the result anyway.\nconst defaultEnsureDir = async (path: string): Promise<void> => {\n mkdirSync(path, { recursive: true })\n}\n\nconst defaultWriteFile = async (path: string, content: string): Promise<void> => {\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, content)\n}\n\n/* eslint-enable @typescript-eslint/require-await */\n\n/**\n * Default loader: dynamic-imports the file at `paramsFilePath` and invokes\n * its default export. Returns null if the file does not exist (the caller\n * will throw StaticPathsRequiredError with a clear message in that case).\n */\nconst defaultLoadStaticPaths: LoadStaticPaths = async (paramsFilePath) => {\n if (!existsSync(paramsFilePath)) return null\n try {\n const mod = (await import(pathToFileURL(paramsFilePath).href)) as {\n default?: LoadStaticPaths | (() => Awaited<ReturnType<LoadStaticPaths>>)\n }\n if (typeof mod.default !== 'function') return null\n // The user's `static-paths.ts` may export `default` as either sync or\n // async — `await` accepts both, but ESLint's `await-thenable` cannot\n // see through the union.\n const value = mod.default(paramsFilePath)\n const result = value instanceof Promise ? await value : value\n return result ?? null\n } catch {\n return null\n }\n}\n\n/**\n * Default renderer: loads the built SSR entry (`.theokit/server/entry-server.js`)\n * and calls its `render(url)` export, wrapping the result in the client\n * `index.html` template. Falls back to a minimal HTML shell when no SSR build\n * is present (so the static adapter still emits something usable even without\n * SSR enabled).\n */\nfunction createDefaultRenderHtml(cwd: string): (url: string) => Promise<string> {\n return async (url: string): Promise<string> => {\n const clientDir = resolve(cwd, '.theokit/client')\n const indexPath = resolve(clientDir, 'index.html')\n const ssrEntryPath = resolve(cwd, '.theokit/server/entry-server.js')\n\n let baseHtml = '<!doctype html><html><body><div id=\"root\"></div></body></html>'\n if (existsSync(indexPath)) {\n baseHtml = readFileSync(indexPath, 'utf-8')\n }\n\n if (!existsSync(ssrEntryPath)) {\n // No SSR build — emit the client shell. Hydration on the client will\n // render the route once JS loads. Acceptable degradation when the user\n // chose static + ssr: false.\n return baseHtml\n }\n\n try {\n const mod = (await import(pathToFileURL(ssrEntryPath).href)) as {\n render?: (u: string) => Promise<string | { redirect: Response }>\n }\n if (typeof mod.render !== 'function') return baseHtml\n const result = await mod.render(url)\n if (typeof result === 'string') {\n const rootMatch = /<div id=[\"']root[\"'][^>]*>/.exec(baseHtml)\n if (rootMatch) {\n const splitIdx = baseHtml.indexOf(rootMatch[0]) + rootMatch[0].length\n return baseHtml.slice(0, splitIdx) + result + baseHtml.slice(splitIdx)\n }\n return baseHtml.replace('</body>', result + '</body>')\n }\n // redirect — emit a meta refresh fallback for static export\n return `<!doctype html><meta http-equiv=\"refresh\" content=\"0; url=/\" />`\n } catch (err) {\n throw new StaticRenderError(url, err)\n }\n }\n}\n\nexport async function buildStatic(\n config: TheoConfig,\n cwd: string,\n deps: StaticBuildDeps = {},\n ctx?: AdapterBuildContext,\n): Promise<void> {\n // Wave 2 (T2.2) — reject polyglot services on this adapter.\n assertServicesUnsupported('static', readManifest(cwd))\n\n const detect = deps.detectApiRoutes ?? detectApiRoutes\n const apiRoutes = detect(resolve(cwd, 'server'))\n if (apiRoutes.length > 0) {\n throw new StaticApiRoutesDetectedError(apiRoutes)\n }\n\n const runNodeBuild = deps.runNodeBuild ?? nodeAdapter.build.bind(nodeAdapter)\n await runNodeBuild(config, cwd, ctx)\n\n const scan = deps.scanAppRoutes ?? scanRoutes\n // #95 — honor config `appDir` (default \"app\") so `--target static` prerenders a custom frontend dir.\n const appDir = resolve(cwd, config.appDir)\n const tree = scan(appDir)\n\n const loadStaticPaths = deps.loadStaticPaths ?? defaultLoadStaticPaths\n const collect = deps.collectPaths ?? collectStaticPaths\n const paths = await collect(tree, { appDir, loadStaticPaths })\n\n if (paths.length === 0) return\n\n const renderHtml = deps.renderHtml ?? createDefaultRenderHtml(cwd)\n const ensureDir = deps.ensureDir ?? defaultEnsureDir\n const writeFile = deps.writeFile ?? defaultWriteFile\n\n const outDir = resolve(cwd, '.theokit/static')\n await ensureDir(outDir)\n\n for (const path of paths) {\n let html: string\n try {\n html = await renderHtml(path.url, cwd)\n } catch (err) {\n throw new StaticRenderError(path.url, err)\n }\n const outPath = resolve(outDir, path.filename)\n await writeFile(outPath, html)\n }\n}\n\nexport const staticAdapter: DeployAdapter = {\n name: 'static',\n build(config, cwd, ctx) {\n return buildStatic(config, cwd, {}, ctx)\n },\n}\n","import type { RouteNode } from '../router/types.js'\n\nexport interface ResolvedPath {\n /** URL path to render, e.g. '/blog/123' */\n url: string\n /** Output file path relative to .theokit/static/, e.g. 'blog/123.html' */\n filename: string\n}\n\nexport type StaticPathParams = Record<string, string | string[]>\n\nexport type LoadStaticPaths = (paramsFilePath: string) => Promise<StaticPathParams[] | null>\n\nexport class StaticPathsRequiredError extends Error {\n constructor(routePath: string, paramsFile: string) {\n super(\n `Route ${routePath} has dynamic segments and requires ${paramsFile}. ` +\n `Export default function returning Array<{ <param>: string | string[] }>.`,\n )\n this.name = 'StaticPathsRequiredError'\n }\n}\n\nexport interface CollectOptions {\n appDir: string\n loadStaticPaths: LoadStaticPaths\n}\n\nexport type ParsedSegment =\n | { kind: 'static' }\n | { kind: 'param'; name: string }\n | { kind: 'catch-all'; name: string }\n\nconst CATCH_ALL = /^\\[\\.\\.\\.(.+)\\]$/\nconst PARAM = /^\\[(.+)\\]$/\n\nexport function parseSegment(segment: string): ParsedSegment {\n const catchAll = CATCH_ALL.exec(segment)\n if (catchAll) return { kind: 'catch-all', name: catchAll[1] }\n const param = PARAM.exec(segment)\n if (param) return { kind: 'param', name: param[1] }\n return { kind: 'static' }\n}\n\nexport async function collectStaticPaths(\n tree: RouteNode,\n options: CollectOptions,\n): Promise<ResolvedPath[]> {\n const out: ResolvedPath[] = []\n await walk(tree, [], out, options)\n return out\n}\n\nasync function walk(\n node: RouteNode,\n parentSegments: string[],\n out: ResolvedPath[],\n options: CollectOptions,\n): Promise<void> {\n const segments = node.segment === '' ? parentSegments : [...parentSegments, node.segment]\n\n if (node.page) {\n const resolved = await resolveNodePaths(segments, options)\n out.push(...resolved)\n }\n\n for (const child of node.children) {\n await walk(child, segments, out, options)\n }\n}\n\nasync function resolveNodePaths(\n segments: string[],\n options: CollectOptions,\n): Promise<ResolvedPath[]> {\n if (segments.length === 0) {\n return [{ url: '/', filename: 'index.html' }]\n }\n\n const parsed = segments.map(parseSegment)\n const hasDynamic = parsed.some((p) => p.kind !== 'static')\n\n if (!hasDynamic) {\n const joined = segments.join('/')\n return [{ url: '/' + joined, filename: joined + '.html' }]\n }\n\n let lastDynamicIdx = 0\n for (let i = 0; i < parsed.length; i++) {\n if (parsed[i].kind !== 'static') lastDynamicIdx = i\n }\n\n const dirSegments = segments.slice(0, lastDynamicIdx + 1)\n const paramsFile = `${options.appDir}/${dirSegments.join('/')}/static-paths.ts`\n const params = await options.loadStaticPaths(paramsFile)\n\n if (!params) {\n const routePath = '/' + segments.join('/')\n throw new StaticPathsRequiredError(routePath, paramsFile)\n }\n\n return params.map((paramSet) => {\n const filled: string[] = []\n for (let i = 0; i < segments.length; i++) {\n const p = parsed[i]\n if (p.kind === 'static') {\n filled.push(segments[i])\n } else {\n const value = paramSet[p.name]\n if (Array.isArray(value)) {\n filled.push(...value)\n } else {\n filled.push(value)\n }\n }\n }\n const joined = filled.join('/')\n return { url: '/' + joined, filename: joined + '.html' }\n })\n}\n"],"mappings":"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","names":[]}
|
|
@@ -106,6 +106,12 @@ interface TheoPluginOptions {
|
|
|
106
106
|
* `"agents"`. Set to e.g. `"core/agents"` to co-locate agents with the domain root (#95 follow-up).
|
|
107
107
|
*/
|
|
108
108
|
agentsDir?: string;
|
|
109
|
+
/**
|
|
110
|
+
* Frontend dir (config `appDir`, default "app") holding the file-based router (`page.tsx`,
|
|
111
|
+
* `layout.tsx`, …), relative to the project root. Set to e.g. `"apps/web"` to group the web UI
|
|
112
|
+
* under an `apps/` root alongside other frontends (#95 follow-up).
|
|
113
|
+
*/
|
|
114
|
+
appDir?: string;
|
|
109
115
|
}
|
|
110
116
|
/**
|
|
111
117
|
* Async factory companion. Returns `[theoPlugin, ...autoChainedPlugins]`.
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
defineTheoIntegration,
|
|
8
8
|
theoPlugin,
|
|
9
9
|
theoPluginAsync
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-3WOODHXK.js";
|
|
11
11
|
import "./chunk-DXF64KS5.js";
|
|
12
12
|
import "./chunk-DMC6HYUR.js";
|
|
13
13
|
import "./chunk-M6NMS5LR.js";
|
|
@@ -32,4 +32,4 @@ export {
|
|
|
32
32
|
theoPlugin,
|
|
33
33
|
theoPluginAsync
|
|
34
34
|
};
|
|
35
|
-
//# sourceMappingURL=vite-plugin-
|
|
35
|
+
//# sourceMappingURL=vite-plugin-3UDOJGO5.js.map
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/commands/build.ts","../src/adapters/types.ts","../src/server/cron/adapter-translators.ts","../src/server/_internal/atomic-write.ts","../src/server/cron/cron-manifest.ts","../src/server/cron/cron-scan.ts","../src/server/jobs/job-manifest.ts","../src/server/jobs/job-scan.ts","../src/cli/cleanup/cleanup.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\n// T1.1 (architecture-medium-deferrals) — nodeAdapter no longer static-imported.\n// All adapters dispatch via `adapterRegistry` (lazy-imported within runAdapterBuild).\nimport { VALID_TARGETS, type BuildTarget, type AdapterBuildContext } from '../../adapters/types.js'\nimport { loadConfig } from '../../config/load-config.js'\nimport { loadEnv } from '../../config/load-env.js'\nimport { validateProjectStructure } from '../../config/validate-structure.js'\nimport {\n ExistingConfigUnparseableError,\n translateCronToAws,\n translateCronToCloudflare,\n translateCronToDeno,\n translateCronToVercel,\n} from '../../server/cron/adapter-translators.js'\nimport { writeCronManifest } from '../../server/cron/cron-manifest.js'\nimport { scanCrons } from '../../server/cron/cron-scan.js'\nimport { writeJobManifest } from '../../server/jobs/job-manifest.js'\nimport { scanJobs } from '../../server/jobs/job-scan.js'\nimport { generateManifest, writeManifest } from '../../server/scan/manifest.js'\nimport {\n buildManifest as buildServicesManifest,\n writeManifest as writeServicesManifest,\n} from '../../services/index.js'\n// G2 T2.2 — OpenAPI emit. Opt-in via `config.openapi`. Dual output:\n// 1. <distDir>/openapi.json (pre-Vite, dev surface + manifests sibling)\n// 2. dist/openapi.json (post-Vite, build artifact)\n// The dist emit awaits runAdapterBuild — if Vite fails, the second emit\n// never runs (EC-2 absorbed: no stale dist artifact).\nimport { emitOpenApi } from '../../vite-plugin/openapi-emit/emit.js'\nimport { loadRoutesForOpenApi } from '../../vite-plugin/openapi-emit/load-routes.js'\nimport { cleanOutDir } from '../cleanup/cleanup.js'\nimport { preflightNodeAndBindings } from '../preflight-node-version.js'\n\n// Adapters that do NOT support cron triggers natively. Build still\n// succeeds with crons declared, but emits a warning + skip note.\nconst CRON_NA_TARGETS = new Set<BuildTarget>(['bun', 'netlify', 'static'])\n\nexport async function buildCommand(options?: { target?: string }): Promise<void> {\n const cwd = process.cwd()\n // Preflight (FIRST — BEFORE anything that touches native bindings).\n preflightNodeAndBindings(cwd)\n // Phase 1 (T1.2) — Load .env BEFORE config load.\n loadEnv({ cwd, mode: 'production' })\n\n const config = await loadConfig(cwd)\n validateProjectStructure(cwd)\n\n // T2.2 — Clean .theokit/ at build start (Astro pattern). Skip .git*.\n const distDirAbs = resolve(cwd, config.distDir)\n await cleanOutDir({ dir: distDirAbs })\n\n const target = (options?.target ?? 'node') as BuildTarget\n\n if (!VALID_TARGETS.includes(target)) {\n throw new Error(\n `Invalid build target \"${target}\". Available targets: ${VALID_TARGETS.join(', ')}`,\n )\n }\n\n // EC-201 — cross-reference note when config.adapters[] diverges from\n // the --target flag. --target is authoritative per ADR D2.\n const configAdapters = (config as { adapters?: readonly string[] }).adapters\n if (configAdapters && configAdapters.length > 0) {\n const otherAdapters = configAdapters.filter((a) => a !== target)\n if (otherAdapters.length > 0) {\n console.log(\n `\\n Note: theo.config.ts.adapters lists [${otherAdapters.join(', ')}]; ` +\n `this build translates for ${target} only. ` +\n `Run \\`theokit build --target=<adapter>\\` separately for each (cross-reference).`,\n )\n }\n }\n\n console.log(`\\n Building for ${target}...\\n`)\n\n // Manifests emit BEFORE adapter bundling (Vite). Why: manifests are\n // fast + deterministic + don't depend on Vite. If Vite fails (missing\n // dep, malformed entry), the user still gets manifests for diagnostics.\n const serverDir = resolve(cwd, config.serverDir)\n const distDir = distDirAbs\n // #95 follow-up — pass projectRoot (cwd) + config.agentsDir so agents scan honors a custom dir.\n const manifest = generateManifest(serverDir, cwd, config.agentsDir)\n writeManifest(manifest, distDir)\n\n const totalEndpoints =\n manifest.routes.length + manifest.actions.length + manifest.websockets.length\n console.log(\n ` ✓ Manifest: ${manifest.routes.length} routes, ${manifest.actions.length} actions, ${manifest.websockets.length} ws (${totalEndpoints} total)`,\n )\n\n // T1.1 — cron scan + manifest + per-target adapter translation\n await emitCronArtifacts({ cwd, serverDir, distDir, target })\n\n // T1.2 — job scan + manifest (no per-target translation needed)\n await emitJobArtifacts({ cwd, serverDir, distDir })\n\n // Wave 2 (T1.2) — services manifest at <cwd>/.theokit/services.json. Always\n // emit (empty array when services: {} is empty) so adapters can rely on\n // the file existing. Topological order preserved by buildServicesManifest.\n //\n // Plan v1.2 T2.1 — when theo.config.ts declares `name`, emit services.json\n // v2 with that project identifier. Falling back to v1 (no project field)\n // keeps TheoCloud's deprecation warning path intact (services-bundle).\n const projectName = config.name\n const servicesManifest = buildServicesManifest(config.services, projectName)\n writeServicesManifest(cwd, servicesManifest)\n if (servicesManifest.services.length > 0) {\n const versionLabel = `v${String(servicesManifest.version)}`\n const projectLabel =\n servicesManifest.version === 2 ? ` project=\"${servicesManifest.project}\"` : ''\n console.log(\n ` ✓ Services manifest (${versionLabel}${projectLabel}): ${String(servicesManifest.services.length)} service(s) ` +\n `(${servicesManifest.services.map((s) => s.name).join(', ')})`,\n )\n } else if (servicesManifest.version === 1) {\n console.log(\n ' ⚠ services.json emitted as v1 (no `name` in theo.config.ts). ' +\n 'TheoCloud will accept this with a deprecation warning; sunset in theokit 0.6.0. ' +\n 'Run `theokit migrate services-json-v1-to-v2` to upgrade.',\n )\n }\n\n // G2 T2.2 — OpenAPI dev-surface emit (pre-Vite, sibling of manifests).\n // Opt-in: only when config.openapi is defined. Best-effort: a route load\n // failure produces a warning, not a build abort.\n if (config.openapi !== undefined) {\n const hydrated = await loadRoutesForOpenApi({ serverDir, routes: manifest.routes })\n const devResult = emitOpenApi({\n manifest: hydrated,\n config: { ...config.openapi, outDir: distDir },\n })\n console.log(` ✓ OpenAPI: ${String(hydrated.length)} ops → ${devResult.path}`)\n }\n\n // Now run the adapter-specific bundling (Vite + adapter-specific work).\n await runAdapterBuild(target, config, cwd)\n\n // G2 T2.2 — OpenAPI build-artifact emit (post-Vite, EC-2 gated on success).\n // If runAdapterBuild threw, execution never reaches this point — no stale\n // dist/openapi.json is written.\n if (config.openapi !== undefined) {\n const distOut = resolve(cwd, 'dist')\n const hydrated = await loadRoutesForOpenApi({ serverDir, routes: manifest.routes })\n const distResult = emitOpenApi({\n manifest: hydrated,\n config: { ...config.openapi, outDir: distOut },\n })\n console.log(` ✓ OpenAPI (dist): ${distResult.path}`)\n }\n\n const ssrNote = config.ssr ? ' (SSR)' : ''\n console.log(`\\n ✓ Build complete → ${target}${ssrNote}\\n`)\n}\n\nasync function runAdapterBuild(\n target: BuildTarget,\n config: Awaited<ReturnType<typeof loadConfig>>,\n cwd: string,\n): Promise<void> {\n // T1.1 (architecture-cleanup) — CLI composes the Vite Plugin[] and INJECTS it into\n // the adapter via ctx.makeVitePlugins. This inverts the previous `adapters → vite-plugin`\n // edge — adapters no longer import from vite-plugin/ directly.\n //\n // theoPlugin AND `@vitejs/plugin-react` are dynamically imported so the deps are\n // materialized only when needed (adapter is `node`). This keeps the CLI's startup\n // path independent of optional build-time deps (so e.g. `theokit build --target=static`\n // does not need react installed). dep-cruiser's per-module rule allows `cli → vite-plugin`.\n //\n // 0.2.2 regression fix: switched sync `theoPlugin()` → `theoPluginAsync()` so the\n // returned Plugin[] includes the @theo/actions virtual module + typed-client +\n // services + @theokit/ui auto-chain. Without async, build-time Rollup couldn't\n // resolve `@theo/actions` even though dev-time Vite (which already used async)\n // could. See https://github.com/usetheo/theokit/commits — 0.2.2 changelog.\n const { theoPluginAsync } = await import('../../vite-plugin/index.js')\n const { default: react } = await import('@vitejs/plugin-react')\n const ctx: AdapterBuildContext = {\n // `react()` may return Plugin or Plugin[] depending on version; spread the\n // async chain so the contract returns a flat Plugin[] (AdapterBuildContext\n // type updated to `Plugin[] | Promise<Plugin[]>`).\n makeVitePlugins: async (opts) => [react(), ...(await theoPluginAsync(opts))].flat(),\n }\n\n // T1.1 (architecture-medium-deferrals, ADR D1) — Adapter Registry replaces\n // the previous 9-case switch. New adapters add 1 line in `adapters/registry.ts`;\n // CLI is closed for modification (OCP).\n const { resolveAdapter } = await import('../../adapters/registry.js')\n const adapter = await resolveAdapter(target)\n await adapter.build(config, cwd, ctx)\n}\n\nasync function emitCronArtifacts(opts: {\n cwd: string\n serverDir: string\n distDir: string\n target: BuildTarget\n}): Promise<void> {\n const cronsDir = resolve(opts.serverDir, 'crons')\n\n const cronNodes = existsSync(cronsDir) ? await scanCrons(cronsDir) : []\n const manifestPath = resolve(opts.distDir, 'crons.json')\n writeCronManifest(manifestPath, cronNodes, opts.cwd)\n\n console.log(` ✓ Crons: ${cronNodes.length} declared`)\n\n if (cronNodes.length === 0) return\n\n // EC-201: target-specific translation. N/A targets emit a warning + skip.\n if (CRON_NA_TARGETS.has(opts.target)) {\n console.log(\n ` ⚠ Cron not supported by target \"${opts.target}\" — declared crons skipped. ` +\n `See docs/concepts/crons.md for supported targets.`,\n )\n return\n }\n\n const manifestEntries = cronNodes.map((n) => ({\n name: n.name,\n filePath: relativize(n.filePath, opts.cwd),\n schedule: n.schedule,\n concurrency: n.concurrency,\n }))\n\n try {\n switch (opts.target) {\n case 'vercel':\n translateCronToVercel(resolve(opts.cwd, 'vercel.json'), manifestEntries)\n console.log(` ✓ Cron → vercel.json crons[] (${cronNodes.length} entries)`)\n break\n case 'cloudflare':\n translateCronToCloudflare(resolve(opts.cwd, 'wrangler.toml'), manifestEntries)\n console.log(` ✓ Cron → wrangler.toml [triggers] (${cronNodes.length})`)\n break\n case 'aws-lambda':\n translateCronToAws(resolve(opts.cwd, 'serverless.yml'), manifestEntries)\n console.log(` ✓ Cron → serverless.yml functions (${cronNodes.length})`)\n break\n case 'deno-deploy':\n translateCronToDeno(resolve(opts.distDir, 'crons-entry.ts'), manifestEntries)\n console.log(` ✓ Cron → ${opts.distDir}/crons-entry.ts (Deno.cron)`)\n break\n case 'node':\n console.log(` ✓ Cron → in-process scheduler (theokit start)`)\n break\n }\n } catch (err) {\n if (err instanceof ExistingConfigUnparseableError) throw err\n throw err\n }\n}\n\nasync function emitJobArtifacts(opts: {\n cwd: string\n serverDir: string\n distDir: string\n}): Promise<void> {\n const jobsDir = resolve(opts.serverDir, 'jobs')\n\n const jobNodes = existsSync(jobsDir) ? await scanJobs(jobsDir) : []\n const manifestPath = resolve(opts.distDir, 'jobs.json')\n writeJobManifest(manifestPath, jobNodes, opts.cwd)\n console.log(` ✓ Jobs: ${jobNodes.length} declared`)\n}\n\nfunction relativize(absPath: string, root: string): string {\n if (absPath.startsWith(root)) {\n const trimmed = absPath.slice(root.length)\n return trimmed.startsWith('/') ? trimmed.slice(1) : trimmed\n }\n return absPath\n}\n","import type { Plugin } from 'vite'\n\nimport type { TheoConfig } from '../config/schema.js'\n\n/**\n * Build context injected by the CLI into adapter.build.\n *\n * - `makeVitePlugins` — optional factory provided by CLI for adapters\n * that drive `viteBuild()` directly (currently: `nodeAdapter`).\n * When omitted, adapters that need Vite must fail with an actionable\n * error. This inverts the previous direct edge `adapters → vite-plugin`\n * per ADR-0001 v3 (T1.1 of the architecture-cleanup plan).\n *\n * The factory MAY return a Promise — `theokit build` uses\n * `theoPluginAsync` to wire the full Plugin[] chain (actions virtual\n * module + typed client + services + @theokit/ui auto-chain) so adapters\n * MUST `await` the result. See `cli/commands/build.ts` for the canonical\n * invocation.\n */\nexport interface AdapterBuildContext {\n makeVitePlugins?: (opts: { root: string; ssr?: boolean }) => Plugin[] | Promise<Plugin[]>\n}\n\nexport interface DeployAdapter {\n name: string\n build(config: TheoConfig, cwd: string, ctx?: AdapterBuildContext): Promise<void>\n}\n\nexport type BuildTarget =\n | 'node'\n | 'vercel'\n | 'cloudflare'\n | 'static'\n | 'bun'\n | 'deno-deploy'\n | 'netlify'\n | 'aws-lambda'\n | 'theo-cloud'\n\nexport const VALID_TARGETS: BuildTarget[] = [\n 'node',\n 'vercel',\n 'cloudflare',\n 'static',\n 'bun',\n 'deno-deploy',\n 'netlify',\n 'aws-lambda',\n 'theo-cloud',\n]\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time adapter translators: caller-controlled paths only.\n */\nimport { existsSync, readFileSync } from 'node:fs'\n\nimport { writeAtomic } from '../_internal/atomic-write.js'\n\nimport type { CronManifestEntry } from './cron-manifest.js'\n\n/**\n * Thrown when an existing platform-config file (vercel.json, wrangler.toml,\n * serverless.yml) cannot be parsed. Caller must fix the file before\n * re-running `theokit build`. We NEVER silently overwrite a user's config.\n */\nexport class ExistingConfigUnparseableError extends Error {\n readonly code = 'EXISTING_CONFIG_UNPARSEABLE'\n constructor(\n public readonly filePath: string,\n public readonly parseError: string,\n ) {\n super(\n `Existing config \"${filePath}\" could not be parsed: ${parseError}. ` +\n 'Fix or remove the file before re-running build — TheoKit never silently overwrites user configuration.',\n )\n this.name = 'ExistingConfigUnparseableError'\n }\n}\n\n// ──────────────────────────────────────────────────────────\n// Vercel — vercel.json crons[]\n// ──────────────────────────────────────────────────────────\n\ninterface VercelJson {\n crons?: { path: string; schedule: string }[]\n [key: string]: unknown\n}\n\n/**\n * Translate a TheoKit cron manifest into `vercel.json crons[]`.\n *\n * EC-105: existing fields (functions, headers, redirects, rewrites, env,\n * etc.) are preserved verbatim — ONLY the `crons[]` slice is replaced.\n */\nexport function translateCronToVercel(\n vercelJsonPath: string,\n crons: readonly CronManifestEntry[],\n): void {\n let existing: VercelJson = {}\n if (existsSync(vercelJsonPath)) {\n const raw = readFileSync(vercelJsonPath, 'utf8')\n try {\n existing = JSON.parse(raw) as VercelJson\n } catch (err) {\n throw new ExistingConfigUnparseableError(\n vercelJsonPath,\n err instanceof Error ? err.message : String(err),\n )\n }\n }\n const merged: VercelJson = {\n ...existing,\n crons: crons.map((c) => ({\n path: `/api/__crons/${c.name}`,\n schedule: c.schedule,\n })),\n }\n writeAtomic(vercelJsonPath, JSON.stringify(merged, null, 2))\n}\n\n// ──────────────────────────────────────────────────────────\n// Cloudflare — wrangler.toml [triggers] crons\n// ──────────────────────────────────────────────────────────\n\n/**\n * Translate a TheoKit cron manifest into `wrangler.toml [triggers] crons`.\n *\n * EC-105: regex-based mutation that preserves comments + other sections\n * verbatim. Replaces only the `[triggers]` block (or appends if absent).\n */\nexport function translateCronToCloudflare(\n wranglerTomlPath: string,\n crons: readonly CronManifestEntry[],\n): void {\n const schedules = crons.map((c) => `\"${c.schedule}\"`).join(', ')\n const triggersBlock = `[triggers]\\ncrons = [${schedules}]\\n`\n\n if (!existsSync(wranglerTomlPath)) {\n writeAtomic(wranglerTomlPath, triggersBlock)\n return\n }\n\n const existing = readFileSync(wranglerTomlPath, 'utf8')\n\n // Replace existing [triggers] section if present. Strategy: split into\n // line array, find the [triggers] header line, drop subsequent lines\n // until the next section header (line starting with `[`) or EOF.\n const lines = existing.split('\\n')\n const triggersIdx = lines.findIndex((l) => l.trim() === '[triggers]')\n\n let next: string\n if (triggersIdx !== -1) {\n let endIdx = lines.length\n for (let i = triggersIdx + 1; i < lines.length; i++) {\n if (lines[i].trim().startsWith('[')) {\n endIdx = i\n break\n }\n }\n const before = lines.slice(0, triggersIdx).join('\\n')\n const after = lines.slice(endIdx).join('\\n')\n next = `${before}\\n${triggersBlock.trimEnd()}\\n${after}`.replace(/\\n{3,}/g, '\\n\\n')\n } else {\n next = existing.endsWith('\\n')\n ? `${existing}\\n${triggersBlock}`\n : `${existing}\\n\\n${triggersBlock}`\n }\n writeAtomic(wranglerTomlPath, next)\n}\n\n// ──────────────────────────────────────────────────────────\n// AWS Lambda — serverless.yml functions.<fn>.events: schedule\n// ──────────────────────────────────────────────────────────\n\n/**\n * Convert TheoKit's 5-field UTC cron to AWS EventBridge's 6-field format.\n * EventBridge requires `?` in EITHER day-of-month OR day-of-week (not both `*`).\n *\n * Algorithm:\n * - If DOM is \"*\" and DOW is \"*\" → insert ? in DOW (default)\n * - If DOM is \"*\" and DOW is specific → insert ? in DOM\n * - If DOM is specific and DOW is \"*\" → insert ? in DOW\n * - Append \"*\" year field at end.\n */\nexport function convertToAwsCron(schedule: string): string {\n const parts = schedule.trim().split(/\\s+/)\n if (parts.length !== 5) {\n throw new Error(`Invalid 5-field schedule for AWS conversion: \"${schedule}\"`)\n }\n const [minute, hour, dom, month, dow] = parts\n let awsDom = dom\n let awsDow = dow\n if (dom === '*' && dow === '*') {\n awsDow = '?'\n } else if (dom === '*') {\n awsDom = '?'\n } else if (dow === '*') {\n awsDow = '?'\n }\n return `cron(${minute} ${hour} ${awsDom} ${month} ${awsDow} *)`\n}\n\n/**\n * Translate a TheoKit cron manifest into a `serverless.yml` functions\n * map with `events: - schedule: cron(...)` entries.\n *\n * EC-105: appends to `functions:` block, preserving all existing\n * functions/sections. Cron functions are named `cron_<name>` to avoid\n * collision with user-declared functions.\n */\nexport function translateCronToAws(\n serverlessYmlPath: string,\n crons: readonly CronManifestEntry[],\n): void {\n const cronFunctionsYaml = crons\n .map(\n (c) => ` cron_${c.name}:\n handler: .theokit/server/crons/${c.name}.handler\n events:\n - schedule: ${convertToAwsCron(c.schedule)}`,\n )\n .join('\\n')\n\n const block = `\\nfunctions:\\n${cronFunctionsYaml}\\n`\n\n if (!existsSync(serverlessYmlPath)) {\n // Minimal serverless.yml scaffold\n writeAtomic(\n serverlessYmlPath,\n `service: theokit-app\\nprovider:\\n name: aws\\n runtime: nodejs22.x\\n${block}`,\n )\n return\n }\n\n const existing = readFileSync(serverlessYmlPath, 'utf8')\n\n // Look for an existing top-level `functions:` block; if present, append our\n // cron entries under it (don't replace user-declared functions).\n const functionsRe = /^functions:\\s*\\n/m\n let next: string\n if (functionsRe.test(existing)) {\n next = existing.replace(functionsRe, (match) => `${match}${cronFunctionsYaml}\\n`)\n } else {\n next = existing.endsWith('\\n') ? `${existing}${block}` : `${existing}\\n${block}`\n }\n writeAtomic(serverlessYmlPath, next)\n}\n\n// ──────────────────────────────────────────────────────────\n// Deno Deploy — Deno.cron registrations\n// ──────────────────────────────────────────────────────────\n\n/**\n * Emit a Deno entry file that registers each cron via `Deno.cron`.\n *\n * Unlike Vercel/CF/AWS, Deno.cron is an in-process runtime API — the\n * entry file is a managed artifact (overwritten each build), not a user\n * config (so EC-105 doesn't apply here).\n */\nexport function translateCronToDeno(entryPath: string, crons: readonly CronManifestEntry[]): void {\n const lines = crons.map(\n (c) =>\n `Deno.cron(\"${c.name}\", \"${c.schedule}\", async () => {\n const mod = await import(\"../${c.filePath}\");\n const def = mod.default;\n await def.handler({\n traceId: crypto.randomUUID().replace(/-/g, \"\"),\n scheduledAt: new Date(),\n signal: AbortSignal.timeout(60_000),\n });\n});`,\n )\n const content = `// AUTOGENERATED by TheoKit cron build — DO NOT EDIT.\n// Source: server/crons/*.ts → .theokit/crons.json → this file.\n${lines.join('\\n\\n')}\n`\n writeAtomic(entryPath, content)\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time atomic write: caller-controlled paths only. No HTTP input\n * reaches these fs calls.\n */\n// T5a.1b — Web Crypto migration. Build-time only; node:fs + node:path stay\n// because this is a manifest-write leaf (e.g. .theokit/jobs.json) and per\n// ADR-0028 the runtime-portable boundary is the request handler, not the\n// scanner. Only node:crypto is swapped out — Web Crypto's\n// getRandomValues works in every supported runtime.\nimport { mkdirSync, renameSync, writeFileSync } from 'node:fs'\nimport { dirname } from 'node:path'\n\n/**\n * Write `content` to `path` atomically via the tmp + rename pattern.\n *\n * Two concurrent calls to `writeAtomic(path, ...)` are guaranteed to\n * leave `path` containing valid content from ONE of the calls (never\n * truncated, never interleaved). POSIX rename is atomic on the same\n * filesystem.\n *\n * EC-106 (jobs-crons-webhooks-cost-tracking-plan) — shared helper for\n * `.theokit/crons.json` and `.theokit/jobs.json` manifest writes so a\n * concurrent dev-server scan + build manifest emit never produces\n * partial JSON.\n *\n * @param path destination path\n * @param content bytes (UTF-8 string) to write\n */\nexport function writeAtomic(path: string, content: string): void {\n mkdirSync(dirname(path), { recursive: true })\n // Include a random suffix so two concurrent writes don't trample each\n // other's tmp file. Web Crypto getRandomValues is non-blocking +\n // collision-safe for the tiny entropy we need (8 hex chars = 32 bits).\n // Hex-encoded manually to avoid Buffer (runtime-agnostic per ADR-0028).\n const randBuf = new Uint8Array(4)\n globalThis.crypto.getRandomValues(randBuf)\n let suffix = ''\n for (const b of randBuf) suffix += b.toString(16).padStart(2, '0')\n const tmp = `${path}.tmp-${process.pid}-${suffix}`\n writeFileSync(tmp, content, 'utf8')\n renameSync(tmp, path)\n}\n","import { writeAtomic } from '../_internal/atomic-write.js'\n\nimport type { CronNode } from './cron-scan.js'\n\nexport const CRON_MANIFEST_SCHEMA_VERSION = 1 as const\n\nexport interface CronManifestEntry {\n readonly name: string\n readonly filePath: string\n readonly schedule: string\n readonly concurrency: 'forbid' | 'allow'\n}\n\nexport interface CronManifest {\n readonly schemaVersion: typeof CRON_MANIFEST_SCHEMA_VERSION\n readonly generatedAt: string\n readonly crons: readonly CronManifestEntry[]\n}\n\n/**\n * Build a `CronManifest` from scanned `CronNode[]`. Filepaths are kept\n * relative to the caller's project root for portability.\n */\nexport function buildCronManifest(nodes: readonly CronNode[], projectRoot?: string): CronManifest {\n const crons: CronManifestEntry[] = nodes.map((n) => ({\n name: n.name,\n filePath: projectRoot ? relativize(n.filePath, projectRoot) : n.filePath,\n schedule: n.schedule,\n concurrency: n.concurrency,\n }))\n return {\n schemaVersion: CRON_MANIFEST_SCHEMA_VERSION,\n generatedAt: new Date().toISOString(),\n crons,\n }\n}\n\n/**\n * Write the cron manifest to `path` atomically (EC-106).\n *\n * Accepts either a pre-built `CronManifest` OR an array of `CronNode[]`\n * (which gets converted via `buildCronManifest`). The atomic-write\n * helper guarantees `path` always contains valid JSON, even under\n * concurrent writes (e.g., dev-server rescan + build).\n */\nexport function writeCronManifest(\n path: string,\n input: readonly CronNode[] | CronManifest,\n projectRoot?: string,\n): void {\n const manifest: CronManifest = isManifest(input) ? input : buildCronManifest(input, projectRoot)\n writeAtomic(path, JSON.stringify(manifest, null, 2))\n}\n\nfunction isManifest(value: unknown): value is CronManifest {\n return typeof value === 'object' && value !== null && 'schemaVersion' in value && 'crons' in value\n}\n\nfunction relativize(absPath: string, root: string): string {\n if (absPath.startsWith(root)) {\n const trimmed = absPath.slice(root.length)\n return trimmed.startsWith('/') ? trimmed.slice(1) : trimmed\n }\n return absPath\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: caller-controlled directory paths only. No HTTP\n * input ever reaches these fs calls.\n */\nimport { existsSync } from 'node:fs'\nimport { basename } from 'node:path'\n\nimport { importUserModule } from '../../config/import-user-module.js'\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nimport type { CronConcurrencyPolicy, CronDefinition } from './cron-types.js'\n\n/**\n * One discovered cron from build-time scan. The handler is intentionally\n * NOT included — manifest is platform-neutral and consumed by adapters\n * that emit static config. Runtime dispatch loads the handler at fire time.\n */\nexport interface CronNode {\n readonly name: string\n readonly filePath: string\n readonly schedule: string\n readonly concurrency: CronConcurrencyPolicy\n}\n\nexport class DuplicateCronNameError extends Error {\n readonly code = 'DUPLICATE_CRON_NAME'\n constructor(\n public readonly cronName: string,\n public readonly filePaths: readonly string[],\n ) {\n super(\n `Duplicate cron name \"${cronName}\" defined in: ${filePaths.join(', ')}. ` +\n 'Cron names must be unique across server/crons/.',\n )\n this.name = 'DuplicateCronNameError'\n }\n}\n\nconst CRON_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.mjs'])\n\ninterface CronModule {\n default?: unknown\n}\n\nfunction isCronDefinition(value: unknown): value is CronDefinition {\n if (typeof value !== 'object' || value === null) return false\n const def = value as Record<string, unknown>\n return (\n typeof def.name === 'string' &&\n typeof def.schedule === 'string' &&\n typeof def.handler === 'function' &&\n (def.concurrency === 'forbid' || def.concurrency === 'allow')\n )\n}\n\n/**\n * Scan a directory for cron definition files and return the discovered\n * `CronNode[]` sorted by name. Throws `DuplicateCronNameError` on name\n * collision and `Error` on missing default export.\n *\n * Sequential by design — module imports are awaited one-by-one to keep\n * error messages anchored to the file that failed.\n */\nexport async function scanCrons(cronsDir: string): Promise<CronNode[]> {\n if (!existsSync(cronsDir)) return []\n\n const filePaths: string[] = []\n walkSourceFiles(cronsDir, { extensions: CRON_EXTENSIONS }, (p) => {\n const base = basename(p)\n // Skip private helpers (`_helper.ts`) and OS junk (`.DS_Store`).\n if (base.startsWith('_') || base.startsWith('.')) return\n filePaths.push(p)\n })\n\n const nodes: CronNode[] = []\n for (const filePath of filePaths) {\n let mod: CronModule\n try {\n mod = await importUserModule(filePath)\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err)\n throw new Error(`Failed to import cron file \"${filePath}\": ${reason}`)\n }\n const exported = mod.default\n if (!isCronDefinition(exported)) {\n throw new Error(\n `Cron file \"${filePath}\" is missing a valid default export. ` +\n 'Expected `export default defineCron(name, { schedule, handler })`.',\n )\n }\n nodes.push({\n name: exported.name,\n filePath,\n schedule: exported.schedule,\n concurrency: exported.concurrency,\n })\n }\n\n // Dup-name detection\n const byName = new Map<string, string[]>()\n for (const node of nodes) {\n const bucket = byName.get(node.name) ?? []\n bucket.push(node.filePath)\n byName.set(node.name, bucket)\n }\n for (const [name, paths] of byName) {\n if (paths.length > 1) {\n throw new DuplicateCronNameError(name, paths)\n }\n }\n\n return nodes.sort((a, b) => a.name.localeCompare(b.name))\n}\n","import { writeAtomic } from '../_internal/atomic-write.js'\n\nimport type { JobNode } from './job-scan.js'\n\nexport const JOB_MANIFEST_SCHEMA_VERSION = 1 as const\n\nexport interface JobManifestEntry {\n readonly name: string\n readonly filePath: string\n readonly maxAttempts: number\n readonly hasInputSchema: boolean\n}\n\nexport interface JobManifest {\n readonly schemaVersion: typeof JOB_MANIFEST_SCHEMA_VERSION\n readonly generatedAt: string\n readonly jobs: readonly JobManifestEntry[]\n}\n\nexport function buildJobManifest(nodes: readonly JobNode[], projectRoot?: string): JobManifest {\n const jobs: JobManifestEntry[] = nodes.map((n) => ({\n name: n.name,\n filePath: projectRoot ? relativize(n.filePath, projectRoot) : n.filePath,\n maxAttempts: n.maxAttempts,\n hasInputSchema: n.hasInputSchema,\n }))\n return {\n schemaVersion: JOB_MANIFEST_SCHEMA_VERSION,\n generatedAt: new Date().toISOString(),\n jobs,\n }\n}\n\nexport function writeJobManifest(\n path: string,\n input: readonly JobNode[] | JobManifest,\n projectRoot?: string,\n): void {\n const manifest: JobManifest = isManifest(input) ? input : buildJobManifest(input, projectRoot)\n writeAtomic(path, JSON.stringify(manifest, null, 2))\n}\n\nfunction isManifest(value: unknown): value is JobManifest {\n return typeof value === 'object' && value !== null && 'schemaVersion' in value && 'jobs' in value\n}\n\nfunction relativize(absPath: string, root: string): string {\n if (absPath.startsWith(root)) {\n const trimmed = absPath.slice(root.length)\n return trimmed.startsWith('/') ? trimmed.slice(1) : trimmed\n }\n return absPath\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: caller-controlled directory paths only.\n */\nimport { existsSync } from 'node:fs'\nimport { basename } from 'node:path'\n\nimport { importUserModule } from '../../config/import-user-module.js'\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nimport type { JobDefinition } from './job-types.js'\n\nexport interface JobNode {\n readonly name: string\n readonly filePath: string\n readonly maxAttempts: number\n readonly hasInputSchema: boolean\n}\n\nexport class DuplicateJobNameError extends Error {\n readonly code = 'DUPLICATE_JOB_NAME'\n constructor(\n public readonly jobName: string,\n public readonly filePaths: readonly string[],\n ) {\n super(\n `Duplicate job name \"${jobName}\" defined in: ${filePaths.join(', ')}. ` +\n 'Job names must be unique across server/jobs/.',\n )\n this.name = 'DuplicateJobNameError'\n }\n}\n\nconst JOB_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.mjs'])\n\ninterface JobModule {\n default?: unknown\n}\n\nfunction isJobDefinition(value: unknown): value is JobDefinition {\n if (typeof value !== 'object' || value === null) return false\n const def = value as Record<string, unknown>\n return (\n typeof def.name === 'string' &&\n typeof def.handler === 'function' &&\n typeof def.maxAttempts === 'number' &&\n typeof def.hasInputSchema === 'boolean'\n )\n}\n\nexport async function scanJobs(jobsDir: string): Promise<JobNode[]> {\n if (!existsSync(jobsDir)) return []\n\n const filePaths: string[] = []\n walkSourceFiles(jobsDir, { extensions: JOB_EXTENSIONS }, (p) => {\n const base = basename(p)\n if (base.startsWith('_') || base.startsWith('.')) return\n filePaths.push(p)\n })\n\n const nodes: JobNode[] = []\n for (const filePath of filePaths) {\n let mod: JobModule\n try {\n mod = await importUserModule(filePath)\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err)\n throw new Error(`Failed to import job file \"${filePath}\": ${reason}`)\n }\n const exported = mod.default\n if (!isJobDefinition(exported)) {\n throw new Error(\n `Job file \"${filePath}\" is missing a valid default export. ` +\n 'Expected `export default defineJob(name, { handler })`.',\n )\n }\n nodes.push({\n name: exported.name,\n filePath,\n maxAttempts: exported.maxAttempts,\n hasInputSchema: exported.hasInputSchema,\n })\n }\n\n const byName = new Map<string, string[]>()\n for (const node of nodes) {\n const bucket = byName.get(node.name) ?? []\n bucket.push(node.filePath)\n byName.set(node.name, bucket)\n }\n for (const [name, paths] of byName) {\n if (paths.length > 1) {\n throw new DuplicateJobNameError(name, paths)\n }\n }\n\n return nodes.sort((a, b) => a.name.localeCompare(b.name))\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Cleanup helpers. Paths are caller-controlled (CLI/config); EC-3 path-safety\n * guard rejects absolute paths outside cwd to prevent catastrophic data loss.\n */\n/**\n * State cleanup utilities (Phase 2 of\n * docs/plans/framework-zero-config-polish-plan.md).\n *\n * - `cleanOutDir` — Astro pattern: empty a directory except a skip list,\n * used at `theokit build` start.\n * - `gcAgentRegistry` — Nuxt LRU pattern: delete oldest agent dirs when\n * count exceeds cap, used at `theokit dev` startup.\n *\n * EC-3 (MUST FIX, CRITICAL): cleanOutDir refuses to wipe anything outside\n * the current cwd. Prevents catastrophic `distDir: '/'` data loss.\n * EC-9 (SHOULD TEST): gcAgentRegistry handles dirs with mtime=0 (Docker overlay).\n * EC-11 (SHOULD TEST): cleanOutDir normalizes trailing-slash in skip basenames.\n * EC-12 (SHOULD TEST): cleanOutDir catches EROFS and continues.\n */\n\nimport { promises as fs } from 'node:fs'\nimport { basename, resolve as resolvePath, sep } from 'node:path'\n\nimport type {\n CleanOutDirOptions,\n CleanOutDirResult,\n GcAgentRegistryOptions,\n GcAgentRegistryResult,\n} from './cleanup-types.js'\n\nconst DEFAULT_SKIP = ['.git', '.gitkeep', '.gitignore']\n// DEFAULT_MAX_AGENTS removed in Phase 7 — gcAgentRegistry is a tombstone.\n\nfunction normalizeSkipName(name: string): string {\n // EC-11 — strip trailing slash + leading `./`\n return basename(name.replace(/\\/$/, ''))\n}\n\n/**\n * Empty a directory, preserving entries in the skip list.\n *\n * Throws if `opts.dir` is not inside `process.cwd()` (EC-3 path safety).\n * Returns `{deleted:0, kept:0}` when the directory does not exist.\n */\nexport async function cleanOutDir(opts: CleanOutDirOptions): Promise<CleanOutDirResult> {\n const resolvedDir = resolvePath(opts.dir)\n const resolvedCwd = resolvePath(process.cwd())\n\n // EC-3 — path safety guard. CRITICAL.\n if (resolvedDir === resolvedCwd) {\n throw new Error(\n `cleanOutDir refused to wipe ${resolvedDir} — must be a child of cwd, not cwd itself`,\n )\n }\n if (!resolvedDir.startsWith(resolvedCwd + sep)) {\n throw new Error(\n `cleanOutDir refused to wipe ${resolvedDir} — must be inside cwd (${resolvedCwd})`,\n )\n }\n\n const skip = new Set((opts.skip ?? DEFAULT_SKIP).map(normalizeSkipName))\n\n let entries: { name: string }[]\n try {\n entries = await fs.readdir(resolvedDir, { withFileTypes: true })\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code === 'ENOENT') return { deleted: 0, kept: 0 }\n throw err\n }\n\n let deleted = 0\n let kept = 0\n for (const entry of entries) {\n const name = entry.name\n if (skip.has(name)) {\n kept++\n continue\n }\n const fullPath = resolvePath(resolvedDir, name)\n try {\n await fs.rm(fullPath, { recursive: true, force: true, maxRetries: 3 })\n deleted++\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code === 'ENOENT') {\n deleted++ // already gone\n continue\n }\n // EC-12 — EROFS or other rm failure: warn + continue, don't crash.\n console.warn(\n `[theokit] cleanOutDir could not remove ${fullPath} (${code ?? 'unknown'}); skipping`,\n )\n kept++\n }\n }\n\n return { deleted, kept }\n}\n\n/**\n * Phase 7 — TOMBSTONE for backward-compat. The SDK v1.1.0's `Agent.registry`\n * handles GC natively; this function is a no-op + emits a deprecation warning\n * ONCE per process (EC-10).\n *\n * Will be deleted entirely in TheoKit 0.4.0. Marked deprecated via runtime\n * warning rather than `@deprecated` JSDoc tag to avoid the eslint\n * sonarjs/deprecation flagging our own internal symbols.\n */\nlet warnedOnce = false\nexport async function gcAgentRegistry(\n _opts: GcAgentRegistryOptions,\n): Promise<GcAgentRegistryResult> {\n if (!warnedOnce) {\n warnedOnce = true\n console.warn(\n '[theokit] gcAgentRegistry is deprecated; SDK Agent.registry handles GC natively (configure via theo.config.ts > agents.registry)',\n )\n }\n return Promise.resolve({ deleted: 0, kept: 0 })\n}\n\n/**\n * @internal — testing helper. Resets the module-scoped warnedOnce flag.\n */\nexport function __resetGcDeprecationWarnedForTests(): void {\n warnedOnce = false\n}\n"],"mappings":"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","names":["existsSync","existsSync","existsSync","relativize","isManifest","existsSync","basename","existsSync","basename","basename","writeManifest","existsSync","relativize"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config/validate-structure.ts","../src/core/errors.ts"],"sourcesContent":["/* 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 '../core/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":";;;;AAIA,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":[]}
|