theokit 0.5.4 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/dist/{actions-virtual-module-DL74V6SR.js → actions-virtual-module-4WVKHQ2X.js} +5 -2
  2. package/dist/actions-virtual-module-4WVKHQ2X.js.map +1 -0
  3. package/dist/{actions-virtual-module-ZDD54ZZE.js → actions-virtual-module-LTOXBSCF.js} +16 -2
  4. package/dist/actions-virtual-module-LTOXBSCF.js.map +1 -0
  5. package/dist/{app-typed-client-4M4NVSK3.js → app-typed-client-4N5OGNOQ.js} +15 -4
  6. package/dist/app-typed-client-4N5OGNOQ.js.map +1 -0
  7. package/dist/{app-typed-client-4GIKUKRT.js → app-typed-client-SAETWZT5.js} +5 -4
  8. package/dist/app-typed-client-SAETWZT5.js.map +1 -0
  9. package/dist/{aws-lambda-SCRGTGF5.js → aws-lambda-WT4HRBNL.js} +3 -3
  10. package/dist/{build-R74EU6BP.js → build-KQD2EDU4.js} +10 -11
  11. package/dist/build-KQD2EDU4.js.map +1 -0
  12. package/dist/{bun-FURRCQMS.js → bun-UTDVN6R4.js} +3 -3
  13. package/dist/{check-NXYPLM6M.js → check-F3UDYSZ4.js} +2 -2
  14. package/dist/chunk-223EFY5X.js +469 -0
  15. package/dist/chunk-223EFY5X.js.map +1 -0
  16. package/dist/{chunk-KJLECZWQ.js → chunk-3FYJ7R7N.js} +57 -14
  17. package/dist/chunk-3FYJ7R7N.js.map +1 -0
  18. package/dist/{chunk-HDA6M7P4.js → chunk-3LVRAGAZ.js} +3 -5
  19. package/dist/{chunk-JD7AQNJS.js → chunk-4I47JW5O.js} +38 -425
  20. package/dist/chunk-4I47JW5O.js.map +1 -0
  21. package/dist/{chunk-H4J2LECY.js → chunk-6BUUZ2PK.js} +9 -9
  22. package/dist/chunk-6BUUZ2PK.js.map +1 -0
  23. package/dist/{chunk-P2RS3WWY.js → chunk-6FYD34NX.js} +194 -22
  24. package/dist/chunk-6FYD34NX.js.map +1 -0
  25. package/dist/{chunk-X3VNROZ7.js → chunk-6VSKLYT6.js} +78 -21
  26. package/dist/chunk-6VSKLYT6.js.map +1 -0
  27. package/dist/{chunk-FGOX37NB.js → chunk-BNGBG2SQ.js} +21 -2
  28. package/dist/chunk-BNGBG2SQ.js.map +1 -0
  29. package/dist/chunk-CBGRFVF5.js +421 -0
  30. package/dist/chunk-CBGRFVF5.js.map +1 -0
  31. package/dist/{chunk-X32KQH5C.js → chunk-CCVC6P6B.js} +3 -3
  32. package/dist/chunk-CCVC6P6B.js.map +1 -0
  33. package/dist/{chunk-DNMSV3R3.js → chunk-D7ZH7DTG.js} +3 -3
  34. package/dist/chunk-D7ZH7DTG.js.map +1 -0
  35. package/dist/{chunk-MWYSRZI4.js → chunk-EQV67HZL.js} +2 -2
  36. package/dist/{chunk-CG563V5M.js → chunk-IEES3CHD.js} +39 -5
  37. package/dist/chunk-IEES3CHD.js.map +1 -0
  38. package/dist/{chunk-DUKXQNM7.js → chunk-IHMJV2OK.js} +2 -2
  39. package/dist/{chunk-WGHJD6I7.js → chunk-JAIKGP3Q.js} +76 -21
  40. package/dist/chunk-JAIKGP3Q.js.map +1 -0
  41. package/dist/{chunk-B3L3TJVF.js → chunk-JBCHWRKF.js} +50 -50
  42. package/dist/chunk-JBCHWRKF.js.map +1 -0
  43. package/dist/{chunk-XMS5VRIK.js → chunk-JQSFBJR5.js} +3 -3
  44. package/dist/chunk-JQSFBJR5.js.map +1 -0
  45. package/dist/{chunk-S74FECKW.js → chunk-MV4LKTW6.js} +119 -153
  46. package/dist/chunk-MV4LKTW6.js.map +1 -0
  47. package/dist/chunk-PBEH6NXR.js +44 -0
  48. package/dist/chunk-PBEH6NXR.js.map +1 -0
  49. package/dist/chunk-PPPR5DGR.js +1 -0
  50. package/dist/{chunk-NZXH2LQQ.js → chunk-SJIRP73B.js} +18 -1
  51. package/dist/chunk-SJIRP73B.js.map +1 -0
  52. package/dist/cli/index.js +25 -10
  53. package/dist/cli/index.js.map +1 -1
  54. package/dist/client/index.d.ts +4 -4
  55. package/dist/{cloudflare-IZ6VJ2OU.js → cloudflare-U5GYYI47.js} +3 -3
  56. package/dist/db-ZPDW3UCU.js +78 -0
  57. package/dist/db-ZPDW3UCU.js.map +1 -0
  58. package/dist/{deno-deploy-O73NBXIW.js → deno-deploy-3QHJ3AJQ.js} +3 -3
  59. package/dist/{dev-ZU46C5AZ.js → dev-NDEZA2MP.js} +21 -11
  60. package/dist/dev-NDEZA2MP.js.map +1 -0
  61. package/dist/{dev-emit-JBVINGHU.js → dev-emit-O4EGOSNV.js} +77 -24
  62. package/dist/dev-emit-O4EGOSNV.js.map +1 -0
  63. package/dist/{dev-emit-66FCOS7V.js → dev-emit-OUPI7NAQ.js} +4 -5
  64. package/dist/{dev-emit-66FCOS7V.js.map → dev-emit-OUPI7NAQ.js.map} +1 -1
  65. package/dist/devtools/entry.js +1 -0
  66. package/dist/devtools/entry.js.map +1 -1
  67. package/dist/{dist-KRW6OVD4.js → dist-6GPD6WCU.js} +10231 -8164
  68. package/dist/dist-6GPD6WCU.js.map +1 -0
  69. package/dist/generate-ODDAYXNR.js +494 -0
  70. package/dist/generate-ODDAYXNR.js.map +1 -0
  71. package/dist/{index-CuHICqb9.d.ts → index-DWZF8uQD.d.ts} +132 -132
  72. package/dist/index.d.ts +32 -3
  73. package/dist/index.js +8 -9
  74. package/dist/index.js.map +1 -1
  75. package/dist/{info-CORLCPEJ.js → info-CSGWIAXA.js} +5 -5
  76. package/dist/{lib-NST3PNZX.js → lib-JBI3XXH3.js} +27 -27
  77. package/dist/{lib-NST3PNZX.js.map → lib-JBI3XXH3.js.map} +1 -1
  78. package/dist/{netlify-O7G3RLK4.js → netlify-DUB7BZ4E.js} +3 -3
  79. package/dist/{node-LXPQLMVB.js → node-ELE236WK.js} +3 -3
  80. package/dist/{openapi-TEOUOIJ2.js → openapi-NXGRXABL.js} +7 -8
  81. package/dist/{openapi-TEOUOIJ2.js.map → openapi-NXGRXABL.js.map} +1 -1
  82. package/dist/registry-B7FA6KMI.js +25 -0
  83. package/dist/{routes-GAXVWJUK.js → routes-6A5XFGF7.js} +7 -9
  84. package/dist/routes-6A5XFGF7.js.map +1 -0
  85. package/dist/{scan-NQFI5S7P.js → scan-I5PT6TUX.js} +2 -2
  86. package/dist/{schema-D3v8VB7o.d.ts → schema-BpH6ivDY.d.ts} +2 -4
  87. package/dist/{schema-Z5VJ2I76.js → schema-NPI4NJEF.js} +3 -3
  88. package/dist/server/auth/index.d.ts +20 -20
  89. package/dist/server/auth/index.js +3 -5
  90. package/dist/server/define/index.d.ts +7 -0
  91. package/dist/server/define/index.js +1 -1
  92. package/dist/server/http/index.d.ts +2 -2
  93. package/dist/server/http/index.js +1 -2
  94. package/dist/server/index.d.ts +36 -3
  95. package/dist/server/index.js +66 -61
  96. package/dist/server/index.js.map +1 -1
  97. package/dist/server/scan/index.d.ts +13 -13
  98. package/dist/server/scan/index.js +8 -12
  99. package/dist/server/security/index.d.ts +69 -69
  100. package/dist/server/security/index.js +1 -1
  101. package/dist/{services-typed-client-ASZL7FQV.js → services-typed-client-24VBMDOF.js} +2 -2
  102. package/dist/{services-typed-client-KK6RHRDG.js → services-typed-client-IETY2SAK.js} +2 -2
  103. package/dist/{start-HGUNNF5X.js → start-SPFWOGHL.js} +95 -80
  104. package/dist/start-SPFWOGHL.js.map +1 -0
  105. package/dist/{static-EO73I4C7.js → static-YWF2M6YT.js} +4 -4
  106. package/dist/{theo-cloud-HBCYEODQ.js → theo-cloud-EUK56I7O.js} +2 -2
  107. package/dist/{vercel-GSFDMF7K.js → vercel-LGDQWYZD.js} +3 -3
  108. package/dist/vite-plugin/index.d.ts +1 -1
  109. package/dist/vite-plugin/index.js +6 -7
  110. package/dist/{vite-plugin-TX5H4MB6.js → vite-plugin-43KQU5S7.js} +11 -9
  111. package/dist/vite-plugin-43KQU5S7.js.map +1 -0
  112. package/package.json +22 -19
  113. package/dist/actions-virtual-module-DL74V6SR.js.map +0 -1
  114. package/dist/actions-virtual-module-ZDD54ZZE.js.map +0 -1
  115. package/dist/app-typed-client-4GIKUKRT.js.map +0 -1
  116. package/dist/app-typed-client-4M4NVSK3.js.map +0 -1
  117. package/dist/build-R74EU6BP.js.map +0 -1
  118. package/dist/chunk-4HBI7DHP.js +0 -20
  119. package/dist/chunk-4HBI7DHP.js.map +0 -1
  120. package/dist/chunk-B3L3TJVF.js.map +0 -1
  121. package/dist/chunk-CG563V5M.js.map +0 -1
  122. package/dist/chunk-DNMSV3R3.js.map +0 -1
  123. package/dist/chunk-FGOX37NB.js.map +0 -1
  124. package/dist/chunk-GPF64ENM.js +0 -73
  125. package/dist/chunk-H4J2LECY.js.map +0 -1
  126. package/dist/chunk-HDA6M7P4.js.map +0 -1
  127. package/dist/chunk-JD7AQNJS.js.map +0 -1
  128. package/dist/chunk-KJLECZWQ.js.map +0 -1
  129. package/dist/chunk-NZXH2LQQ.js.map +0 -1
  130. package/dist/chunk-OLPB36O2.js +0 -259
  131. package/dist/chunk-OLPB36O2.js.map +0 -1
  132. package/dist/chunk-P2RS3WWY.js.map +0 -1
  133. package/dist/chunk-P3SGM4NR.js +0 -156
  134. package/dist/chunk-P3SGM4NR.js.map +0 -1
  135. package/dist/chunk-S74FECKW.js.map +0 -1
  136. package/dist/chunk-UVHRD33F.js +0 -181
  137. package/dist/chunk-UVHRD33F.js.map +0 -1
  138. package/dist/chunk-WGHJD6I7.js.map +0 -1
  139. package/dist/chunk-X32KQH5C.js.map +0 -1
  140. package/dist/chunk-X3VNROZ7.js.map +0 -1
  141. package/dist/chunk-XMS5VRIK.js.map +0 -1
  142. package/dist/dev-ZU46C5AZ.js.map +0 -1
  143. package/dist/dev-emit-JBVINGHU.js.map +0 -1
  144. package/dist/dist-KRW6OVD4.js.map +0 -1
  145. package/dist/generate-AZ2K6Z2Z.js +0 -281
  146. package/dist/generate-AZ2K6Z2Z.js.map +0 -1
  147. package/dist/registry-KW4F4D2L.js +0 -25
  148. package/dist/routes-GAXVWJUK.js.map +0 -1
  149. package/dist/start-HGUNNF5X.js.map +0 -1
  150. package/dist/{aws-lambda-SCRGTGF5.js.map → aws-lambda-WT4HRBNL.js.map} +0 -0
  151. package/dist/{bun-FURRCQMS.js.map → bun-UTDVN6R4.js.map} +0 -0
  152. package/dist/{check-NXYPLM6M.js.map → check-F3UDYSZ4.js.map} +0 -0
  153. package/dist/{chunk-GPF64ENM.js.map → chunk-3LVRAGAZ.js.map} +0 -0
  154. package/dist/{chunk-MWYSRZI4.js.map → chunk-EQV67HZL.js.map} +0 -0
  155. package/dist/{chunk-DUKXQNM7.js.map → chunk-IHMJV2OK.js.map} +0 -0
  156. package/dist/{node-LXPQLMVB.js.map → chunk-PPPR5DGR.js.map} +0 -0
  157. package/dist/{cloudflare-IZ6VJ2OU.js.map → cloudflare-U5GYYI47.js.map} +0 -0
  158. package/dist/{deno-deploy-O73NBXIW.js.map → deno-deploy-3QHJ3AJQ.js.map} +0 -0
  159. package/dist/{info-CORLCPEJ.js.map → info-CSGWIAXA.js.map} +0 -0
  160. package/dist/{module-loader-Cfz7dgCP.d.ts → match-CfbEFRG4.d.ts} +4 -4
  161. /package/dist/{netlify-O7G3RLK4.js.map → netlify-DUB7BZ4E.js.map} +0 -0
  162. /package/dist/{scan-NQFI5S7P.js.map → node-ELE236WK.js.map} +0 -0
  163. /package/dist/{registry-KW4F4D2L.js.map → registry-B7FA6KMI.js.map} +0 -0
  164. /package/dist/{schema-Z5VJ2I76.js.map → scan-I5PT6TUX.js.map} +0 -0
  165. /package/dist/{vite-plugin-TX5H4MB6.js.map → schema-NPI4NJEF.js.map} +0 -0
  166. /package/dist/{services-typed-client-ASZL7FQV.js.map → services-typed-client-24VBMDOF.js.map} +0 -0
  167. /package/dist/{services-typed-client-KK6RHRDG.js.map → services-typed-client-IETY2SAK.js.map} +0 -0
  168. /package/dist/{static-EO73I4C7.js.map → static-YWF2M6YT.js.map} +0 -0
  169. /package/dist/{theo-cloud-HBCYEODQ.js.map → theo-cloud-EUK56I7O.js.map} +0 -0
  170. /package/dist/{vercel-GSFDMF7K.js.map → vercel-LGDQWYZD.js.map} +0 -0
