theokit 0.2.3 → 0.2.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/LICENSE +201 -0
- package/dist/{actions-virtual-module-E6PU47GV.js → actions-virtual-module-HWNTIEPI.js} +40 -6
- package/dist/actions-virtual-module-HWNTIEPI.js.map +1 -0
- package/dist/{actions-virtual-module-E7IELVHL.js → actions-virtual-module-SEIPACG5.js} +40 -6
- package/dist/actions-virtual-module-SEIPACG5.js.map +1 -0
- package/dist/add-2ZFFGGE4.js +0 -0
- package/dist/{app-typed-client-FOWJP534.js → app-typed-client-6GC6VWXS.js} +4 -4
- package/dist/{app-typed-client-IMILAGER.js → app-typed-client-US2ZXBEC.js} +3 -3
- package/dist/aws-lambda-XYCGZH3I.js +0 -0
- package/dist/broadcast-QOQGUNNK.js +0 -0
- package/dist/{build-KRSHWXMJ.js → build-MREW6MVS.js} +5 -5
- package/dist/build-request-body-preview-II2235E6.js +0 -0
- package/dist/bun-K73TQEWC.js +0 -0
- package/dist/check-PZR67OY5.js +0 -0
- package/dist/chunk-5ODOE6EF.js +0 -0
- package/dist/{chunk-MOT42NAH.js → chunk-5OFPQK6N.js} +18 -3
- package/dist/chunk-5OFPQK6N.js.map +1 -0
- package/dist/{chunk-RIBIAL7A.js → chunk-5OKZY2VL.js} +5 -5
- package/dist/{chunk-7DHGV77J.js → chunk-7TOMTMHT.js} +18 -3
- package/dist/chunk-7TOMTMHT.js.map +1 -0
- package/dist/{chunk-SEVYARIU.js → chunk-BE2LKDI7.js} +3 -3
- package/dist/chunk-F3TG5FJV.js +0 -0
- package/dist/chunk-KT3MWNZG.js +0 -0
- package/dist/chunk-O7335ZGH.js +0 -0
- package/dist/{chunk-QLGES2HC.js → chunk-SNDZQ64K.js} +2 -2
- package/dist/chunk-TPCT3MXV.js +0 -0
- package/dist/chunk-WZ7CPVQB.js +0 -0
- package/dist/{chunk-F7RLPDXW.js → chunk-X2OLD264.js} +6 -6
- package/dist/chunk-X4JAX2U3.js +0 -0
- package/dist/chunk-XMS5VRIK.js +0 -0
- package/dist/chunk-XP7YXRHO.js +0 -0
- package/dist/{chunk-ERAZNHHR.js → chunk-YWWHK7R6.js} +2 -2
- package/dist/chunk-ZJW5FFYJ.js +0 -0
- package/dist/chunk-ZOXNJV2O.js +0 -0
- package/dist/cli/index.js +5 -5
- package/dist/cloudflare-LCAOTRYZ.js +0 -0
- package/dist/configure-agent-registry-6YGTJYBC.js +0 -0
- package/dist/deno-deploy-ZN4RE5HF.js +0 -0
- package/dist/{dev-UZJVBUF3.js → dev-6P2DM33Q.js} +4 -4
- package/dist/{dev-emit-3CHM73TV.js → dev-emit-MLDFOLUU.js} +3 -3
- package/dist/{dev-emit-I3CKOHML.js → dev-emit-PRSRPDHA.js} +4 -4
- package/dist/dispatcher-E6QV32BO.js +0 -0
- package/dist/dist-E6BL4ZVY.js +0 -0
- package/dist/docker-EZDA5FT7.js +0 -0
- package/dist/generate-DT4IIAHF.js +0 -0
- package/dist/index.js +2 -2
- package/dist/info-FJ4NXKTB.js +0 -0
- package/dist/lib-NL5JXGEB.js +0 -0
- package/dist/netlify-5VQ22Z2P.js +0 -0
- package/dist/node-3APTLGWB.js +0 -0
- package/dist/{openapi-ILYEJ6OR.js → openapi-SOKZTFE3.js} +4 -4
- package/dist/proper-lockfile-4Y3DP3RP.js +0 -0
- package/dist/registry-5QFTLOL2.js +0 -0
- package/dist/{routes-IGT42JJO.js → routes-XVRAUH6H.js} +3 -3
- package/dist/scan-C2BOKLSY.js +0 -0
- package/dist/schema-VR6YNVLQ.js +0 -0
- package/dist/server/index.d.ts +10 -0
- package/dist/server/index.js +2 -2
- package/dist/services-typed-client-ZX5JUHH3.js +0 -0
- package/dist/sqlite-vec-ARPNUBWT.js +0 -0
- package/dist/{start-UX2IPPSH.js → start-R7XSQL5L.js} +4 -4
- package/dist/static-TZBVBDTB.js +0 -0
- package/dist/storage-manager-D5KBXXKB.js +0 -0
- package/dist/theo-cloud-3K4DGB3S.js +0 -0
- package/dist/upgrade-readiness-GUJBCJIS.js +0 -0
- package/dist/vercel-MWW24ABC.js +0 -0
- package/dist/vite-plugin/index.js +2 -2
- package/dist/{vite-plugin-FTYXDKKG.js → vite-plugin-6MPHTKIW.js} +4 -4
- package/package.json +7 -7
- package/dist/actions-virtual-module-E6PU47GV.js.map +0 -1
- package/dist/actions-virtual-module-E7IELVHL.js.map +0 -1
- package/dist/chunk-7DHGV77J.js.map +0 -1
- package/dist/chunk-MOT42NAH.js.map +0 -1
- /package/dist/{app-typed-client-FOWJP534.js.map → app-typed-client-6GC6VWXS.js.map} +0 -0
- /package/dist/{app-typed-client-IMILAGER.js.map → app-typed-client-US2ZXBEC.js.map} +0 -0
- /package/dist/{build-KRSHWXMJ.js.map → build-MREW6MVS.js.map} +0 -0
- /package/dist/{chunk-RIBIAL7A.js.map → chunk-5OKZY2VL.js.map} +0 -0
- /package/dist/{chunk-SEVYARIU.js.map → chunk-BE2LKDI7.js.map} +0 -0
- /package/dist/{chunk-QLGES2HC.js.map → chunk-SNDZQ64K.js.map} +0 -0
- /package/dist/{chunk-F7RLPDXW.js.map → chunk-X2OLD264.js.map} +0 -0
- /package/dist/{chunk-ERAZNHHR.js.map → chunk-YWWHK7R6.js.map} +0 -0
- /package/dist/{dev-UZJVBUF3.js.map → dev-6P2DM33Q.js.map} +0 -0
- /package/dist/{dev-emit-3CHM73TV.js.map → dev-emit-MLDFOLUU.js.map} +0 -0
- /package/dist/{dev-emit-I3CKOHML.js.map → dev-emit-PRSRPDHA.js.map} +0 -0
- /package/dist/{openapi-ILYEJ6OR.js.map → openapi-SOKZTFE3.js.map} +0 -0
- /package/dist/{routes-IGT42JJO.js.map → routes-XVRAUH6H.js.map} +0 -0
- /package/dist/{start-UX2IPPSH.js.map → start-R7XSQL5L.js.map} +0 -0
- /package/dist/{vite-plugin-FTYXDKKG.js.map → vite-plugin-6MPHTKIW.js.map} +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for describing the origin of the Work and
|
|
141
|
+
reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 usetheo.dev
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import "tsx/esm";
|
|
3
3
|
import {
|
|
4
4
|
scanServerActionsEnriched
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-5OFPQK6N.js";
|
|
6
6
|
import "./chunk-KT3MWNZG.js";
|
|
7
7
|
|
|
8
8
|
// src/vite-plugin/actions-virtual-module.ts
|
|
@@ -65,9 +65,14 @@ function actionsVirtualModule(opts) {
|
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
function generateActionsDts(entries) {
|
|
68
|
-
const lines = entries.map(
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
const lines = entries.map((entry) => {
|
|
69
|
+
const invokerType = `(input: unknown) => Promise<{ data: unknown; error: undefined } | { data: undefined; error: { code: string; message: string; fields?: Record<string, string[]>; type?: string } }>`;
|
|
70
|
+
if (entry.schemaFilePath !== void 0) {
|
|
71
|
+
const schemaImportPath = entry.schemaFilePath.replace(/\\/g, "/").replace(/\.(ts|tsx|js|jsx)$/, ".js");
|
|
72
|
+
return ` ${JSON.stringify(entry.name)}: (${invokerType}) & { readonly __zodSchema: typeof import(${JSON.stringify(schemaImportPath)}).schema }`;
|
|
73
|
+
}
|
|
74
|
+
return ` ${JSON.stringify(entry.name)}: ${invokerType}`;
|
|
75
|
+
}).join("\n");
|
|
71
76
|
return `// AUTO-GENERATED by @theo/actions virtual module. DO NOT EDIT MANUALLY.
|
|
72
77
|
declare module '@theo/actions' {
|
|
73
78
|
export const actions: {
|
|
@@ -85,12 +90,24 @@ function safeScan(serverDir) {
|
|
|
85
90
|
}
|
|
86
91
|
function generateClientFacade(entries) {
|
|
87
92
|
const entryLines = entries.map((entry) => ` ${JSON.stringify(entry.name)}: ${JSON.stringify(entry.urlPath)},`).join("\n");
|
|
93
|
+
const schemaEntries = entries.filter((e) => e.schemaFilePath !== void 0);
|
|
94
|
+
const schemaImports = schemaEntries.map((entry, idx) => {
|
|
95
|
+
const importPath = entry.schemaFilePath.replace(/\\/g, "/");
|
|
96
|
+
return `import { schema as __theoSchema${idx} } from ${JSON.stringify(importPath)}`;
|
|
97
|
+
}).join("\n");
|
|
98
|
+
const schemaMapLines = schemaEntries.map((entry, idx) => ` ${JSON.stringify(entry.name)}: __theoSchema${idx},`).join("\n");
|
|
88
99
|
return `// AUTO-GENERATED by @theo/actions virtual module
|
|
89
100
|
/* eslint-disable */
|
|
101
|
+
${schemaImports}
|
|
102
|
+
|
|
90
103
|
const ACTION_URL_MAP = {
|
|
91
104
|
${entryLines}
|
|
92
105
|
}
|
|
93
106
|
|
|
107
|
+
const ACTION_SCHEMA_MAP = {
|
|
108
|
+
${schemaMapLines}
|
|
109
|
+
}
|
|
110
|
+
|
|
94
111
|
// Devtools telemetry \u2014 fires in the SAME realm as the Overlay React tree
|
|
95
112
|
// (browser), so the Actions tab updates immediately on each call.
|
|
96
113
|
// The Overlay component installs \`window.__theoDevtoolsDispatcher\` on
|
|
@@ -152,12 +169,29 @@ async function callAction(name, url, input) {
|
|
|
152
169
|
return result
|
|
153
170
|
}
|
|
154
171
|
|
|
172
|
+
// P#4 plugin-forms cache: callable proxies must be stable per action so that
|
|
173
|
+
// repeated property access does not allocate + reset the __zodSchema attach.
|
|
174
|
+
const ACTION_CALLABLE_CACHE = {}
|
|
175
|
+
|
|
155
176
|
export const actions = new Proxy({}, {
|
|
156
177
|
get(target, prop) {
|
|
157
178
|
if (typeof prop !== 'string') return undefined
|
|
158
179
|
const url = ACTION_URL_MAP[prop]
|
|
159
180
|
if (!url) return undefined
|
|
160
|
-
|
|
181
|
+
if (!ACTION_CALLABLE_CACHE[prop]) {
|
|
182
|
+
const callable = (input) => callAction(prop, url, input)
|
|
183
|
+
const schema = ACTION_SCHEMA_MAP[prop]
|
|
184
|
+
if (schema !== undefined) {
|
|
185
|
+
Object.defineProperty(callable, '__zodSchema', {
|
|
186
|
+
value: schema,
|
|
187
|
+
enumerable: false,
|
|
188
|
+
writable: false,
|
|
189
|
+
configurable: false,
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
ACTION_CALLABLE_CACHE[prop] = callable
|
|
193
|
+
}
|
|
194
|
+
return ACTION_CALLABLE_CACHE[prop]
|
|
161
195
|
},
|
|
162
196
|
})
|
|
163
197
|
`;
|
|
@@ -172,4 +206,4 @@ export const actions = manifest
|
|
|
172
206
|
export {
|
|
173
207
|
actionsVirtualModule
|
|
174
208
|
};
|
|
175
|
-
//# sourceMappingURL=actions-virtual-module-
|
|
209
|
+
//# sourceMappingURL=actions-virtual-module-HWNTIEPI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/vite-plugin/actions-virtual-module.ts"],"sourcesContent":["/**\n * Vite plugin: resolves `@theo/actions` virtual module and emits a typed\n * Proxy facade that calls `theoFetch('/_actions/<name>', body)` against the\n * server-side action handlers.\n *\n * Per plan g3-server-actions-and-useaction v1.2 § Phase 3 / T3.1 + ADR D4.\n * Astro `vite-plugin-actions.ts` modular pattern. Single focused plugin\n * (not monolithic), virtual module + `configureServer` watcher.\n *\n * Module shape emitted on client environment: thin proxy that forwards\n * `actions.<name>(input)` to `POST /_actions/<name>` with JSON or\n * FormData body. On server environment: re-export of the manifest for\n * SSR / module-loader access (T1.3 sub-C consumer).\n *\n * EC-15 (DOCUMENT): HMR uses Vite default invalidation — coarse but\n * correct. Optimization (fine-grained invalidation of only consumers of\n * `@theo/actions`) deferred to G3.1 if dev-server p95 reload > 500ms.\n */\nimport { existsSync, mkdirSync, renameSync, watch as fsWatch, writeFileSync } from 'node:fs'\nimport { dirname, join, posix, resolve } from 'node:path'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\nimport { scanServerActionsEnriched, type ActionManifestEntry } from '../server/scan/action-scan.js'\n\nconst VIRTUAL_ID = '@theo/actions'\nconst RESOLVED_ID = `\\0${VIRTUAL_ID}`\n\nexport interface ActionsVirtualModuleOptions {\n /** Path to server directory (where `actions/` lives). */\n serverDir: string\n /**\n * Optional `.theo/` output dir for emitting `actions.d.ts`. When provided,\n * the plugin writes an ambient `declare module '@theo/actions'` so TS in\n * consumer apps can resolve the virtual module import. Mirrors the G1\n * `app-typed-client` emit pattern.\n */\n distDir?: string\n}\n\nexport function actionsVirtualModule(opts: ActionsVirtualModuleOptions): Plugin {\n const { serverDir, distDir } = opts\n\n function emitActionsDts(): void {\n if (distDir === undefined) return\n const entries = safeScan(serverDir)\n const dtsPath = posix.join(resolve(distDir).replace(/\\\\/g, '/'), 'actions.d.ts')\n const content = generateActionsDts(entries)\n try {\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n mkdirSync(dirname(dtsPath), { recursive: true })\n const tmp = `${dtsPath}.${process.pid}.tmp`\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n writeFileSync(tmp, content)\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n renameSync(tmp, dtsPath)\n } catch (err) {\n console.error('[theokit:actions-virtual-module] dts emit error:', err)\n }\n }\n\n return {\n name: 'theo:actions-virtual-module',\n enforce: 'pre',\n configResolved() {\n emitActionsDts()\n },\n buildEnd() {\n emitActionsDts()\n },\n resolveId(id: string) {\n if (id === VIRTUAL_ID) return RESOLVED_ID\n return undefined\n },\n load(id: string) {\n if (id !== RESOLVED_ID) return undefined\n const entries = safeScan(serverDir)\n const ctx = this as unknown as { environment?: { name?: string } } | undefined\n const envName = ctx?.environment?.name\n const isClient = envName === 'client' || envName === undefined\n return isClient ? generateClientFacade(entries) : generateServerManifest(entries)\n },\n configureServer(server: ViteDevServer) {\n const actionsDir = join(serverDir, 'actions')\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n if (!existsSync(actionsDir)) return\n // EC-15: coarse invalidation — invalidate ALL modules on actions file change.\n // Optimization deferred per plan ADR D4 note.\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time path\n const watcher = fsWatch(actionsDir, { recursive: true }, () => {\n emitActionsDts()\n const mod = server.moduleGraph.getModuleById(RESOLVED_ID)\n if (mod) {\n server.moduleGraph.invalidateModule(mod)\n }\n server.ws.send({ type: 'full-reload' })\n })\n server.httpServer?.once('close', () => {\n watcher.close()\n })\n },\n }\n}\n\n/**\n * Emit `.theo/actions.d.ts` — ambient declaration of `@theo/actions` so\n * consumer TS apps can `import { actions } from '@theo/actions'` and get\n * typed completions for each scanned action name. Each entry maps name to\n * a generic `(input: unknown) => Promise<{data, error}>` invoker — the full\n * input/output inference from defineAction would require either monomorphic\n * dts-bundle or virtual type emit (deferred to a future plan).\n */\nfunction generateActionsDts(entries: readonly ActionManifestEntry[]): string {\n const lines = entries\n .map((entry) => {\n const invokerType = `(input: unknown) => Promise<{ data: unknown; error: undefined } | { data: undefined; error: { code: string; message: string; fields?: Record<string, string[]>; type?: string } }>`\n // P#4 plugin-forms typed __zodSchema (per plan p4-plugin-forms v1.1 EC-3).\n // When schemaFilePath present, emit intersection type so consumers see both\n // the callable invoker AND the __zodSchema field with the exact schema type.\n if (entry.schemaFilePath !== undefined) {\n const schemaImportPath = entry.schemaFilePath\n .replace(/\\\\/g, '/')\n .replace(/\\.(ts|tsx|js|jsx)$/, '.js')\n return ` ${JSON.stringify(entry.name)}: (${invokerType}) & { readonly __zodSchema: typeof import(${JSON.stringify(schemaImportPath)}).schema }`\n }\n return ` ${JSON.stringify(entry.name)}: ${invokerType}`\n })\n .join('\\n')\n return `// AUTO-GENERATED by @theo/actions virtual module. DO NOT EDIT MANUALLY.\ndeclare module '@theo/actions' {\n export const actions: {\n${lines}\n }\n}\n`\n}\n\nfunction safeScan(serverDir: string): ActionManifestEntry[] {\n try {\n return scanServerActionsEnriched(serverDir)\n } catch {\n // ActionScanError surfaces at build time; for HMR keep an empty manifest\n // so the dev server doesn't crash mid-edit.\n return []\n }\n}\n\n/**\n * Client-side module: emits a Proxy facade. For each action `name` in\n * manifest, `actions.<name>(input)` POSTs to `/_actions/<name>` with JSON\n * body (or FormData when input is a FormData instance) and parses the\n * `application/json+devalue` response via the runtime helper.\n */\nfunction generateClientFacade(entries: ActionManifestEntry[]): string {\n const entryLines = entries\n .map((entry) => ` ${JSON.stringify(entry.name)}: ${JSON.stringify(entry.urlPath)},`)\n .join('\\n')\n\n // P#4 plugin-forms shared-schema convention (per plan p4-plugin-forms v1.1 T1.1).\n // Emit ESM imports + ACTION_SCHEMA_MAP so the Proxy can attach __zodSchema.\n // Vite resolves absolute filesystem paths via its module graph; schema files\n // are isomorphic (zod-only, no server APIs) so they bundle into the client.\n const schemaEntries = entries.filter((e) => e.schemaFilePath !== undefined)\n const schemaImports = schemaEntries\n .map((entry, idx) => {\n // schemaEntries was filtered by `e.schemaFilePath !== undefined` above,\n // so the assertion is provably safe in this scope.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const importPath = entry.schemaFilePath!.replace(/\\\\/g, '/')\n return `import { schema as __theoSchema${idx} } from ${JSON.stringify(importPath)}`\n })\n .join('\\n')\n const schemaMapLines = schemaEntries\n .map((entry, idx) => ` ${JSON.stringify(entry.name)}: __theoSchema${idx},`)\n .join('\\n')\n\n return `// AUTO-GENERATED by @theo/actions virtual module\n/* eslint-disable */\n${schemaImports}\n\nconst ACTION_URL_MAP = {\n${entryLines}\n}\n\nconst ACTION_SCHEMA_MAP = {\n${schemaMapLines}\n}\n\n// Devtools telemetry — fires in the SAME realm as the Overlay React tree\n// (browser), so the Actions tab updates immediately on each call.\n// The Overlay component installs \\`window.__theoDevtoolsDispatcher\\` on\n// mount (dev only; prod tree-shakes the Overlay entirely). Reading the\n// global avoids a cross-package dynamic import that Vite's virtual-module\n// resolver cannot trace. Same pattern as React DevTools'\n// \\`__REACT_DEVTOOLS_GLOBAL_HOOK__\\`.\nfunction emitTelemetry(name, startedAt, input, outcome) {\n if (typeof window === 'undefined') return\n const dispatcher = window.__theoDevtoolsDispatcher\n if (!dispatcher || typeof dispatcher.onActionCall !== 'function') return\n try {\n dispatcher.onActionCall({\n id: 'act-' + String(Date.now()) + '-' + Math.random().toString(36).slice(2, 8),\n timestamp: startedAt,\n name,\n input,\n output: outcome.status === 'success' ? outcome.data : undefined,\n error: outcome.status === 'error' ? outcome.error : undefined,\n durationMs: Date.now() - startedAt,\n status: outcome.status,\n })\n } catch (_) {\n // dispatcher threw — devtools UI bug; never bubble to consumer code\n }\n}\n\nasync function callAction(name, url, input) {\n const startedAt = Date.now()\n const isForm = typeof FormData !== 'undefined' && input instanceof FormData\n const headers = { 'x-theo-action': '1' }\n const body = isForm ? input : JSON.stringify(input ?? {})\n if (!isForm) headers['content-type'] = 'application/json'\n let response\n try {\n response = await fetch(url, { method: 'POST', headers, body, credentials: 'same-origin' })\n } catch (e) {\n const err = { code: 'NETWORK_ERROR', message: e && e.message ? e.message : 'fetch failed' }\n emitTelemetry(name, startedAt, input, { status: 'error', error: err })\n return { data: undefined, error: err }\n }\n const contentType = response.headers.get('content-type') ?? ''\n let result\n if (response.status === 204) {\n result = { data: undefined, error: undefined }\n } else if (contentType.startsWith('application/json+devalue')) {\n const text = await response.text()\n const { parse } = await import('devalue')\n result = { data: parse(text, { URL: (h) => new URL(h) }), error: undefined }\n } else if (contentType.startsWith('application/json')) {\n const json = await response.json()\n result = response.ok ? { data: json, error: undefined } : { data: undefined, error: json }\n } else {\n result = { data: undefined, error: { code: 'INTERNAL_SERVER_ERROR', message: await response.text() } }\n }\n emitTelemetry(name, startedAt, input, result.error\n ? { status: 'error', error: result.error }\n : { status: 'success', data: result.data })\n return result\n}\n\n// P#4 plugin-forms cache: callable proxies must be stable per action so that\n// repeated property access does not allocate + reset the __zodSchema attach.\nconst ACTION_CALLABLE_CACHE = {}\n\nexport const actions = new Proxy({}, {\n get(target, prop) {\n if (typeof prop !== 'string') return undefined\n const url = ACTION_URL_MAP[prop]\n if (!url) return undefined\n if (!ACTION_CALLABLE_CACHE[prop]) {\n const callable = (input) => callAction(prop, url, input)\n const schema = ACTION_SCHEMA_MAP[prop]\n if (schema !== undefined) {\n Object.defineProperty(callable, '__zodSchema', {\n value: schema,\n enumerable: false,\n writable: false,\n configurable: false,\n })\n }\n ACTION_CALLABLE_CACHE[prop] = callable\n }\n return ACTION_CALLABLE_CACHE[prop]\n },\n})\n`\n}\n\n/**\n * Server-side module: re-exports the manifest entries for SSR / module-\n * loader consumption (T1.3 sub-C in action-execute.ts will route requests\n * via this manifest).\n */\nfunction generateServerManifest(entries: ActionManifestEntry[]): string {\n return `// AUTO-GENERATED by @theo/actions virtual module (server env)\n/* eslint-disable */\nexport const manifest = ${JSON.stringify(entries, null, 2)}\nexport const actions = manifest\n`\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
scanServerActionsEnriched
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-7TOMTMHT.js";
|
|
4
4
|
import "./chunk-X2VVCJ4V.js";
|
|
5
5
|
import "./chunk-DGUM43GV.js";
|
|
6
6
|
|
|
@@ -64,9 +64,14 @@ function actionsVirtualModule(opts) {
|
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
function generateActionsDts(entries) {
|
|
67
|
-
const lines = entries.map(
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
const lines = entries.map((entry) => {
|
|
68
|
+
const invokerType = `(input: unknown) => Promise<{ data: unknown; error: undefined } | { data: undefined; error: { code: string; message: string; fields?: Record<string, string[]>; type?: string } }>`;
|
|
69
|
+
if (entry.schemaFilePath !== void 0) {
|
|
70
|
+
const schemaImportPath = entry.schemaFilePath.replace(/\\/g, "/").replace(/\.(ts|tsx|js|jsx)$/, ".js");
|
|
71
|
+
return ` ${JSON.stringify(entry.name)}: (${invokerType}) & { readonly __zodSchema: typeof import(${JSON.stringify(schemaImportPath)}).schema }`;
|
|
72
|
+
}
|
|
73
|
+
return ` ${JSON.stringify(entry.name)}: ${invokerType}`;
|
|
74
|
+
}).join("\n");
|
|
70
75
|
return `// AUTO-GENERATED by @theo/actions virtual module. DO NOT EDIT MANUALLY.
|
|
71
76
|
declare module '@theo/actions' {
|
|
72
77
|
export const actions: {
|
|
@@ -84,12 +89,24 @@ function safeScan(serverDir) {
|
|
|
84
89
|
}
|
|
85
90
|
function generateClientFacade(entries) {
|
|
86
91
|
const entryLines = entries.map((entry) => ` ${JSON.stringify(entry.name)}: ${JSON.stringify(entry.urlPath)},`).join("\n");
|
|
92
|
+
const schemaEntries = entries.filter((e) => e.schemaFilePath !== void 0);
|
|
93
|
+
const schemaImports = schemaEntries.map((entry, idx) => {
|
|
94
|
+
const importPath = entry.schemaFilePath.replace(/\\/g, "/");
|
|
95
|
+
return `import { schema as __theoSchema${idx} } from ${JSON.stringify(importPath)}`;
|
|
96
|
+
}).join("\n");
|
|
97
|
+
const schemaMapLines = schemaEntries.map((entry, idx) => ` ${JSON.stringify(entry.name)}: __theoSchema${idx},`).join("\n");
|
|
87
98
|
return `// AUTO-GENERATED by @theo/actions virtual module
|
|
88
99
|
/* eslint-disable */
|
|
100
|
+
${schemaImports}
|
|
101
|
+
|
|
89
102
|
const ACTION_URL_MAP = {
|
|
90
103
|
${entryLines}
|
|
91
104
|
}
|
|
92
105
|
|
|
106
|
+
const ACTION_SCHEMA_MAP = {
|
|
107
|
+
${schemaMapLines}
|
|
108
|
+
}
|
|
109
|
+
|
|
93
110
|
// Devtools telemetry \u2014 fires in the SAME realm as the Overlay React tree
|
|
94
111
|
// (browser), so the Actions tab updates immediately on each call.
|
|
95
112
|
// The Overlay component installs \`window.__theoDevtoolsDispatcher\` on
|
|
@@ -151,12 +168,29 @@ async function callAction(name, url, input) {
|
|
|
151
168
|
return result
|
|
152
169
|
}
|
|
153
170
|
|
|
171
|
+
// P#4 plugin-forms cache: callable proxies must be stable per action so that
|
|
172
|
+
// repeated property access does not allocate + reset the __zodSchema attach.
|
|
173
|
+
const ACTION_CALLABLE_CACHE = {}
|
|
174
|
+
|
|
154
175
|
export const actions = new Proxy({}, {
|
|
155
176
|
get(target, prop) {
|
|
156
177
|
if (typeof prop !== 'string') return undefined
|
|
157
178
|
const url = ACTION_URL_MAP[prop]
|
|
158
179
|
if (!url) return undefined
|
|
159
|
-
|
|
180
|
+
if (!ACTION_CALLABLE_CACHE[prop]) {
|
|
181
|
+
const callable = (input) => callAction(prop, url, input)
|
|
182
|
+
const schema = ACTION_SCHEMA_MAP[prop]
|
|
183
|
+
if (schema !== undefined) {
|
|
184
|
+
Object.defineProperty(callable, '__zodSchema', {
|
|
185
|
+
value: schema,
|
|
186
|
+
enumerable: false,
|
|
187
|
+
writable: false,
|
|
188
|
+
configurable: false,
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
ACTION_CALLABLE_CACHE[prop] = callable
|
|
192
|
+
}
|
|
193
|
+
return ACTION_CALLABLE_CACHE[prop]
|
|
160
194
|
},
|
|
161
195
|
})
|
|
162
196
|
`;
|
|
@@ -171,4 +205,4 @@ export const actions = manifest
|
|
|
171
205
|
export {
|
|
172
206
|
actionsVirtualModule
|
|
173
207
|
};
|
|
174
|
-
//# sourceMappingURL=actions-virtual-module-
|
|
208
|
+
//# sourceMappingURL=actions-virtual-module-SEIPACG5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/vite-plugin/actions-virtual-module.ts"],"sourcesContent":["/**\n * Vite plugin: resolves `@theo/actions` virtual module and emits a typed\n * Proxy facade that calls `theoFetch('/_actions/<name>', body)` against the\n * server-side action handlers.\n *\n * Per plan g3-server-actions-and-useaction v1.2 § Phase 3 / T3.1 + ADR D4.\n * Astro `vite-plugin-actions.ts` modular pattern. Single focused plugin\n * (not monolithic), virtual module + `configureServer` watcher.\n *\n * Module shape emitted on client environment: thin proxy that forwards\n * `actions.<name>(input)` to `POST /_actions/<name>` with JSON or\n * FormData body. On server environment: re-export of the manifest for\n * SSR / module-loader access (T1.3 sub-C consumer).\n *\n * EC-15 (DOCUMENT): HMR uses Vite default invalidation — coarse but\n * correct. Optimization (fine-grained invalidation of only consumers of\n * `@theo/actions`) deferred to G3.1 if dev-server p95 reload > 500ms.\n */\nimport { existsSync, mkdirSync, renameSync, watch as fsWatch, writeFileSync } from 'node:fs'\nimport { dirname, join, posix, resolve } from 'node:path'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\nimport { scanServerActionsEnriched, type ActionManifestEntry } from '../server/scan/action-scan.js'\n\nconst VIRTUAL_ID = '@theo/actions'\nconst RESOLVED_ID = `\\0${VIRTUAL_ID}`\n\nexport interface ActionsVirtualModuleOptions {\n /** Path to server directory (where `actions/` lives). */\n serverDir: string\n /**\n * Optional `.theo/` output dir for emitting `actions.d.ts`. When provided,\n * the plugin writes an ambient `declare module '@theo/actions'` so TS in\n * consumer apps can resolve the virtual module import. Mirrors the G1\n * `app-typed-client` emit pattern.\n */\n distDir?: string\n}\n\nexport function actionsVirtualModule(opts: ActionsVirtualModuleOptions): Plugin {\n const { serverDir, distDir } = opts\n\n function emitActionsDts(): void {\n if (distDir === undefined) return\n const entries = safeScan(serverDir)\n const dtsPath = posix.join(resolve(distDir).replace(/\\\\/g, '/'), 'actions.d.ts')\n const content = generateActionsDts(entries)\n try {\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n mkdirSync(dirname(dtsPath), { recursive: true })\n const tmp = `${dtsPath}.${process.pid}.tmp`\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n writeFileSync(tmp, content)\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n renameSync(tmp, dtsPath)\n } catch (err) {\n console.error('[theokit:actions-virtual-module] dts emit error:', err)\n }\n }\n\n return {\n name: 'theo:actions-virtual-module',\n enforce: 'pre',\n configResolved() {\n emitActionsDts()\n },\n buildEnd() {\n emitActionsDts()\n },\n resolveId(id: string) {\n if (id === VIRTUAL_ID) return RESOLVED_ID\n return undefined\n },\n load(id: string) {\n if (id !== RESOLVED_ID) return undefined\n const entries = safeScan(serverDir)\n const ctx = this as unknown as { environment?: { name?: string } } | undefined\n const envName = ctx?.environment?.name\n const isClient = envName === 'client' || envName === undefined\n return isClient ? generateClientFacade(entries) : generateServerManifest(entries)\n },\n configureServer(server: ViteDevServer) {\n const actionsDir = join(serverDir, 'actions')\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time derived path\n if (!existsSync(actionsDir)) return\n // EC-15: coarse invalidation — invalidate ALL modules on actions file change.\n // Optimization deferred per plan ADR D4 note.\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time path\n const watcher = fsWatch(actionsDir, { recursive: true }, () => {\n emitActionsDts()\n const mod = server.moduleGraph.getModuleById(RESOLVED_ID)\n if (mod) {\n server.moduleGraph.invalidateModule(mod)\n }\n server.ws.send({ type: 'full-reload' })\n })\n server.httpServer?.once('close', () => {\n watcher.close()\n })\n },\n }\n}\n\n/**\n * Emit `.theo/actions.d.ts` — ambient declaration of `@theo/actions` so\n * consumer TS apps can `import { actions } from '@theo/actions'` and get\n * typed completions for each scanned action name. Each entry maps name to\n * a generic `(input: unknown) => Promise<{data, error}>` invoker — the full\n * input/output inference from defineAction would require either monomorphic\n * dts-bundle or virtual type emit (deferred to a future plan).\n */\nfunction generateActionsDts(entries: readonly ActionManifestEntry[]): string {\n const lines = entries\n .map((entry) => {\n const invokerType = `(input: unknown) => Promise<{ data: unknown; error: undefined } | { data: undefined; error: { code: string; message: string; fields?: Record<string, string[]>; type?: string } }>`\n // P#4 plugin-forms typed __zodSchema (per plan p4-plugin-forms v1.1 EC-3).\n // When schemaFilePath present, emit intersection type so consumers see both\n // the callable invoker AND the __zodSchema field with the exact schema type.\n if (entry.schemaFilePath !== undefined) {\n const schemaImportPath = entry.schemaFilePath\n .replace(/\\\\/g, '/')\n .replace(/\\.(ts|tsx|js|jsx)$/, '.js')\n return ` ${JSON.stringify(entry.name)}: (${invokerType}) & { readonly __zodSchema: typeof import(${JSON.stringify(schemaImportPath)}).schema }`\n }\n return ` ${JSON.stringify(entry.name)}: ${invokerType}`\n })\n .join('\\n')\n return `// AUTO-GENERATED by @theo/actions virtual module. DO NOT EDIT MANUALLY.\ndeclare module '@theo/actions' {\n export const actions: {\n${lines}\n }\n}\n`\n}\n\nfunction safeScan(serverDir: string): ActionManifestEntry[] {\n try {\n return scanServerActionsEnriched(serverDir)\n } catch {\n // ActionScanError surfaces at build time; for HMR keep an empty manifest\n // so the dev server doesn't crash mid-edit.\n return []\n }\n}\n\n/**\n * Client-side module: emits a Proxy facade. For each action `name` in\n * manifest, `actions.<name>(input)` POSTs to `/_actions/<name>` with JSON\n * body (or FormData when input is a FormData instance) and parses the\n * `application/json+devalue` response via the runtime helper.\n */\nfunction generateClientFacade(entries: ActionManifestEntry[]): string {\n const entryLines = entries\n .map((entry) => ` ${JSON.stringify(entry.name)}: ${JSON.stringify(entry.urlPath)},`)\n .join('\\n')\n\n // P#4 plugin-forms shared-schema convention (per plan p4-plugin-forms v1.1 T1.1).\n // Emit ESM imports + ACTION_SCHEMA_MAP so the Proxy can attach __zodSchema.\n // Vite resolves absolute filesystem paths via its module graph; schema files\n // are isomorphic (zod-only, no server APIs) so they bundle into the client.\n const schemaEntries = entries.filter((e) => e.schemaFilePath !== undefined)\n const schemaImports = schemaEntries\n .map((entry, idx) => {\n // schemaEntries was filtered by `e.schemaFilePath !== undefined` above,\n // so the assertion is provably safe in this scope.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const importPath = entry.schemaFilePath!.replace(/\\\\/g, '/')\n return `import { schema as __theoSchema${idx} } from ${JSON.stringify(importPath)}`\n })\n .join('\\n')\n const schemaMapLines = schemaEntries\n .map((entry, idx) => ` ${JSON.stringify(entry.name)}: __theoSchema${idx},`)\n .join('\\n')\n\n return `// AUTO-GENERATED by @theo/actions virtual module\n/* eslint-disable */\n${schemaImports}\n\nconst ACTION_URL_MAP = {\n${entryLines}\n}\n\nconst ACTION_SCHEMA_MAP = {\n${schemaMapLines}\n}\n\n// Devtools telemetry — fires in the SAME realm as the Overlay React tree\n// (browser), so the Actions tab updates immediately on each call.\n// The Overlay component installs \\`window.__theoDevtoolsDispatcher\\` on\n// mount (dev only; prod tree-shakes the Overlay entirely). Reading the\n// global avoids a cross-package dynamic import that Vite's virtual-module\n// resolver cannot trace. Same pattern as React DevTools'\n// \\`__REACT_DEVTOOLS_GLOBAL_HOOK__\\`.\nfunction emitTelemetry(name, startedAt, input, outcome) {\n if (typeof window === 'undefined') return\n const dispatcher = window.__theoDevtoolsDispatcher\n if (!dispatcher || typeof dispatcher.onActionCall !== 'function') return\n try {\n dispatcher.onActionCall({\n id: 'act-' + String(Date.now()) + '-' + Math.random().toString(36).slice(2, 8),\n timestamp: startedAt,\n name,\n input,\n output: outcome.status === 'success' ? outcome.data : undefined,\n error: outcome.status === 'error' ? outcome.error : undefined,\n durationMs: Date.now() - startedAt,\n status: outcome.status,\n })\n } catch (_) {\n // dispatcher threw — devtools UI bug; never bubble to consumer code\n }\n}\n\nasync function callAction(name, url, input) {\n const startedAt = Date.now()\n const isForm = typeof FormData !== 'undefined' && input instanceof FormData\n const headers = { 'x-theo-action': '1' }\n const body = isForm ? input : JSON.stringify(input ?? {})\n if (!isForm) headers['content-type'] = 'application/json'\n let response\n try {\n response = await fetch(url, { method: 'POST', headers, body, credentials: 'same-origin' })\n } catch (e) {\n const err = { code: 'NETWORK_ERROR', message: e && e.message ? e.message : 'fetch failed' }\n emitTelemetry(name, startedAt, input, { status: 'error', error: err })\n return { data: undefined, error: err }\n }\n const contentType = response.headers.get('content-type') ?? ''\n let result\n if (response.status === 204) {\n result = { data: undefined, error: undefined }\n } else if (contentType.startsWith('application/json+devalue')) {\n const text = await response.text()\n const { parse } = await import('devalue')\n result = { data: parse(text, { URL: (h) => new URL(h) }), error: undefined }\n } else if (contentType.startsWith('application/json')) {\n const json = await response.json()\n result = response.ok ? { data: json, error: undefined } : { data: undefined, error: json }\n } else {\n result = { data: undefined, error: { code: 'INTERNAL_SERVER_ERROR', message: await response.text() } }\n }\n emitTelemetry(name, startedAt, input, result.error\n ? { status: 'error', error: result.error }\n : { status: 'success', data: result.data })\n return result\n}\n\n// P#4 plugin-forms cache: callable proxies must be stable per action so that\n// repeated property access does not allocate + reset the __zodSchema attach.\nconst ACTION_CALLABLE_CACHE = {}\n\nexport const actions = new Proxy({}, {\n get(target, prop) {\n if (typeof prop !== 'string') return undefined\n const url = ACTION_URL_MAP[prop]\n if (!url) return undefined\n if (!ACTION_CALLABLE_CACHE[prop]) {\n const callable = (input) => callAction(prop, url, input)\n const schema = ACTION_SCHEMA_MAP[prop]\n if (schema !== undefined) {\n Object.defineProperty(callable, '__zodSchema', {\n value: schema,\n enumerable: false,\n writable: false,\n configurable: false,\n })\n }\n ACTION_CALLABLE_CACHE[prop] = callable\n }\n return ACTION_CALLABLE_CACHE[prop]\n },\n})\n`\n}\n\n/**\n * Server-side module: re-exports the manifest entries for SSR / module-\n * loader consumption (T1.3 sub-C in action-execute.ts will route requests\n * via this manifest).\n */\nfunction generateServerManifest(entries: ActionManifestEntry[]): string {\n return `// AUTO-GENERATED by @theo/actions virtual module (server env)\n/* eslint-disable */\nexport const manifest = ${JSON.stringify(entries, null, 2)}\nexport const actions = manifest\n`\n}\n"],"mappings":"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","names":[]}
|
package/dist/add-2ZFFGGE4.js
CHANGED
|
File without changes
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import "tsx/esm";
|
|
3
3
|
import {
|
|
4
4
|
generateManifest
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-BE2LKDI7.js";
|
|
6
6
|
import {
|
|
7
7
|
HTTP_METHODS
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-SNDZQ64K.js";
|
|
9
|
+
import "./chunk-5OFPQK6N.js";
|
|
10
10
|
import "./chunk-KT3MWNZG.js";
|
|
11
11
|
|
|
12
12
|
// src/vite-plugin/app-typed-client.ts
|
|
@@ -278,4 +278,4 @@ export {
|
|
|
278
278
|
emitClientDts,
|
|
279
279
|
generateClientDts
|
|
280
280
|
};
|
|
281
|
-
//# sourceMappingURL=app-typed-client-
|
|
281
|
+
//# sourceMappingURL=app-typed-client-6GC6VWXS.js.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
generateManifest
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-YWWHK7R6.js";
|
|
4
4
|
import "./chunk-2BQYEBCK.js";
|
|
5
|
-
import "./chunk-
|
|
5
|
+
import "./chunk-7TOMTMHT.js";
|
|
6
6
|
import "./chunk-X2VVCJ4V.js";
|
|
7
7
|
import {
|
|
8
8
|
HTTP_METHODS
|
|
@@ -278,4 +278,4 @@ export {
|
|
|
278
278
|
emitClientDts,
|
|
279
279
|
generateClientDts
|
|
280
280
|
};
|
|
281
|
-
//# sourceMappingURL=app-typed-client-
|
|
281
|
+
//# sourceMappingURL=app-typed-client-US2ZXBEC.js.map
|
|
File without changes
|
|
File without changes
|
|
@@ -22,11 +22,11 @@ import {
|
|
|
22
22
|
import {
|
|
23
23
|
generateManifest,
|
|
24
24
|
writeManifest as writeManifest2
|
|
25
|
-
} from "./chunk-
|
|
26
|
-
import "./chunk-
|
|
25
|
+
} from "./chunk-BE2LKDI7.js";
|
|
26
|
+
import "./chunk-SNDZQ64K.js";
|
|
27
27
|
import {
|
|
28
28
|
walkSourceFiles
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-5OFPQK6N.js";
|
|
30
30
|
import "./chunk-KT3MWNZG.js";
|
|
31
31
|
|
|
32
32
|
// src/cli/commands/build.ts
|
|
@@ -520,7 +520,7 @@ async function buildCommand(options) {
|
|
|
520
520
|
`);
|
|
521
521
|
}
|
|
522
522
|
async function runAdapterBuild(target, config, cwd) {
|
|
523
|
-
const { theoPluginAsync } = await import("./vite-plugin-
|
|
523
|
+
const { theoPluginAsync } = await import("./vite-plugin-6MPHTKIW.js");
|
|
524
524
|
const { default: react } = await import("@vitejs/plugin-react");
|
|
525
525
|
const ctx = {
|
|
526
526
|
// `react()` may return Plugin or Plugin[] depending on version; spread the
|
|
@@ -595,4 +595,4 @@ function relativize3(absPath, root) {
|
|
|
595
595
|
export {
|
|
596
596
|
buildCommand
|
|
597
597
|
};
|
|
598
|
-
//# sourceMappingURL=build-
|
|
598
|
+
//# sourceMappingURL=build-MREW6MVS.js.map
|
|
File without changes
|
package/dist/bun-K73TQEWC.js
CHANGED
|
File without changes
|
package/dist/check-PZR67OY5.js
CHANGED
|
File without changes
|
package/dist/chunk-5ODOE6EF.js
CHANGED
|
File without changes
|
|
@@ -70,6 +70,7 @@ function scanServerActionsEnriched(serverDir) {
|
|
|
70
70
|
walkSourceFiles(actionsDir, { extensions: ACTION_EXTENSIONS }, (absPath) => {
|
|
71
71
|
if (TEST_FILE_RE.test(absPath)) return;
|
|
72
72
|
const rel = relative(actionsDir, absPath).replace(/\\/g, "/");
|
|
73
|
+
if (rel.startsWith("schemas/")) return;
|
|
73
74
|
const name = rel.slice(0, -extname2(rel).length);
|
|
74
75
|
const basename = name.includes("/") ? name.split("/").pop() ?? name : name;
|
|
75
76
|
if (RESERVED_NAMES.has(basename)) {
|
|
@@ -84,6 +85,7 @@ function scanServerActionsEnriched(serverDir) {
|
|
|
84
85
|
const accept = /\baccept\s*:\s*['"]form['"]/.test(stripped) ? "form" : "json";
|
|
85
86
|
const hasInput = /\binput\s*:\s*z\./.test(stripped) || /\binput\s*:\s*\w+\(/.test(stripped);
|
|
86
87
|
const exportNames = extractActionExportNames(stripped);
|
|
88
|
+
const schemaFilePath = name.includes("/") ? void 0 : detectSchemaFile(actionsDir, basename);
|
|
87
89
|
for (const exportName of exportNames) {
|
|
88
90
|
const proxyKey = exportName === "default" ? name : exportName;
|
|
89
91
|
if (seenNames.has(proxyKey)) {
|
|
@@ -94,13 +96,17 @@ function scanServerActionsEnriched(serverDir) {
|
|
|
94
96
|
);
|
|
95
97
|
}
|
|
96
98
|
seenNames.add(proxyKey);
|
|
97
|
-
|
|
99
|
+
const entry = {
|
|
98
100
|
name: proxyKey,
|
|
99
101
|
filePath: absPath,
|
|
100
102
|
urlPath: `/api/__actions/${name}/${exportName}`,
|
|
101
103
|
accept,
|
|
102
104
|
hasInput
|
|
103
|
-
}
|
|
105
|
+
};
|
|
106
|
+
if (schemaFilePath !== void 0) {
|
|
107
|
+
entry.schemaFilePath = schemaFilePath;
|
|
108
|
+
}
|
|
109
|
+
entries.push(entry);
|
|
104
110
|
}
|
|
105
111
|
});
|
|
106
112
|
for (const entry of entries) {
|
|
@@ -135,6 +141,15 @@ function extractActionExportNames(stripped) {
|
|
|
135
141
|
if (names.size === 0) names.add("default");
|
|
136
142
|
return [...names];
|
|
137
143
|
}
|
|
144
|
+
function detectSchemaFile(actionsDir, basename) {
|
|
145
|
+
const schemasDir = join2(actionsDir, "schemas");
|
|
146
|
+
if (!existsSync(schemasDir)) return void 0;
|
|
147
|
+
for (const ext of [".ts", ".tsx", ".js", ".jsx"]) {
|
|
148
|
+
const candidate = join2(schemasDir, `${basename}${ext}`);
|
|
149
|
+
if (existsSync(candidate)) return candidate;
|
|
150
|
+
}
|
|
151
|
+
return void 0;
|
|
152
|
+
}
|
|
138
153
|
function stripComments(source) {
|
|
139
154
|
let out = "";
|
|
140
155
|
let i = 0;
|
|
@@ -162,4 +177,4 @@ export {
|
|
|
162
177
|
scanServerActions,
|
|
163
178
|
scanServerActionsEnriched
|
|
164
179
|
};
|
|
165
|
-
//# sourceMappingURL=chunk-
|
|
180
|
+
//# sourceMappingURL=chunk-5OFPQK6N.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/scan/action-scan.ts","../src/server/_internal/scan-walker.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 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 * `.theo/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"],"mappings":"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","names":["extname","join","join","extname"]}
|