@@ -1,8 +1,177 @@
1
1
  #!/usr/bin/env node
2
2
  import "tsx/esm";
3
- import {
4
- walkSourceFiles
5
- } from "./chunk-UVHRD33F.js";
3
+
4
+ // src/server/scan/action-scan.ts
5
+ import { existsSync, readFileSync, statSync } from "fs";
6
+ import { extname as extname2, join as join2, relative } from "path";
7
+
8
+ // src/server/_internal/scan-walker.ts
9
+ import { readdirSync } from "fs";
10
+ import { extname, join, resolve } from "path";
11
+ function walkSourceFiles(root, opts, onFile) {
12
+ const skipPrefixes = opts.skipPrefixes ?? ["_", "."];
13
+ const visit = (dir) => {
14
+ let entries;
15
+ try {
16
+ entries = readdirSync(dir, { withFileTypes: true });
17
+ } catch {
18
+ return;
19
+ }
20
+ for (const entry of entries) {
21
+ const fullPath = join(dir, entry.name);
22
+ if (entry.isDirectory() && !skipPrefixes.some((p) => entry.name.startsWith(p))) {
23
+ visit(fullPath);
24
+ } else if (entry.isFile() && opts.extensions.has(extname(entry.name))) {
25
+ onFile(resolve(fullPath));
26
+ }
27
+ }
28
+ };
29
+ visit(root);
30
+ }
31
+
32
+ // src/server/scan/action-scan.ts
33
+ var ActionScanError = class extends Error {
34
+ code;
35
+ conflictingPaths;
36
+ constructor(code, message, conflictingPaths) {
37
+ super(message);
38
+ this.name = "ActionScanError";
39
+ this.code = code;
40
+ this.conflictingPaths = conflictingPaths;
41
+ }
42
+ };
43
+ var ACTION_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
44
+ var TEST_FILE_RE = /\.(test|spec)\.(ts|tsx|js|jsx)$/;
45
+ var RESERVED_NAMES = /* @__PURE__ */ new Set(["index", "constructor", "__proto__", "prototype", "hasOwnProperty"]);
46
+ function scanServerActions(serverDir) {
47
+ const actionsDir = join2(serverDir, "actions");
48
+ if (!existsSync(actionsDir) || !statSync(actionsDir).isDirectory()) {
49
+ return [];
50
+ }
51
+ const results = [];
52
+ walkSourceFiles(actionsDir, { extensions: ACTION_EXTENSIONS }, (absPath) => {
53
+ if (TEST_FILE_RE.test(absPath)) return;
54
+ let rel = relative(actionsDir, absPath);
55
+ rel = rel.replace(/\\/g, "/");
56
+ rel = rel.slice(0, -extname2(rel).length);
57
+ results.push({
58
+ filePath: absPath,
59
+ actionPath: rel
60
+ });
61
+ });
62
+ return results;
63
+ }
64
+ function scanServerActionsEnriched(serverDir) {
65
+ const actionsDir = join2(serverDir, "actions");
66
+ if (!existsSync(actionsDir) || !statSync(actionsDir).isDirectory()) {
67
+ return [];
68
+ }
69
+ const seenNames = /* @__PURE__ */ new Set();
70
+ const entries = [];
71
+ walkSourceFiles(actionsDir, { extensions: ACTION_EXTENSIONS }, (absPath) => {
72
+ if (TEST_FILE_RE.test(absPath)) return;
73
+ const rel = relative(actionsDir, absPath).replace(/\\/g, "/");
74
+ if (rel.startsWith("schemas/")) return;
75
+ const name = rel.slice(0, -extname2(rel).length);
76
+ const basename2 = name.includes("/") ? name.split("/").pop() ?? name : name;
77
+ if (RESERVED_NAMES.has(basename2)) {
78
+ throw new ActionScanError(
79
+ "RESERVED_NAME",
80
+ `Reserved JS identifier "${basename2}" cannot be an action name (${absPath})`,
81
+ [absPath]
82
+ );
83
+ }
84
+ const source = readFileSync(absPath, "utf8");
85
+ const stripped = stripComments(source);
86
+ const accept = /\baccept\s*:\s*['"]form['"]/.test(stripped) ? "form" : "json";
87
+ const hasInput = /\binput\s*:\s*z\./.test(stripped) || /\binput\s*:\s*\w+\(/.test(stripped);
88
+ const exportNames = extractActionExportNames(stripped);
89
+ const schemaFilePath = name.includes("/") ? void 0 : detectSchemaFile(actionsDir, basename2);
90
+ for (const exportName of exportNames) {
91
+ const proxyKey = exportName === "default" ? name : exportName;
92
+ if (seenNames.has(proxyKey)) {
93
+ throw new ActionScanError(
94
+ "NAME_COLLISION",
95
+ `Duplicate action proxy key "${proxyKey}" (two files export the same name)`,
96
+ [absPath]
97
+ );
98
+ }
99
+ seenNames.add(proxyKey);
100
+ const entry = {
101
+ name: proxyKey,
102
+ filePath: absPath,
103
+ urlPath: `/api/__actions/${name}/${exportName}`,
104
+ accept,
105
+ hasInput
106
+ };
107
+ if (schemaFilePath !== void 0) {
108
+ entry.schemaFilePath = schemaFilePath;
109
+ }
110
+ entries.push(entry);
111
+ }
112
+ });
113
+ for (const entry of entries) {
114
+ const childPrefix = `${entry.name}/`;
115
+ const conflictingChild = entries.find((other) => other.name.startsWith(childPrefix));
116
+ if (conflictingChild) {
117
+ throw new ActionScanError(
118
+ "NAME_COLLISION",
119
+ `Action "${entry.name}" conflicts with directory of same name containing "${conflictingChild.name}"`,
120
+ [entry.filePath, conflictingChild.filePath]
121
+ );
122
+ }
123
+ }
124
+ entries.sort((a, b) => {
125
+ if (a.name < b.name) return -1;
126
+ if (a.name > b.name) return 1;
127
+ return 0;
128
+ });
129
+ return entries;
130
+ }
131
+ function extractActionExportNames(stripped) {
132
+ const names = /* @__PURE__ */ new Set();
133
+ const namedRe = /\bexport\s+(?:const|let|var|function\*?)\s+([a-zA-Z_$][\w$]*)\s*[=(]/g;
134
+ let m;
135
+ while ((m = namedRe.exec(stripped)) !== null) {
136
+ const name = m[1];
137
+ if (typeof name === "string" && name.length > 0) names.add(name);
138
+ }
139
+ if (/\bexport\s+default\s+defineAction\b/.test(stripped)) {
140
+ names.add("default");
141
+ }
142
+ if (names.size === 0) names.add("default");
143
+ return [...names];
144
+ }
145
+ function detectSchemaFile(actionsDir, basename2) {
146
+ const schemasDir = join2(actionsDir, "schemas");
147
+ if (!existsSync(schemasDir)) return void 0;
148
+ for (const ext of [".ts", ".tsx", ".js", ".jsx"]) {
149
+ const candidate = join2(schemasDir, `${basename2}${ext}`);
150
+ if (existsSync(candidate)) return candidate;
151
+ }
152
+ return void 0;
153
+ }
154
+ function stripComments(source) {
155
+ let out = "";
156
+ let i = 0;
157
+ while (i < source.length) {
158
+ const ch = source[i];
159
+ const next = source[i + 1];
160
+ if (ch === "/" && next === "/") {
161
+ while (i < source.length && source[i] !== "\n") i++;
162
+ continue;
163
+ }
164
+ if (ch === "/" && next === "*") {
165
+ i += 2;
166
+ while (i < source.length - 1 && !(source[i] === "*" && source[i + 1] === "/")) i++;
167
+ i += 2;
168
+ continue;
169
+ }
170
+ out += ch;
171
+ i++;
172
+ }
173
+ return out;
174
+ }
6
175
 
7
176
  // src/core/contracts/http-methods.ts
8
177
  var HTTP_METHODS = [
@@ -17,11 +186,11 @@ var HTTP_METHODS = [
17
186
  var HTTP_METHOD_LOWERCASE = HTTP_METHODS.map((m) => m.toLowerCase());
18
187
 
19
188
  // src/server/scan/scan.ts
20
- import { existsSync, statSync } from "fs";
21
- import { basename, extname, join, relative } from "path";
189
+ import { existsSync as existsSync2, statSync as statSync2 } from "fs";
190
+ import { basename, extname as extname3, join as join3, relative as relative2 } from "path";
22
191
 
23
192
  // src/server/scan/detect-http-methods.ts
24
- import { readFileSync } from "fs";
193
+ import { readFileSync as readFileSync2 } from "fs";
25
194
  import { createRequire } from "module";
26
195
  var require_ = createRequire(import.meta.url);
27
196
  var ts = require_("typescript");
@@ -57,7 +226,7 @@ function collectFromStatement(stmt, found) {
57
226
  }
58
227
  }
59
228
  function detectExportedHttpMethods(filePath, content) {
60
- const src = content ?? readFileSync(filePath, "utf-8");
229
+ const src = content ?? readFileSync2(filePath, "utf-8");
61
230
  const sourceFile = ts.createSourceFile(
62
231
  filePath,
63
232
  src,
@@ -162,15 +331,15 @@ function splitDottedSegmentOutsideBrackets(segment) {
162
331
  return parts;
163
332
  }
164
333
  function buildDirectoryNestedSuggestion(filePath, routesDir) {
165
- const rel = relative(routesDir, filePath).replace(/\\/g, "/");
166
- const ext = extname(rel);
334
+ const rel = relative2(routesDir, filePath).replace(/\\/g, "/");
335
+ const ext = extname3(rel);
167
336
  const withoutExt = rel.slice(0, -ext.length);
168
337
  const segments = withoutExt.split("/").flatMap(splitDottedSegmentOutsideBrackets);
169
338
  return `routes/${segments.join("/")}${ext}`;
170
339
  }
171
340
  function assertNoDottedSegment(filePath, routesDir) {
172
- const rel = relative(routesDir, filePath).replace(/\\/g, "/");
173
- const ext = extname(rel);
341
+ const rel = relative2(routesDir, filePath).replace(/\\/g, "/");
342
+ const ext = extname3(rel);
174
343
  const withoutExt = rel.slice(0, -ext.length);
175
344
  const segments = withoutExt.split("/");
176
345
  for (const seg of segments) {
@@ -183,8 +352,8 @@ function assertNoDottedSegment(filePath, routesDir) {
183
352
  }
184
353
  }
185
354
  function fileToRoutePath(filePath, routesDir) {
186
- let rel = relative(routesDir, filePath);
187
- const ext = extname(rel);
355
+ let rel = relative2(routesDir, filePath);
356
+ const ext = extname3(rel);
188
357
  rel = rel.slice(0, -ext.length);
189
358
  rel = rel.replace(/\\/g, "/");
190
359
  if (rel.endsWith("/index")) {
@@ -197,8 +366,8 @@ function fileToRoutePath(filePath, routesDir) {
197
366
  return `/api/${rel}`;
198
367
  }
199
368
  function scanServerRoutes(serverDir) {
200
- const routesDir = join(serverDir, "routes");
201
- if (!existsSync(routesDir) || !statSync(routesDir).isDirectory()) {
369
+ const routesDir = join3(serverDir, "routes");
370
+ if (!existsSync2(routesDir) || !statSync2(routesDir).isDirectory()) {
202
371
  return [];
203
372
  }
204
373
  const results = [];
@@ -238,19 +407,19 @@ function scanServerRoutes(serverDir) {
238
407
  }
239
408
 
240
409
  // src/server/scan/ws-scan.ts
241
- import { existsSync as existsSync2, statSync as statSync2 } from "fs";
242
- import { extname as extname2, join as join2, relative as relative2 } from "path";
410
+ import { existsSync as existsSync3, statSync as statSync3 } from "fs";
411
+ import { extname as extname4, join as join4, relative as relative3 } from "path";
243
412
  var WS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
244
413
  function scanWebSocketRoutes(serverDir) {
245
- const wsDir = join2(serverDir, "ws");
246
- if (!existsSync2(wsDir) || !statSync2(wsDir).isDirectory()) {
414
+ const wsDir = join4(serverDir, "ws");
415
+ if (!existsSync3(wsDir) || !statSync3(wsDir).isDirectory()) {
247
416
  return [];
248
417
  }
249
418
  const results = [];
250
419
  walkSourceFiles(wsDir, { extensions: WS_EXTENSIONS }, (absPath) => {
251
- let rel = relative2(wsDir, absPath);
420
+ let rel = relative3(wsDir, absPath);
252
421
  rel = rel.replace(/\\/g, "/");
253
- rel = rel.slice(0, -extname2(rel).length);
422
+ rel = rel.slice(0, -extname4(rel).length);
254
423
  if (rel.endsWith("/index")) rel = rel.slice(0, -6);
255
424
  else if (rel === "index") rel = "";
256
425
  results.push({
@@ -262,10 +431,13 @@ function scanWebSocketRoutes(serverDir) {
262
431
  }
263
432
 
264
433
  export {
434
+ walkSourceFiles,
435
+ scanServerActions,
436
+ scanServerActionsEnriched,
265
437
  compilePattern,
266
438
  matchRoute,
267
439
  HTTP_METHODS,
268
440
  scanServerRoutes,
269
441
  scanWebSocketRoutes
270
442
  };
271
- //# sourceMappingURL=chunk-P2RS3WWY.js.map
443
+ //# sourceMappingURL=chunk-6FYD34NX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/scan/action-scan.ts","../src/server/_internal/scan-walker.ts","../src/core/contracts/http-methods.ts","../src/server/scan/scan.ts","../src/server/scan/detect-http-methods.ts","../src/server/scan/errors.ts","../src/server/scan/match.ts","../src/server/scan/ws-scan.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/actions/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { existsSync, readFileSync, statSync } from 'node:fs'\nimport { extname, join, relative } from 'node:path'\n\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nexport interface ActionNode {\n filePath: string\n actionPath: string\n}\n\n/**\n * Enriched manifest entry per plan g3-server-actions-and-useaction v1.2\n * § Phase 1 / T1.4 + ADR D4. Consumed by virtual module `@theo/actions`\n * (T3.1) and G4 devtools \"Actions\" tab (T5.1).\n */\nexport interface ActionManifestEntry {\n name: string\n filePath: string\n urlPath: string\n accept: 'form' | 'json'\n hasInput: boolean\n /**\n * P#4 plugin-forms shared-schema convention (per plan p4-plugin-forms v1.1 T1.1).\n * When present, points to an isomorphic schema file at\n * `<serverDir>/actions/schemas/<basename>.ts` exporting `export const schema = z.object(...)`.\n * Virtual module emits `import {schema} from '<schemaFilePath>'` + attaches as\n * `actions.X.__zodSchema` so client-side <TheoForm> can drive zodResolver.\n * Undefined when convention not followed (graceful degrade — TheoForm still\n * works via explicit `schema={...}` prop escape hatch).\n */\n schemaFilePath?: string\n}\n\n/**\n * EC-2: structured error for scan-time defects (name collision, reserved\n * identifier, etc.). Throw at scan time to fail loud — silent shadowing is\n * a security/correctness footgun.\n */\nexport class ActionScanError extends Error {\n readonly code: 'NAME_COLLISION' | 'RESERVED_NAME'\n readonly conflictingPaths: readonly string[]\n\n constructor(\n code: 'NAME_COLLISION' | 'RESERVED_NAME',\n message: string,\n conflictingPaths: readonly string[],\n ) {\n super(message)\n // T4.1 fix: identify class by name so serverErrorToEnvelope boundary\n // translator can route via class-name lookup (without this, runtime\n // `err.name` defaults to 'Error' and the meta.name diagnostic is wrong).\n this.name = 'ActionScanError'\n this.code = code\n this.conflictingPaths = conflictingPaths\n }\n}\n\nconst ACTION_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\nconst TEST_FILE_RE = /\\.(test|spec)\\.(ts|tsx|js|jsx)$/\nconst RESERVED_NAMES = new Set(['index', 'constructor', '__proto__', 'prototype', 'hasOwnProperty'])\n\n/**\n * Backward-compatible: original simple-shape scanner used by existing\n * consumers. Preserved verbatim; new consumers should call\n * `scanServerActionsEnriched`.\n */\nexport function scanServerActions(serverDir: string): ActionNode[] {\n const actionsDir = join(serverDir, 'actions')\n if (!existsSync(actionsDir) || !statSync(actionsDir).isDirectory()) {\n return []\n }\n\n const results: ActionNode[] = []\n walkSourceFiles(actionsDir, { extensions: ACTION_EXTENSIONS }, (absPath) => {\n if (TEST_FILE_RE.test(absPath)) return\n let rel = relative(actionsDir, absPath)\n rel = rel.replace(/\\\\/g, '/')\n rel = rel.slice(0, -extname(rel).length)\n results.push({\n filePath: absPath,\n actionPath: rel,\n })\n })\n return results\n}\n\n/**\n * Enriched scan: light AST detection of `accept: 'form'|'json'` + `input:`\n * presence via regex-after-comment-stripping (EC-9). Throws ActionScanError\n * on file/dir name collision (EC-2) or reserved JS identifier names.\n *\n * Output `ActionManifestEntry[]` is sorted by `name` for deterministic\n * `.theokit/actions-manifest.json` emission.\n */\nexport function scanServerActionsEnriched(serverDir: string): ActionManifestEntry[] {\n const actionsDir = join(serverDir, 'actions')\n if (!existsSync(actionsDir) || !statSync(actionsDir).isDirectory()) {\n return []\n }\n\n const seenNames = new Set<string>()\n const entries: ActionManifestEntry[] = []\n\n // Collect first; collision-check after the full walk.\n walkSourceFiles(actionsDir, { extensions: ACTION_EXTENSIONS }, (absPath) => {\n if (TEST_FILE_RE.test(absPath)) return\n const rel = relative(actionsDir, absPath).replace(/\\\\/g, '/')\n // P#4 plugin-forms — `schemas/` subdir holds isomorphic zod schemas\n // for the shared-schema convention (T1.1). NOT executable actions; skip.\n if (rel.startsWith('schemas/')) return\n const name = rel.slice(0, -extname(rel).length)\n\n const basename = name.includes('/') ? (name.split('/').pop() ?? name) : name\n if (RESERVED_NAMES.has(basename)) {\n throw new ActionScanError(\n 'RESERVED_NAME',\n `Reserved JS identifier \"${basename}\" cannot be an action name (${absPath})`,\n [absPath],\n )\n }\n // File-level collision (one file appearing twice) is impossible via the\n // walker; per-export collision check happens inside the export loop below.\n const source = readFileSync(absPath, 'utf8')\n const stripped = stripComments(source)\n const accept = /\\baccept\\s*:\\s*['\"]form['\"]/.test(stripped) ? 'form' : 'json'\n const hasInput = /\\binput\\s*:\\s*z\\./.test(stripped) || /\\binput\\s*:\\s*\\w+\\(/.test(stripped)\n\n // T7.1 wire fix — extract exports so the urlPath includes the second\n // segment required by action-middleware (`/api/__actions/<file>/<export>`).\n // Each named action export becomes its own manifest entry.\n // Proxy key on the EXPORT name (so consumers write `actions.saveMemory(input)`).\n // URL keeps the runtime 2-segment shape `/api/__actions/<file>/<export>`\n // expected by action-middleware. Cross-file export collisions become a\n // scan error to surface the ambiguity early.\n const exportNames = extractActionExportNames(stripped)\n // P#4 plugin-forms shared-schema convention: check for\n // `<actionsDir>/schemas/<basename>.ts` (or .tsx/.js/.jsx).\n // Skip when actions live in subdirs (`schemas/` is flat by convention).\n const schemaFilePath = name.includes('/') ? undefined : detectSchemaFile(actionsDir, basename)\n for (const exportName of exportNames) {\n const proxyKey = exportName === 'default' ? name : exportName\n if (seenNames.has(proxyKey)) {\n throw new ActionScanError(\n 'NAME_COLLISION',\n `Duplicate action proxy key \"${proxyKey}\" (two files export the same name)`,\n [absPath],\n )\n }\n seenNames.add(proxyKey)\n const entry: ActionManifestEntry = {\n name: proxyKey,\n filePath: absPath,\n urlPath: `/api/__actions/${name}/${exportName}`,\n accept,\n hasInput,\n }\n if (schemaFilePath !== undefined) {\n entry.schemaFilePath = schemaFilePath\n }\n entries.push(entry)\n }\n })\n\n // EC-2: detect file vs dir collisions (e.g., foo.ts AND foo/bar.ts).\n // After walk completes, any name that has children prefixed `name/` triggers collision.\n for (const entry of entries) {\n const childPrefix = `${entry.name}/`\n const conflictingChild = entries.find((other) => other.name.startsWith(childPrefix))\n if (conflictingChild) {\n throw new ActionScanError(\n 'NAME_COLLISION',\n `Action \"${entry.name}\" conflicts with directory of same name containing \"${conflictingChild.name}\"`,\n [entry.filePath, conflictingChild.filePath],\n )\n }\n }\n\n entries.sort((a, b) => {\n if (a.name < b.name) return -1\n if (a.name > b.name) return 1\n return 0\n })\n return entries\n}\n\n/**\n * Strip JavaScript line + block comments from source. Simple state machine\n * (does not parse strings — false positives if a comment marker appears\n * inside a string literal, but that's an acceptable trade-off for v1 vs\n * full AST parse).\n */\n/**\n * Extract action export names via regex over comment-stripped source.\n * Matches `export const <name> = defineAction(...)`, `export default\n * defineAction(...)`, and `export function <name>(...)` forms. Returns\n * `['default']` when no named action exports are found (best-effort fallback\n * for default-export shapes the regex misses).\n */\nfunction extractActionExportNames(stripped: string): string[] {\n const names = new Set<string>()\n const namedRe = /\\bexport\\s+(?:const|let|var|function\\*?)\\s+([a-zA-Z_$][\\w$]*)\\s*[=(]/g\n let m: RegExpExecArray | null\n while ((m = namedRe.exec(stripped)) !== null) {\n const name = m[1]\n if (typeof name === 'string' && name.length > 0) names.add(name)\n }\n if (/\\bexport\\s+default\\s+defineAction\\b/.test(stripped)) {\n names.add('default')\n }\n if (names.size === 0) names.add('default')\n return [...names]\n}\n\n/**\n * P#4 plugin-forms shared-schema convention helper (per plan p4-plugin-forms v1.1 T1.1).\n * Returns the resolved path to `<actionsDir>/schemas/<basename>.<ext>` if it exists.\n * Tries `.ts`, `.tsx`, `.js`, `.jsx` in that order. Returns undefined when no match.\n */\nfunction detectSchemaFile(actionsDir: string, basename: string): string | undefined {\n const schemasDir = join(actionsDir, 'schemas')\n if (!existsSync(schemasDir)) return undefined\n for (const ext of ['.ts', '.tsx', '.js', '.jsx']) {\n const candidate = join(schemasDir, `${basename}${ext}`)\n if (existsSync(candidate)) return candidate\n }\n return undefined\n}\n\nfunction stripComments(source: string): string {\n let out = ''\n let i = 0\n while (i < source.length) {\n const ch = source[i]\n const next = source[i + 1]\n if (ch === '/' && next === '/') {\n // Line comment: skip until newline\n while (i < source.length && source[i] !== '\\n') i++\n continue\n }\n if (ch === '/' && next === '*') {\n // Block comment: skip until */\n i += 2\n while (i < source.length - 1 && !(source[i] === '*' && source[i + 1] === '/')) i++\n i += 2\n continue\n }\n out += ch\n i++\n }\n return out\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks directories derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { readdirSync } from 'node:fs'\nimport { extname, join, resolve } from 'node:path'\n\n/**\n * Options for walkSourceFiles.\n *\n * Sequential by design — callers (route precedence) depend on insertion\n * order (EC-19 documented decision). Async-callback support is implicit\n * via JavaScript's serial await loop.\n *\n * Tested on macOS + Linux. Windows long-path support not validated (EC-20).\n */\nexport interface WalkOptions {\n /** File extensions to include (e.g., new Set(['.ts', '.tsx'])). */\n extensions: ReadonlySet<string>\n /** Skip directories whose name starts with any of these (default: ['_', '.']). */\n skipPrefixes?: readonly string[]\n}\n\n/**\n * Recursively walk `root`, invoking `onFile(absPath)` for every file matching\n * `opts.extensions`. Directories whose name starts with any `skipPrefixes`\n * char are skipped (defaults to `_` and `.`).\n *\n * Replaces 3 near-identical recursive walkers in scan.ts, action-scan.ts,\n * ws-scan.ts (PV-3 — DRY consolidation). Resolves T3.1 of\n * architecture-review-remediation-plan.\n *\n * Symlink loops are NOT tracked — callers must avoid them or pre-resolve\n * via `fs.realpath`. EC-11 documented but not implemented (rare in dev).\n */\nexport function walkSourceFiles(\n root: string,\n opts: WalkOptions,\n onFile: (absPath: string) => void,\n): void {\n const skipPrefixes = opts.skipPrefixes ?? ['_', '.']\n const visit = (dir: string): void => {\n let entries\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n } catch {\n // Silently skip unreadable directories — caller controls discoverability\n return\n }\n for (const entry of entries) {\n const fullPath = join(dir, entry.name)\n if (entry.isDirectory() && !skipPrefixes.some((p) => entry.name.startsWith(p))) {\n visit(fullPath)\n } else if (entry.isFile() && opts.extensions.has(extname(entry.name))) {\n onFile(resolve(fullPath))\n }\n }\n }\n visit(root)\n}\n","/**\n * Canonical HTTP method set used by the typed client codegen + Proxy runtime.\n * Kept here (under `server/scan/`) because the SCAN is the first consumer —\n * `vite-plugin/app-typed-client.ts` (Phase 2) and `client/app-client.ts` (Phase 3)\n * import from here to ensure a single source of truth.\n *\n * Lowercase variants are derived via `.toLowerCase()` at call sites; we do not\n * export them separately to avoid drift between the two lists.\n */\n\nexport const HTTP_METHODS = [\n 'GET',\n 'POST',\n 'PUT',\n 'PATCH',\n 'DELETE',\n 'HEAD',\n 'OPTIONS',\n] as const\n\nexport type HttpMethod = (typeof HTTP_METHODS)[number]\n\nexport const HTTP_METHOD_LOWERCASE = HTTP_METHODS.map((m) => m.toLowerCase()) as readonly string[]\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/routes/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { existsSync, statSync } from 'node:fs'\nimport { basename, extname, join, relative } from 'node:path'\n\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nimport { detectExportedHttpMethods } from './detect-http-methods.js'\nimport { RouterConventionError } from './errors.js'\nimport { compilePattern, type ServerRouteNode } from './match.js'\n\nconst ROUTE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\n\n// EC-4: Co-located unit/spec tests must be silently skipped by the scanner,\n// BEFORE the dotted-basename check fires. Matches `*.test.ts|tsx|js|jsx`\n// and `*.spec.ts|tsx|js|jsx`.\nconst TEST_OR_SPEC_RE = /\\.(test|spec)\\.[jt]sx?$/\n\nfunction isTestOrSpecFile(filePath: string): boolean {\n return TEST_OR_SPEC_RE.test(basename(filePath))\n}\n\nfunction hasDotOutsideBrackets(segment: string): boolean {\n let depth = 0\n for (const ch of segment) {\n if (ch === '[') depth++\n else if (ch === ']') depth--\n else if (ch === '.' && depth === 0) return true\n }\n return false\n}\n\nfunction splitDottedSegmentOutsideBrackets(segment: string): string[] {\n const parts: string[] = []\n let current = ''\n let depth = 0\n for (const ch of segment) {\n if (ch === '[') {\n depth++\n current += ch\n } else if (ch === ']') {\n depth--\n current += ch\n } else if (ch === '.' && depth === 0) {\n if (current) parts.push(current)\n current = ''\n } else {\n current += ch\n }\n }\n if (current) parts.push(current)\n return parts\n}\n\nfunction buildDirectoryNestedSuggestion(filePath: string, routesDir: string): string {\n const rel = relative(routesDir, filePath).replace(/\\\\/g, '/')\n const ext = extname(rel)\n const withoutExt = rel.slice(0, -ext.length)\n const segments = withoutExt.split('/').flatMap(splitDottedSegmentOutsideBrackets)\n return `routes/${segments.join('/')}${ext}`\n}\n\nfunction assertNoDottedSegment(filePath: string, routesDir: string): void {\n const rel = relative(routesDir, filePath).replace(/\\\\/g, '/')\n const ext = extname(rel)\n const withoutExt = rel.slice(0, -ext.length)\n const segments = withoutExt.split('/')\n for (const seg of segments) {\n if (hasDotOutsideBrackets(seg)) {\n throw new RouterConventionError({\n file: filePath,\n suggestion: buildDirectoryNestedSuggestion(filePath, routesDir),\n })\n }\n }\n}\n\nfunction fileToRoutePath(filePath: string, routesDir: string): string {\n let rel = relative(routesDir, filePath)\n // Strip extension\n const ext = extname(rel)\n rel = rel.slice(0, -ext.length)\n // Normalize separators\n rel = rel.replace(/\\\\/g, '/')\n // Strip index suffix\n if (rel.endsWith('/index')) {\n rel = rel.slice(0, -6)\n } else if (rel === 'index') {\n rel = ''\n }\n // Replace [...param] with :...param (catch-all, before regular params).\n // Replace [param] with :param. Inputs are file paths bounded by the\n // OS filename limit; the bracket capture is bounded by `]`.\n rel = rel.replace(/\\[\\.\\.\\.([^\\]]+)\\]/g, ':...$1')\n // eslint-disable-next-line sonarjs/slow-regex -- bounded by `]`; input is a single filename\n rel = rel.replace(/\\[([^\\]]+)\\]/g, ':$1')\n return `/api/${rel}`\n}\n\nexport function scanServerRoutes(serverDir: string): ServerRouteNode[] {\n const routesDir = join(serverDir, 'routes')\n if (!existsSync(routesDir) || !statSync(routesDir).isDirectory()) {\n return []\n }\n\n const results: ServerRouteNode[] = []\n walkSourceFiles(routesDir, { extensions: ROUTE_EXTENSIONS }, (absPath) => {\n // EC-4: skip co-located test/spec files BEFORE the dotted-basename check\n if (isTestOrSpecFile(absPath)) return\n\n // G6 T1.1: reject dotted basenames (legacy convention that produced wrong\n // paramNames due to greedy `:(?:\\.\\.\\.)?([^/]+)` regex in compilePattern).\n assertNoDottedSegment(absPath, routesDir)\n\n const routePath = fileToRoutePath(absPath, routesDir)\n const { pattern, paramNames } = compilePattern(routePath)\n const methods = detectExportedHttpMethods(absPath)\n results.push({\n filePath: absPath,\n routePath,\n paramNames,\n pattern,\n methods,\n })\n })\n\n // T1.4 / EC-2 — refuse to scan if a user route collides with the reserved\n // batch endpoint path. User must rename or disable batching.\n const conflicting = results.find((r) => r.routePath === '/api/__theo_batch__')\n if (conflicting) {\n throw new Error(\n `Server route ${conflicting.filePath} resolves to '/api/__theo_batch__' which is reserved for the batch endpoint. Rename the route or disable batching in theo.config.ts.`,\n )\n }\n\n // Sort: static first, then dynamic, then catch-all last\n const isCatchAll = (route: ServerRouteNode) => route.routePath.includes(':...')\n results.sort((a, b) => {\n const aStatic = a.paramNames.length === 0\n const bStatic = b.paramNames.length === 0\n const aCatchAll = isCatchAll(a)\n const bCatchAll = isCatchAll(b)\n\n // Static routes first\n if (aStatic && !bStatic) return -1\n if (!aStatic && bStatic) return 1\n // Catch-all routes last\n if (aCatchAll && !bCatchAll) return 1\n if (!aCatchAll && bCatchAll) return -1\n return a.routePath.localeCompare(b.routePath)\n })\n\n return results\n}\n","/**\n * Detect which HTTP-method named exports a route file declares.\n *\n * Uses the TypeScript compiler API (not regex) per G1 edge-case review EC-4:\n * regex over file content emits false positives for `// export const GET = ...`\n * in comments and `` `export const GET = ...` `` in template literals. AST\n * walking avoids both classes of bug.\n *\n * `typescript` ships as CommonJS with internal dynamic `require('fs')`.\n * When loaded via ESM `import`, the dynamic requires fail at module-bootstrap.\n * We use `createRequire(import.meta.url)` to keep the package on its native\n * CJS path. The type-only namespace import gives us the AST helpers shape.\n *\n * Returns the set of HTTP methods (uppercase) the file exports. Empty array\n * means the file has no HTTP exports (the route file is util-only).\n */\n\nimport { readFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\n\nimport type * as TS from 'typescript'\n\nimport { HTTP_METHODS, type HttpMethod } from '../../core/contracts/http-methods.js'\n\nconst require_ = createRequire(import.meta.url)\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nconst ts = require_('typescript') as typeof TS\n\nconst HTTP_METHOD_NAMES = new Set<string>(HTTP_METHODS)\n\nfunction hasExportModifier(modifiers: readonly TS.Modifier[] | undefined): boolean {\n if (!modifiers) return false\n for (const m of modifiers) {\n if (m.kind === ts.SyntaxKind.ExportKeyword) return true\n }\n return false\n}\n\nfunction collectFromStatement(stmt: TS.Statement, found: Set<HttpMethod>): void {\n // `export const GET = ...` / `export function GET ...` / `export async function GET ...`\n if (ts.isVariableStatement(stmt) && hasExportModifier(ts.getModifiers(stmt))) {\n for (const decl of stmt.declarationList.declarations) {\n if (ts.isIdentifier(decl.name) && HTTP_METHOD_NAMES.has(decl.name.text)) {\n found.add(decl.name.text as HttpMethod)\n }\n }\n return\n }\n\n if (\n (ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt)) &&\n hasExportModifier(ts.getModifiers(stmt))\n ) {\n if (stmt.name && HTTP_METHOD_NAMES.has(stmt.name.text)) {\n found.add(stmt.name.text as HttpMethod)\n }\n return\n }\n\n // `export { GET }` / `export { handler as GET } from './shared'` (EC-5)\n if (ts.isExportDeclaration(stmt) && stmt.exportClause && ts.isNamedExports(stmt.exportClause)) {\n for (const spec of stmt.exportClause.elements) {\n // spec.name is the exported (re-)name; spec.propertyName is the original (when renamed)\n if (HTTP_METHOD_NAMES.has(spec.name.text)) {\n found.add(spec.name.text as HttpMethod)\n }\n }\n }\n}\n\nexport function detectExportedHttpMethods(filePath: string, content?: string): HttpMethod[] {\n const src = content ?? readFileSync(filePath, 'utf-8')\n const sourceFile = ts.createSourceFile(\n filePath,\n src,\n ts.ScriptTarget.Latest,\n /* setParentNodes */ false,\n ts.ScriptKind.TS,\n )\n const found = new Set<HttpMethod>()\n for (const stmt of sourceFile.statements) {\n collectFromStatement(stmt, found)\n }\n return [...found].sort()\n}\n","/**\n * Router-convention errors thrown by the server-route scanner.\n *\n * Plan: .claude/knowledge-base/plans/g6-router-convention-plan.md v1.1\n *\n * theokit 0.4.0+ enforces directory-nested file-system routing\n * (`auth/[provider]/login.ts`) and REJECTS dotted-basename routes\n * (`auth.[provider].login.ts`) because the legacy regex extracted the\n * dotted basename incorrectly — `params.provider` was undefined at request\n * time. See ADR-XXX (router-convention-decision) in CHANGELOG 0.4.0.\n */\n\n/**\n * Canonical migration guide URL. T4.2 establishes this as the authoritative\n * landing page. EC-3: error message uses this constant so the URL never\n * drifts from the doc location.\n */\nexport const ROUTER_MIGRATION_GUIDE_URL = 'https://theokit.dev/migration/0.3-to-0.4-router'\n\nexport interface RouterConventionErrorOptions {\n /** Absolute path of the offending route file. */\n file: string\n /** Suggested directory-nested replacement path (relative, e.g. `routes/auth/[provider]/login.ts`). */\n suggestion: string\n /** Migration guide URL (defaults to `ROUTER_MIGRATION_GUIDE_URL`). */\n migrationUrl?: string\n}\n\n/**\n * Thrown by `scanServerRoutes` when a route file uses the legacy\n * dotted-basename convention (`auth.[provider].login.ts`).\n *\n * The error is FAIL-FAST by design — running with a route that has wrong\n * `paramNames` produces silent 404s at request time, which is strictly\n * worse than a build-time error.\n */\nexport class RouterConventionError extends Error {\n override readonly name = 'RouterConventionError'\n readonly file: string\n readonly suggestion: string\n readonly migrationUrl: string\n\n constructor(opts: RouterConventionErrorOptions) {\n const migrationUrl = opts.migrationUrl ?? ROUTER_MIGRATION_GUIDE_URL\n const message = [\n `Router convention violation: dotted route basename is not supported in theokit 0.4+.`,\n ``,\n ` File: ${opts.file}`,\n ` Use directory-nested form: ${opts.suggestion}`,\n ``,\n `Migration guide: ${migrationUrl}`,\n `Run \\`theokit migrate router\\` to convert all dotted basenames automatically.`,\n ].join('\\n')\n super(message)\n this.file = opts.file\n this.suggestion = opts.suggestion\n this.migrationUrl = migrationUrl\n }\n}\n","export interface ServerRouteNode {\n filePath: string\n routePath: string\n paramNames: string[]\n pattern: RegExp\n /** HTTP methods (uppercase) the route file exports. Optional for backward\n * compatibility with manifests generated before G1. Empty array means the\n * file has no HTTP exports (util-only); undefined means \"not detected\". */\n methods?: string[]\n}\n\nexport function compilePattern(routePath: string): {\n pattern: RegExp\n paramNames: string[]\n} {\n const paramNames: string[] = []\n // Single pass: handle both catch-all (:...name) and regular (:name) params\n const regexStr = routePath.replace(/:(?:\\.\\.\\.)?([^/]+)/g, (match: string, name: string) => {\n paramNames.push(name)\n // Catch-all matches across slashes, regular matches single segment\n return match.startsWith(':...') ? '(.+)' : '([^/]+)'\n })\n // `regexStr` is derived from a developer-authored route path (build-time\n // input, not HTTP-controlled). The `security/detect-non-literal-regexp`\n // rule cannot see this constraint — disable narrowly.\n // eslint-disable-next-line security/detect-non-literal-regexp -- route pattern from build-time scan, never HTTP input\n return { pattern: new RegExp(`^${regexStr}$`), paramNames }\n}\n\nexport function matchRoute(\n url: string,\n routes: ServerRouteNode[],\n): { route: ServerRouteNode; params: Record<string, string> } | null {\n // Strip query string and trailing slash\n let path = url.split('?')[0]\n if (path.length > 1 && path.endsWith('/')) {\n path = path.slice(0, -1)\n }\n\n for (const route of routes) {\n const match = route.pattern.exec(path)\n if (match) {\n const params: Record<string, string> = {}\n route.paramNames.forEach((name, i) => {\n params[name] = match[i + 1]\n })\n return { route, params }\n }\n }\n return null\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/ws/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { existsSync, statSync } from 'node:fs'\nimport { extname, join, relative } from 'node:path'\n\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nconst WS_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\n\nexport interface WebSocketRouteNode {\n filePath: string\n wsPath: string\n}\n\nexport function scanWebSocketRoutes(serverDir: string): WebSocketRouteNode[] {\n const wsDir = join(serverDir, 'ws')\n if (!existsSync(wsDir) || !statSync(wsDir).isDirectory()) {\n return []\n }\n\n const results: WebSocketRouteNode[] = []\n walkSourceFiles(wsDir, { extensions: WS_EXTENSIONS }, (absPath) => {\n let rel = relative(wsDir, absPath)\n rel = rel.replace(/\\\\/g, '/')\n rel = rel.slice(0, -extname(rel).length)\n if (rel.endsWith('/index')) rel = rel.slice(0, -6)\n else if (rel === 'index') rel = ''\n results.push({\n filePath: absPath,\n wsPath: `/ws/${rel}`,\n })\n })\n return results\n}\n"],"mappings":"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","names":["extname","join","join","extname","basename","existsSync","statSync","extname","join","relative","readFileSync","readFileSync","relative","extname","join","existsSync","statSync","existsSync","statSync","extname","join","relative","join","existsSync","statSync","relative","extname"]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  buildServicesProxyConfig,
3
3
  servicesConfigSchema
4
- } from "./chunk-DNMSV3R3.js";
4
+ } from "./chunk-D7ZH7DTG.js";
5
5
  import {
6
6
  broadcastToDevtools
7
7
  } from "./chunk-7BIM27LV.js";
@@ -23,19 +23,17 @@ import {
23
23
  } from "./chunk-JQSKBMXP.js";
24
24
  import {
25
25
  matchRoute,
26
+ scanServerActions,
26
27
  scanServerRoutes,
27
28
  scanWebSocketRoutes
28
- } from "./chunk-OLPB36O2.js";
29
- import {
30
- scanServerActions
31
- } from "./chunk-P3SGM4NR.js";
29
+ } from "./chunk-223EFY5X.js";
32
30
  import {
33
31
  CSP_REPORT_PATH,
34
32
  CsrfReadinessStore,
35
33
  applySecurityHeaders,
36
34
  handleCspReport,
37
35
  handleCsrfReadiness
38
- } from "./chunk-B3L3TJVF.js";
36
+ } from "./chunk-JBCHWRKF.js";
39
37
  import {
40
38
  generateNonce
41
39
  } from "./chunk-C3ZZ56YZ.js";
@@ -51,7 +49,7 @@ import {
51
49
  extractTraceId,
52
50
  handleBatchRequest,
53
51
  sendError
54
- } from "./chunk-KJLECZWQ.js";
52
+ } from "./chunk-3FYJ7R7N.js";
55
53
  import {
56
54
  logRequest
57
55
  } from "./chunk-Z5IGLBWE.js";
@@ -369,6 +367,25 @@ var theoConfigSchema = z8.object({
369
367
  }).optional()
370
368
  }).optional(),
371
369
  port: z8.number().int().min(1).max(65535).default(3e3),
370
+ /** Listen on all addresses (0.0.0.0) for LAN/mobile testing. */
371
+ host: z8.union([z8.string(), z8.boolean()]).default("localhost"),
372
+ /** Automatically open browser on `theokit dev`. */
373
+ open: z8.union([z8.boolean(), z8.string()]).default(false),
374
+ /** Exit if port is already in use instead of trying next available. */
375
+ strictPort: z8.boolean().default(false),
376
+ /** Forward browser console/errors to terminal — useful for AI agent dev. */
377
+ forwardConsole: z8.union([
378
+ z8.boolean(),
379
+ z8.object({
380
+ unhandledErrors: z8.boolean().optional(),
381
+ logLevels: z8.array(z8.enum(["error", "warn", "info", "log", "debug"])).optional()
382
+ })
383
+ ]).default(false),
384
+ /** Pre-transform files on startup for faster first page load. */
385
+ warmup: z8.object({
386
+ clientFiles: z8.array(z8.string()).optional(),
387
+ ssrFiles: z8.array(z8.string()).optional()
388
+ }).optional(),
372
389
  ssr: z8.boolean().default(false),
373
390
  /** When true (and ssr === true), use renderToPipeableStream with progressive
374
391
  * shell flush instead of single-shot renderToString. Opt-in for streaming
@@ -811,19 +828,53 @@ function attachRouteFiles(node, dir) {
811
828
  }
812
829
  }
813
830
  }
831
+ function parseSegment(name) {
832
+ if (name.startsWith("[[")) {
833
+ throw new Error(
834
+ `Optional catch-all route segment is not supported yet: '${name}'. Use '[...param]' for catch-all, or split into explicit segments.`
835
+ );
836
+ }
837
+ const catchAll = /^\[\.\.\.(.+)\]$/.exec(name);
838
+ if (catchAll) return checkedSegment("catchall", catchAll[1], name);
839
+ const dynamic = /^\[(.+)\]$/.exec(name);
840
+ if (dynamic) return checkedSegment("dynamic", dynamic[1], name);
841
+ return { kind: "static" };
842
+ }
843
+ function checkedSegment(kind, paramName, name) {
844
+ if (!/^\w+$/.test(paramName)) {
845
+ throw new Error(
846
+ `Invalid route param '${paramName}' in segment '${name}' \u2014 param names must match [A-Za-z0-9_] (react-router constraint).`
847
+ );
848
+ }
849
+ return { kind, paramName };
850
+ }
814
851
  function nodeHasContent(node) {
815
852
  return node.page !== void 0 || node.layout !== void 0 || node.error !== void 0 || node.loading !== void 0 || node.notFound !== void 0 || node.children.length > 0;
816
853
  }
854
+ function buildChildPath(routePath, urlSegment) {
855
+ if (!urlSegment) return routePath;
856
+ return routePath === "/" ? `/${urlSegment}` : `${routePath}/${urlSegment}`;
857
+ }
858
+ function scanChild(parentDir, dirName, routePath) {
859
+ const isRouteGroup = dirName.startsWith("(") && dirName.endsWith(")");
860
+ const urlSegment = isRouteGroup ? "" : dirName;
861
+ const childPath = buildChildPath(routePath, urlSegment);
862
+ const child = scanDir(join2(parentDir, dirName), isRouteGroup ? "" : dirName, childPath);
863
+ if (!isRouteGroup) {
864
+ const parsed = parseSegment(dirName);
865
+ if (parsed.kind !== "static" && parsed.paramName !== void 0) {
866
+ child.dynamic = { paramName: parsed.paramName, catchAll: parsed.kind === "catchall" };
867
+ }
868
+ }
869
+ return child;
870
+ }
817
871
  function scanDir(dir, segment, routePath) {
818
872
  const node = { segment, path: routePath, children: [] };
819
873
  attachRouteFiles(node, dir);
820
874
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
821
875
  if (!entry.isDirectory()) continue;
822
876
  if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
823
- const isRouteGroup = entry.name.startsWith("(") && entry.name.endsWith(")");
824
- const urlSegment = isRouteGroup ? "" : entry.name;
825
- const childPath = urlSegment ? routePath === "/" ? `/${urlSegment}` : `${routePath}/${urlSegment}` : routePath;
826
- const child = scanDir(join2(dir, entry.name), isRouteGroup ? "" : entry.name, childPath);
877
+ const child = scanChild(dir, entry.name, routePath);
827
878
  if (nodeHasContent(child)) {
828
879
  node.children.push(child);
829
880
  }
@@ -1297,7 +1348,7 @@ async function runConfigureServer(server, ctx) {
1297
1348
  server.watcher.on("unlink", handleRouteChange);
1298
1349
  if (ctx.resolvedOpenApi !== void 0) {
1299
1350
  const openApiCfg = ctx.resolvedOpenApi;
1300
- const { reEmitOpenApi } = await import("./dev-emit-JBVINGHU.js");
1351
+ const { reEmitOpenApi } = await import("./dev-emit-O4EGOSNV.js");
1301
1352
  const openApiServerDir = resolve6(ctx.projectRoot, "server");
1302
1353
  const openApiDistDir = resolve6(ctx.projectRoot, ctx.resolvedDistDir);
1303
1354
  const isRouteFileForOpenApi = (file) => file.startsWith(openApiServerDir) && /\.(ts|tsx|js|mjs)$/.test(file);
@@ -1990,6 +2041,11 @@ function safeVarName(segment, prefix) {
1990
2041
  const safe = segment.replace(/[^a-zA-Z0-9]/g, "_") || "root";
1991
2042
  return `${prefix}_${safe}`;
1992
2043
  }
2044
+ function segmentPath(node) {
2045
+ if (node.dynamic?.catchAll) return "*";
2046
+ if (node.dynamic) return `:${node.dynamic.paramName}`;
2047
+ return node.segment;
2048
+ }
1993
2049
  function buildRoutePath(parents, segment) {
1994
2050
  const joined = [...parents, segment].filter(Boolean).join("/");
1995
2051
  return "/" + joined;
@@ -2001,7 +2057,7 @@ function pushIf(staticImports, filePath, varName) {
2001
2057
  }
2002
2058
  function walkRouteTree(node, parents, acc) {
2003
2059
  const seg = node.segment || "root";
2004
- const routePath = buildRoutePath(parents, node.segment);
2060
+ const routePath = buildRoutePath(parents, segmentPath(node));
2005
2061
  if (node.page !== void 0) {
2006
2062
  acc.lazyPages.push({
2007
2063
  varName: safeVarName(seg, "Page"),
@@ -2015,7 +2071,8 @@ function walkRouteTree(node, parents, acc) {
2015
2071
  pushIf(acc.staticImports, node.loading, safeVarName(seg, "Loading"));
2016
2072
  pushIf(acc.staticImports, node.notFound, safeVarName(seg, "NotFound"));
2017
2073
  for (const child of node.children) {
2018
- const nextParents = node.segment ? [...parents, node.segment] : parents;
2074
+ const childSegPath = segmentPath(node);
2075
+ const nextParents = childSegPath ? [...parents, childSegPath] : parents;
2019
2076
  walkRouteTree(child, nextParents, acc);
2020
2077
  }
2021
2078
  }
@@ -2074,7 +2131,7 @@ function generateRouteManifest(tree) {
2074
2131
  const childrenArray = buildChildrenArray(node, seg);
2075
2132
  if (node.layout) {
2076
2133
  const layoutVar = safeVarName(seg, "Layout");
2077
- const pathPart = isRoot ? `path: '/'` : `path: '${node.segment}'`;
2134
+ const pathPart = isRoot ? `path: '/'` : `path: '${segmentPath(node)}'`;
2078
2135
  return `{ ${pathPart}, element: React.createElement(${layoutVar}, { children: React.createElement(Outlet) }), children: ${childrenArray} }`;
2079
2136
  }
2080
2137
  if (isRoot) {
@@ -2087,9 +2144,9 @@ function generateRouteManifest(tree) {
2087
2144
  const pageVar = safeVarName(seg, "Page");
2088
2145
  const fallbackEl = node.loading ? `React.createElement(${safeVarName(seg, "Loading")})` : "null";
2089
2146
  const pageElement = `React.createElement(Suspense, { fallback: ${fallbackEl} }, React.createElement(${pageVar}))`;
2090
- return `{ path: '${node.segment}', element: ${pageElement} }`;
2147
+ return `{ path: '${segmentPath(node)}', element: ${pageElement} }`;
2091
2148
  }
2092
- return `{ path: '${node.segment}', children: ${childrenArray} }`;
2149
+ return `{ path: '${segmentPath(node)}', children: ${childrenArray} }`;
2093
2150
  }
2094
2151
  const routeConfig = genRouteConfig(tree, true);
2095
2152
  lines.push(`export const routes = [${routeConfig}]`);
@@ -2269,7 +2326,7 @@ async function theoPluginAsync(rootOrOptions) {
2269
2326
  });
2270
2327
  const servicesPlugins = [];
2271
2328
  if (options.services && Object.keys(options.services).length > 0) {
2272
- const { servicesTypedClientPlugin } = await import("./services-typed-client-KK6RHRDG.js");
2329
+ const { servicesTypedClientPlugin } = await import("./services-typed-client-IETY2SAK.js");
2273
2330
  servicesPlugins.push(
2274
2331
  servicesTypedClientPlugin({
2275
2332
  cwd: projectRoot,
@@ -2277,13 +2334,13 @@ async function theoPluginAsync(rootOrOptions) {
2277
2334
  })
2278
2335
  );
2279
2336
  }
2280
- const { appTypedClientPlugin } = await import("./app-typed-client-4M4NVSK3.js");
2337
+ const { appTypedClientPlugin } = await import("./app-typed-client-4N5OGNOQ.js");
2281
2338
  const appClientPlugin = appTypedClientPlugin({
2282
2339
  cwd: projectRoot,
2283
2340
  serverDir: resolve9(projectRoot, "server"),
2284
2341
  distDir: resolve9(projectRoot, ".theokit")
2285
2342
  });
2286
- const { actionsVirtualModule } = await import("./actions-virtual-module-ZDD54ZZE.js");
2343
+ const { actionsVirtualModule } = await import("./actions-virtual-module-LTOXBSCF.js");
2287
2344
  const actionsPlugin = actionsVirtualModule({
2288
2345
  serverDir: resolve9(projectRoot, "server"),
2289
2346
  distDir: resolve9(projectRoot, ".theokit")
@@ -2457,4 +2514,4 @@ export {
2457
2514
  theoPluginAsync,
2458
2515
  theoPlugin
2459
2516
  };
2460
- //# sourceMappingURL=chunk-X3VNROZ7.js.map
2517
+ //# sourceMappingURL=chunk-6VSKLYT6.js.map