@exposify/next 0.0.0-stage → 0.0.2

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.
@@ -0,0 +1,2046 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/config.ts
21
+ var config_exports = {};
22
+ __export(config_exports, {
23
+ generate: () => generate,
24
+ withExposify: () => withExposify
25
+ });
26
+ module.exports = __toCommonJS(config_exports);
27
+ var import_node_fs7 = require("fs");
28
+ var import_node_path7 = require("path");
29
+ var import_core3 = require("@exposify/core");
30
+
31
+ // src/codegen.ts
32
+ var import_node_crypto = require("crypto");
33
+ var import_node_fs6 = require("fs");
34
+ var import_node_path6 = require("path");
35
+ var import_core2 = require("@exposify/core");
36
+ var import_tinyglobby = require("tinyglobby");
37
+
38
+ // src/ast.ts
39
+ var import_parser = require("@babel/parser");
40
+ var FUNCTION_TYPES = /* @__PURE__ */ new Set(["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]);
41
+ function parseModule(source, file) {
42
+ const plugins = /\.tsx$/.test(file) ? ["typescript", "jsx"] : /\.[mc]?ts$/.test(file) ? ["typescript"] : ["jsx"];
43
+ try {
44
+ return (0, import_parser.parse)(source, { sourceType: "module", plugins, errorRecovery: true }).program;
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+ function walk(node, visit, parents = []) {
50
+ if (!node || typeof node !== "object") return;
51
+ if (Array.isArray(node)) {
52
+ for (const child of node) walk(child, visit, parents);
53
+ return;
54
+ }
55
+ const n = node;
56
+ if (typeof n.type !== "string") return;
57
+ visit(n, parents);
58
+ parents.push(n);
59
+ for (const key of Object.keys(n)) {
60
+ if (key === "loc" || key === "leadingComments" || key === "trailingComments" || key === "innerComments") continue;
61
+ const value = n[key];
62
+ if (value && typeof value === "object") walk(value, visit, parents);
63
+ }
64
+ parents.pop();
65
+ }
66
+ var text = (source, n) => n && typeof n.start === "number" && typeof n.end === "number" ? source.slice(n.start, n.end) : void 0;
67
+ var stringValue = (n) => n?.type === "StringLiteral" ? n.value : n?.type === "TemplateLiteral" && n.expressions.length === 0 ? n.quasis[0]?.value?.cooked : void 0;
68
+ var keyName = (n) => n?.type === "Identifier" ? n.name : stringValue(n);
69
+ function unwrapTs(n) {
70
+ while (n && (n.type === "TSAsExpression" || n.type === "TSSatisfiesExpression" || n.type === "TSNonNullExpression" || n.type === "ParenthesizedExpression")) n = n.expression;
71
+ return n;
72
+ }
73
+ var paramName = (p) => {
74
+ const id = p.type === "AssignmentPattern" ? p.left : p.type === "TSParameterProperty" ? p.parameter : p;
75
+ return id?.type === "Identifier" ? id.name : void 0;
76
+ };
77
+ var lineOf = (n) => n.loc?.start.line ?? 1;
78
+
79
+ // src/fields.ts
80
+ var listScope = (program, file, load) => ({ program, file, lists: /* @__PURE__ */ new Map(), ...load ? { load } : {} });
81
+ var FORM_READS = /* @__PURE__ */ new Set(["get", "getAll", "has"]);
82
+ var FORM_ITERATION = /* @__PURE__ */ new Set(["entries", "keys", "values", "forEach"]);
83
+ var STRING_METHODS = /* @__PURE__ */ new Set(["trim", "trimStart", "trimEnd", "toString", "toLowerCase", "toUpperCase", "normalize", "valueOf"]);
84
+ var NUMBER_FNS = /* @__PURE__ */ new Set(["Number", "parseInt", "parseFloat"]);
85
+ var LOOP_METHODS = /* @__PURE__ */ new Set(["map", "forEach", "flatMap", "filter", "some", "every", "find", "reduce"]);
86
+ var RANK = { string: 0, number: 1, boolean: 2, json: 3, file: 4 };
87
+ var SEARCH_DEPTH = 3;
88
+ var MAX_EXPANDED = 200;
89
+ function merge(a, b) {
90
+ const type = !a.type ? b.type : !b.type ? a.type : RANK[b.type] > RANK[a.type] ? b.type : a.type;
91
+ const values = a.enum ?? b.enum;
92
+ return { ...type ? { type } : {}, ...values ? { enum: values } : {} };
93
+ }
94
+ function constLists(program) {
95
+ const out = /* @__PURE__ */ new Map();
96
+ for (const stmt of program.body) {
97
+ const decl = stmt.type === "ExportNamedDeclaration" ? stmt.declaration : stmt;
98
+ if (decl?.type !== "VariableDeclaration" || decl.kind !== "const") continue;
99
+ for (const d of decl.declarations) {
100
+ const init = unwrapTs(d.init);
101
+ if (d.id.type !== "Identifier" || init?.type !== "ArrayExpression") continue;
102
+ const values = init.elements.map((e) => stringValue(e));
103
+ if (values.length > 0 && values.every((v) => v !== void 0)) out.set(d.id.name, values);
104
+ }
105
+ }
106
+ return out;
107
+ }
108
+ function listNamed(name, st) {
109
+ if (st.lists.has(name)) return st.lists.get(name) ?? void 0;
110
+ let found = constLists(st.program).get(name);
111
+ if (!found && st.load) {
112
+ for (const stmt of st.program.body) {
113
+ if (stmt.type !== "ImportDeclaration" || stmt.importKind === "type") continue;
114
+ const spec = stmt.specifiers.find((s) => s.type === "ImportSpecifier" && s.local.name === name);
115
+ if (!spec) continue;
116
+ const mod = st.load(stmt.source.value, st.file);
117
+ const program = mod && parseModule(mod.source, mod.file);
118
+ if (program) found = constLists(program).get(keyName(spec.imported) ?? name);
119
+ break;
120
+ }
121
+ }
122
+ st.lists.set(name, found ?? null);
123
+ return found;
124
+ }
125
+ function listOf(n, st) {
126
+ n = unwrapTs(n);
127
+ if (n?.type === "Identifier") return listNamed(n.name, st);
128
+ if (n?.type === "ArrayExpression") {
129
+ const values = n.elements.map((e) => stringValue(e));
130
+ return values.length > 0 && values.every((v) => v !== void 0) ? values : void 0;
131
+ }
132
+ return void 0;
133
+ }
134
+ var isFileType = (t) => t?.type === "TSTypeReference" && t.typeName?.type === "Identifier" && ["File", "Blob"].includes(t.typeName.name);
135
+ function referencesTo(root, name) {
136
+ const out = [];
137
+ walk(root, (n, parents) => {
138
+ if (n.type !== "Identifier" || n.name !== name) return;
139
+ const p = parents[parents.length - 1];
140
+ if ((p?.type === "MemberExpression" || p?.type === "OptionalMemberExpression") && p.property === n && !p.computed) return;
141
+ if (p?.type === "ObjectProperty" && p.key === n && !p.computed) return;
142
+ if (p?.type === "VariableDeclarator" && p.id === n) return;
143
+ if (p && FUNCTION_TYPES.has(p.type) && p.params.includes(n)) return;
144
+ out.push({ node: n, parents: [...parents] });
145
+ });
146
+ return out;
147
+ }
148
+ function classify(node, parents, st, fns, depth) {
149
+ let cur = node;
150
+ for (let i = parents.length - 1; i >= 0; i--) {
151
+ const p = parents[i];
152
+ if (FUNCTION_TYPES.has(p.type)) break;
153
+ if (p.type === "TSAsExpression" || p.type === "TSTypeAssertion") {
154
+ if (isFileType(p.typeAnnotation)) return { type: "file" };
155
+ cur = p;
156
+ continue;
157
+ }
158
+ if (p.type === "TSSatisfiesExpression" || p.type === "TSNonNullExpression" || p.type === "ParenthesizedExpression") {
159
+ cur = p;
160
+ continue;
161
+ }
162
+ if (p.type === "LogicalExpression" && p.left === cur && (p.operator === "??" || p.operator === "||")) {
163
+ cur = p;
164
+ continue;
165
+ }
166
+ if (p.type === "UnaryExpression" && p.operator === "+") return { type: "number" };
167
+ if (p.type === "BinaryExpression") {
168
+ const other = unwrapTs(p.left === cur ? p.right : p.left);
169
+ if (p.operator === "instanceof" && other?.type === "Identifier" && ["File", "Blob"].includes(other.name)) return { type: "file" };
170
+ if (["===", "==", "!==", "!="].includes(p.operator) && stringValue(other) === "on") return { type: "boolean" };
171
+ break;
172
+ }
173
+ const call = parents[i - 1];
174
+ if (p.type === "MemberExpression" && p.object === cur && !p.computed && STRING_METHODS.has(keyName(p.property) ?? "") && call?.type === "CallExpression" && call.callee === p) {
175
+ cur = call;
176
+ i--;
177
+ continue;
178
+ }
179
+ if (p.type === "CallExpression" && p.arguments.includes(cur)) {
180
+ const callee = unwrapTs(p.callee);
181
+ const index = p.arguments.indexOf(cur);
182
+ if (callee.type === "Identifier") {
183
+ if (callee.name === "String") {
184
+ cur = p;
185
+ continue;
186
+ }
187
+ if (NUMBER_FNS.has(callee.name)) return { type: "number" };
188
+ const local = fns.get(callee.name);
189
+ if (local && depth < SEARCH_DEPTH) return followParam(local, index, st, fns, depth + 1);
190
+ break;
191
+ }
192
+ if (callee.type === "MemberExpression") {
193
+ const object = unwrapTs(callee.object);
194
+ const method = keyName(callee.property);
195
+ if (object?.type === "Identifier" && object.name === "JSON" && method === "parse") return { type: "json" };
196
+ if (object?.type === "Identifier" && object.name === "Number" && (method === "parseInt" || method === "parseFloat")) return { type: "number" };
197
+ if (method === "includes" && index === 0) {
198
+ const values = listOf(object, st);
199
+ if (values) return { enum: values };
200
+ }
201
+ }
202
+ break;
203
+ }
204
+ if (p.type === "VariableDeclarator" && p.init === cur && p.id.type === "Identifier") {
205
+ if (depth >= SEARCH_DEPTH) break;
206
+ let scope = parents[0] ?? st.program;
207
+ for (let j = i - 1; j >= 0; j--) {
208
+ if (FUNCTION_TYPES.has(parents[j].type)) {
209
+ scope = parents[j].body;
210
+ break;
211
+ }
212
+ }
213
+ let hint = {};
214
+ for (const ref of referencesTo(scope, p.id.name)) hint = merge(hint, classify(ref.node, ref.parents, st, fns, depth + 1));
215
+ return hint;
216
+ }
217
+ break;
218
+ }
219
+ return {};
220
+ }
221
+ function followParam(fn, index, st, fns, depth) {
222
+ const param = fn.params[index];
223
+ const name = param && paramName(param);
224
+ if (!name) return {};
225
+ let hint = {};
226
+ for (const ref of referencesTo(fn.body, name)) hint = merge(hint, classify(ref.node, ref.parents, st, fns, depth));
227
+ return hint;
228
+ }
229
+ function loopValues(name, parents, st) {
230
+ for (let i = parents.length - 1; i >= 0; i--) {
231
+ const p = parents[i];
232
+ if (p.type === "ForOfStatement") {
233
+ const id = p.left.type === "VariableDeclaration" ? p.left.declarations[0]?.id : p.left;
234
+ if (id?.type === "Identifier" && id.name === name) return listOf(p.right, st);
235
+ }
236
+ if (FUNCTION_TYPES.has(p.type) && p.params[0] && paramName(p.params[0]) === name) {
237
+ const call = parents[i - 1];
238
+ const callee = call?.type === "CallExpression" && call.arguments[0] === p ? unwrapTs(call.callee) : void 0;
239
+ if (callee?.type === "MemberExpression" && LOOP_METHODS.has(keyName(callee.property) ?? "")) return listOf(callee.object, st);
240
+ return void 0;
241
+ }
242
+ }
243
+ return void 0;
244
+ }
245
+ function namesOf(arg, parents, st) {
246
+ arg = unwrapTs(arg);
247
+ const literal = stringValue(arg);
248
+ if (literal !== void 0) return [literal];
249
+ if (arg?.type !== "TemplateLiteral") return void 0;
250
+ let names = [arg.quasis[0].value.cooked];
251
+ for (let k = 0; k < arg.expressions.length; k++) {
252
+ const e = unwrapTs(arg.expressions[k]);
253
+ const values = e?.type === "Identifier" ? loopValues(e.name, parents, st) : void 0;
254
+ if (!values || names.length * values.length > MAX_EXPANDED) return void 0;
255
+ const after = arg.quasis[k + 1].value.cooked;
256
+ names = names.flatMap((prefix) => values.map((v) => `${prefix}${v}${after}`));
257
+ }
258
+ return names;
259
+ }
260
+ function addField(st, name, hint, array) {
261
+ const existing = st.fields.get(name);
262
+ const merged = merge(existing ?? {}, hint);
263
+ st.fields.set(name, {
264
+ name,
265
+ type: merged.type ?? "string",
266
+ ...array || existing?.array ? { array: true } : {},
267
+ ...merged.enum ? { enum: merged.enum } : {},
268
+ ...existing?.required ? { required: true } : {}
269
+ });
270
+ }
271
+ function nestedFunctions(fn) {
272
+ const out = /* @__PURE__ */ new Map();
273
+ walk(fn.body, (n) => {
274
+ if (n.type === "FunctionDeclaration" && n.id) out.set(n.id.name, n);
275
+ if (n.type === "VariableDeclarator" && n.id.type === "Identifier") {
276
+ const init = unwrapTs(n.init);
277
+ if (init && FUNCTION_TYPES.has(init.type)) out.set(n.id.name, init);
278
+ }
279
+ });
280
+ return out;
281
+ }
282
+ var formRead = (n, formNames) => {
283
+ if (n.type !== "CallExpression") return void 0;
284
+ const callee = n.callee;
285
+ if (callee.type !== "MemberExpression" && callee.type !== "OptionalMemberExpression") return void 0;
286
+ const object = unwrapTs(callee.object);
287
+ const method = keyName(callee.property);
288
+ return object?.type === "Identifier" && formNames.has(object.name) && method && FORM_READS.has(method) ? method : void 0;
289
+ };
290
+ function asHelper(h, closure, st, fns, depth) {
291
+ const params = h.params.map(paramName);
292
+ let found;
293
+ walk(h.body, (n, parents) => {
294
+ if (found || n.type !== "CallExpression") return;
295
+ const callee = n.callee;
296
+ if (callee.type !== "MemberExpression" && callee.type !== "OptionalMemberExpression") return;
297
+ const method = keyName(callee.property);
298
+ const object = unwrapTs(callee.object);
299
+ const arg = unwrapTs(n.arguments[0]);
300
+ if (!method || !FORM_READS.has(method) || object?.type !== "Identifier" || arg?.type !== "Identifier") return;
301
+ const nameIndex = params.indexOf(arg.name);
302
+ if (nameIndex < 0) return;
303
+ const formIndex = params.indexOf(object.name);
304
+ if (formIndex < 0 && !closure.has(object.name)) return;
305
+ found = { nameIndex, ...formIndex >= 0 ? { formIndex } : {}, method, hint: method === "get" ? classify(n, parents, st, fns, depth) : {} };
306
+ });
307
+ return found;
308
+ }
309
+ function readsOf(fn, formNames, st, depth) {
310
+ if (st.visited.has(fn)) return;
311
+ st.visited.add(fn);
312
+ const names = new Set(formNames);
313
+ walk(fn.body, (n) => {
314
+ const init = unwrapTs(n.init);
315
+ if (n.type === "VariableDeclarator" && n.id.type === "Identifier" && init?.type === "Identifier" && names.has(init.name)) names.add(n.id.name);
316
+ });
317
+ const nested = nestedFunctions(fn);
318
+ const fns = new Map([...st.topLevel, ...nested]);
319
+ const helpers = /* @__PURE__ */ new Map();
320
+ for (const [name, h] of fns) {
321
+ if (h === fn) continue;
322
+ const helper = asHelper(h, nested.has(name) ? names : /* @__PURE__ */ new Set(), st, fns, depth);
323
+ if (helper) helpers.set(name, helper);
324
+ }
325
+ const unresolved = (n, what) => st.unresolved.push(`line ${lineOf(n)}: ${what}`);
326
+ walk(fn.body, (n, parents) => {
327
+ if (n.type === "ForOfStatement" && unwrapTs(n.right)?.type === "Identifier" && names.has(unwrapTs(n.right).name)) st.readsAll = true;
328
+ if (n.type === "SpreadElement" && unwrapTs(n.argument)?.type === "Identifier" && names.has(unwrapTs(n.argument).name)) st.readsAll = true;
329
+ if (n.type !== "CallExpression") return;
330
+ const callee = unwrapTs(n.callee);
331
+ const args = n.arguments;
332
+ const method = formRead(n, names);
333
+ if (method) {
334
+ const arg = unwrapTs(args[0]);
335
+ const enclosing = [...parents].reverse().find((p) => FUNCTION_TYPES.has(p.type));
336
+ if (arg?.type === "Identifier" && enclosing && enclosing.params.some((p) => paramName(p) === arg.name) && [...helpers.keys()].some((h) => fns.get(h) === enclosing)) return;
337
+ const fieldNames = namesOf(arg, parents, st);
338
+ if (!fieldNames) return unresolved(n, `a field name worked out at runtime: ${text(st.source, n)}`);
339
+ const hint = method === "has" ? { type: "boolean" } : method === "getAll" ? {} : classify(n, parents, st, fns, depth);
340
+ for (const name of fieldNames) addField(st, name, hint, method === "getAll");
341
+ return;
342
+ }
343
+ if (callee.type === "MemberExpression" && unwrapTs(callee.object)?.type === "Identifier" && names.has(unwrapTs(callee.object).name) && FORM_ITERATION.has(keyName(callee.property) ?? "")) {
344
+ st.readsAll = true;
345
+ return;
346
+ }
347
+ const helper = callee.type === "Identifier" ? helpers.get(callee.name) : void 0;
348
+ if (helper) {
349
+ const form = helper.formIndex === void 0 ? void 0 : unwrapTs(args[helper.formIndex]);
350
+ if (helper.formIndex !== void 0 && !(form?.type === "Identifier" && names.has(form.name))) return;
351
+ const fieldNames = namesOf(args[helper.nameIndex], parents, st);
352
+ if (!fieldNames) return unresolved(n, `a field name worked out at runtime: ${text(st.source, n)}`);
353
+ const hint = helper.method === "has" ? { type: "boolean" } : merge(helper.hint, classify(n, parents, st, fns, depth));
354
+ for (const name of fieldNames) addField(st, name, hint, helper.method === "getAll");
355
+ return;
356
+ }
357
+ const index = args.findIndex((a) => unwrapTs(a)?.type === "Identifier" && names.has(unwrapTs(a).name));
358
+ if (index < 0) return;
359
+ if (callee.type === "MemberExpression" && keyName(callee.object) === "Object" && keyName(callee.property) === "fromEntries") {
360
+ st.readsAll = true;
361
+ const decl = [...parents].reverse().find((p) => !["TSAsExpression", "TSSatisfiesExpression", "ParenthesizedExpression"].includes(p.type));
362
+ if (decl?.type === "VariableDeclarator" && decl.id.type === "ObjectPattern") {
363
+ for (const prop of decl.id.properties) {
364
+ const key = prop.type === "ObjectProperty" ? keyName(prop.key) : void 0;
365
+ if (key) addField(st, key, {}, false);
366
+ }
367
+ }
368
+ return;
369
+ }
370
+ const local = callee.type === "Identifier" ? fns.get(callee.name) : void 0;
371
+ const param = local && local.params[index];
372
+ const paramAs = param && paramName(param);
373
+ if (local && paramAs && depth < SEARCH_DEPTH) return readsOf(local, /* @__PURE__ */ new Set([paramAs]), st, depth + 1);
374
+ unresolved(n, `the form goes to ${text(st.source, callee) ?? "a function"}(), which isn't a function in this file`);
375
+ });
376
+ }
377
+ function annotated(t) {
378
+ if (t?.type === "TSStringKeyword") return { type: "string" };
379
+ if (t?.type === "TSNumberKeyword") return { type: "number" };
380
+ if (t?.type === "TSBooleanKeyword") return { type: "boolean" };
381
+ if (t?.type === "TSUnionType") {
382
+ const values = t.types.map((m) => m.type === "TSLiteralType" ? stringValue(m.literal) : void 0);
383
+ if (values.every((v) => v !== void 0)) return { type: "string", enum: values };
384
+ }
385
+ return void 0;
386
+ }
387
+ function analyzeFields(fn, roles, ctx) {
388
+ const st = { ...ctx, lists: /* @__PURE__ */ new Map(), fields: /* @__PURE__ */ new Map(), readsAll: false, unresolved: [], visited: /* @__PURE__ */ new Set() };
389
+ const params = fn.params.filter((p) => p.type !== "RestElement");
390
+ params.forEach((p, i) => {
391
+ const role = roles[i];
392
+ if (!role || role.role === "prev") return;
393
+ const id = p.type === "AssignmentPattern" ? p.left : p;
394
+ if (role.role === "formData") return readsOf(fn, /* @__PURE__ */ new Set([role.name]), st, 0);
395
+ if (role.role === "input") {
396
+ const hint = annotated(id.typeAnnotation?.typeAnnotation);
397
+ if (!hint) return st.unresolved.push(`line ${lineOf(p)}: parameter ${role.name} has no type it can read (string, number, boolean or a union of strings)`);
398
+ st.fields.set(role.name, { name: role.name, type: hint.type, ...hint.enum ? { enum: hint.enum } : {}, required: true });
399
+ return;
400
+ }
401
+ const literal = id.typeAnnotation?.typeAnnotation;
402
+ const members = /* @__PURE__ */ new Map();
403
+ if (literal?.type === "TSTypeLiteral") {
404
+ for (const m of literal.members) if (m.type === "TSPropertySignature" && keyName(m.key)) members.set(keyName(m.key), m);
405
+ }
406
+ for (const prop of id.properties) {
407
+ const key = prop.type === "ObjectProperty" ? keyName(prop.key) : void 0;
408
+ if (!key) {
409
+ st.unresolved.push(`line ${lineOf(prop)}: a rest element in parameter ${role.name}`);
410
+ continue;
411
+ }
412
+ const member = members.get(key);
413
+ const hint = annotated(member?.typeAnnotation?.typeAnnotation);
414
+ if (!hint) {
415
+ st.unresolved.push(`line ${lineOf(prop)}: ${key} has no type it can read (string, number, boolean or a union of strings)`);
416
+ continue;
417
+ }
418
+ st.fields.set(key, { name: key, type: hint.type, ...hint.enum ? { enum: hint.enum } : {}, ...member?.optional || prop.value.type === "AssignmentPattern" ? {} : { required: true } });
419
+ }
420
+ });
421
+ return { fields: [...st.fields.values()], readsAll: st.readsAll, unresolved: st.unresolved };
422
+ }
423
+
424
+ // src/forms.ts
425
+ var STATE_HOOKS = /* @__PURE__ */ new Set(["useActionState", "useFormState"]);
426
+ var FIELD_TAGS = /* @__PURE__ */ new Set(["input", "select", "textarea"]);
427
+ var COMPONENT_DEPTH = 2;
428
+ var tagName = (el) => el.openingElement?.name?.type === "JSXIdentifier" ? el.openingElement.name.name : void 0;
429
+ var attrName = (a) => a.name?.type === "JSXIdentifier" ? a.name.name : a.name?.type === "JSXNamespacedName" ? `${a.name.namespace.name}:${a.name.name.name}` : void 0;
430
+ var isComponent = (tag) => tag !== void 0 && /^[A-Z]/.test(tag);
431
+ function attrValue(attr, parents, scope) {
432
+ const v = attr.value;
433
+ if (v === null) return { kind: "bool", value: true };
434
+ if (v.type === "StringLiteral") return { kind: "string", value: v.value };
435
+ if (v.type !== "JSXExpressionContainer") return void 0;
436
+ const e = unwrapTs(v.expression);
437
+ if (!e || e.type === "JSXEmptyExpression") return void 0;
438
+ if (e.type === "BooleanLiteral") return { kind: "bool", value: e.value };
439
+ if (e.type === "NumericLiteral") return { kind: "number", value: e.value };
440
+ const s = stringValue(e);
441
+ if (s !== void 0) return { kind: "string", value: s };
442
+ return { kind: "expr", node: e, parents, scope };
443
+ }
444
+ function attributes(el, parents, scope) {
445
+ return el.openingElement.attributes.map((a) => a.type === "JSXSpreadAttribute" ? null : [attrName(a) ?? "", attrValue(a, parents, scope)]);
446
+ }
447
+ function moduleFor(file, source, ctx) {
448
+ if (ctx.modules.has(file)) return ctx.modules.get(file);
449
+ const program = source === void 0 ? null : parseModule(source, file);
450
+ const out = program ? { program, scope: listScope(program, file, ctx.load) } : null;
451
+ ctx.modules.set(file, out);
452
+ return out;
453
+ }
454
+ function componentIn(program, name) {
455
+ for (const stmt of program.body) {
456
+ const decl = stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportDefaultDeclaration" ? stmt.declaration : stmt;
457
+ if (name === "default" && stmt.type === "ExportDefaultDeclaration" && decl && FUNCTION_TYPES.has(decl.type)) return decl;
458
+ if (decl?.type === "FunctionDeclaration" && decl.id?.name === name) return decl;
459
+ if (decl?.type === "VariableDeclaration") {
460
+ for (const d of decl.declarations) {
461
+ if (d.id.type !== "Identifier" || d.id.name !== name) continue;
462
+ let init = unwrapTs(d.init);
463
+ while (init?.type === "CallExpression" && init.arguments.length > 0) init = unwrapTs(init.arguments[0]);
464
+ if (init && FUNCTION_TYPES.has(init.type)) return init;
465
+ }
466
+ }
467
+ }
468
+ return void 0;
469
+ }
470
+ function findComponent(tag, mod, ctx) {
471
+ const local = componentIn(mod.program, tag);
472
+ if (local) return { fn: local, mod };
473
+ if (!ctx.load) return void 0;
474
+ for (const stmt of mod.program.body) {
475
+ if (stmt.type !== "ImportDeclaration") continue;
476
+ const spec = stmt.specifiers.find((s) => s.local.name === tag);
477
+ if (!spec) continue;
478
+ const loaded = ctx.load(stmt.source.value, mod.scope.file);
479
+ if (!loaded) return void 0;
480
+ const other = moduleFor(loaded.file, loaded.source, ctx);
481
+ const fn = other && componentIn(other.program, spec.type === "ImportDefaultSpecifier" ? "default" : keyName(spec.imported) ?? tag);
482
+ return fn && other ? { fn, mod: other } : void 0;
483
+ }
484
+ return void 0;
485
+ }
486
+ function renderedAttributes(el, fn, use, mod) {
487
+ const param = fn.params[0];
488
+ const pattern = param?.type === "AssignmentPattern" ? param.left : param;
489
+ const propsName = pattern?.type === "Identifier" ? pattern.name : void 0;
490
+ let restName;
491
+ const locals = /* @__PURE__ */ new Map();
492
+ if (pattern?.type === "ObjectPattern") {
493
+ for (const p of pattern.properties) {
494
+ if (p.type === "RestElement" && p.argument.type === "Identifier") restName = p.argument.name;
495
+ if (p.type !== "ObjectProperty") continue;
496
+ const prop = keyName(p.key);
497
+ const value = p.value.type === "AssignmentPattern" ? p.value.left : p.value;
498
+ if (!prop || value.type !== "Identifier") continue;
499
+ const fallback = p.value.type === "AssignmentPattern" ? attrValue({ type: "JSXAttribute", value: { type: "JSXExpressionContainer", expression: p.value.right } }, [], mod.scope) : void 0;
500
+ locals.set(value.name, { prop, ...fallback ? { fallback } : {} });
501
+ }
502
+ }
503
+ const named2 = new Set([...locals.values()].map((l) => l.prop));
504
+ const out = /* @__PURE__ */ new Map();
505
+ for (const a of el.openingElement.attributes) {
506
+ if (a.type === "JSXSpreadAttribute") {
507
+ const arg = unwrapTs(a.argument);
508
+ if (arg?.type === "Identifier" && (arg.name === restName || arg.name === propsName)) {
509
+ for (const [k, v] of use) if (arg.name === propsName || !named2.has(k)) out.set(k, v);
510
+ }
511
+ continue;
512
+ }
513
+ const key = attrName(a);
514
+ if (!key) continue;
515
+ const expr = a.value?.type === "JSXExpressionContainer" ? unwrapTs(a.value.expression) : void 0;
516
+ if (expr?.type === "Identifier" && locals.has(expr.name)) {
517
+ const { prop, fallback } = locals.get(expr.name);
518
+ const v = use.has(prop) ? use.get(prop) : fallback;
519
+ if (v !== void 0) out.set(key, v);
520
+ continue;
521
+ }
522
+ if (expr?.type === "MemberExpression" && unwrapTs(expr.object)?.type === "Identifier" && unwrapTs(expr.object).name === propsName) {
523
+ const prop = keyName(expr.property);
524
+ if (prop && use.has(prop)) out.set(key, use.get(prop));
525
+ continue;
526
+ }
527
+ out.set(key, attrValue(a, [], mod.scope));
528
+ }
529
+ return out;
530
+ }
531
+ function receivesProps(el, fn) {
532
+ const param = fn.params[0];
533
+ const pattern = param?.type === "AssignmentPattern" ? param.left : param;
534
+ const names = /* @__PURE__ */ new Set();
535
+ if (pattern?.type === "Identifier") names.add(pattern.name);
536
+ if (pattern?.type === "ObjectPattern") {
537
+ for (const p of pattern.properties) {
538
+ if (p.type === "RestElement" && p.argument.type === "Identifier") names.add(p.argument.name);
539
+ const value = p.type === "ObjectProperty" ? p.value.type === "AssignmentPattern" ? p.value.left : p.value : void 0;
540
+ if (value?.type === "Identifier" && keyName(p.key) === "name") names.add(value.name);
541
+ }
542
+ }
543
+ return el.openingElement.attributes.some((a) => {
544
+ if (a.type === "JSXSpreadAttribute") return unwrapTs(a.argument)?.type === "Identifier" && names.has(unwrapTs(a.argument).name);
545
+ if (attrName(a) !== "name") return false;
546
+ const e = a.value?.type === "JSXExpressionContainer" ? unwrapTs(a.value.expression) : void 0;
547
+ return e?.type === "Identifier" && names.has(e.name) || e?.type === "MemberExpression" && unwrapTs(e.object)?.type === "Identifier" && names.has(unwrapTs(e.object).name);
548
+ });
549
+ }
550
+ function throughComponent(tag, use, mod, ctx, depth) {
551
+ if (depth > COMPONENT_DEPTH) return void 0;
552
+ const found = findComponent(tag, mod, ctx);
553
+ if (!found) return void 0;
554
+ let result;
555
+ walk(found.fn.body, (n) => {
556
+ if (result || n.type !== "JSXElement") return;
557
+ const inner = tagName(n);
558
+ if (!inner || !(FIELD_TAGS.has(inner) || isComponent(inner)) || !receivesProps(n, found.fn)) return;
559
+ const attrs = renderedAttributes(n, found.fn, use, found.mod);
560
+ result = FIELD_TAGS.has(inner) ? { tag: inner, attrs } : throughComponent(inner, attrs, found.mod, ctx, depth + 1);
561
+ });
562
+ return result;
563
+ }
564
+ var num = (v) => v?.kind === "number" ? v.value : v?.kind === "string" && v.value.trim() !== "" && !Number.isNaN(Number(v.value)) ? Number(v.value) : void 0;
565
+ var str = (v) => v?.kind === "string" ? v.value : void 0;
566
+ var truthy = (v) => v?.kind === "bool" ? v.value : v?.kind === "string" ? v.value !== "false" : false;
567
+ function selectOptions(el, scope) {
568
+ const values = [];
569
+ let open = false;
570
+ walk(el.children, (n, parents) => {
571
+ if (n.type !== "JSXElement" || tagName(n) !== "option") return;
572
+ const attr = n.openingElement.attributes.find((a) => a.type === "JSXAttribute" && attrName(a) === "value");
573
+ const v = attr && attrValue(attr, [...parents], scope);
574
+ if (v?.kind === "string") return void values.push(v.value);
575
+ const names = v?.kind !== "expr" ? void 0 : v.node.type === "Identifier" ? loopValues(v.node.name, [...parents], scope) : namesOf(v.node, [...parents], scope);
576
+ if (names) values.push(...names);
577
+ else open = true;
578
+ });
579
+ return open || values.length === 0 ? void 0 : [...new Set(values)];
580
+ }
581
+ function formFields(form, mod, ctx, unresolved) {
582
+ const fields = /* @__PURE__ */ new Map();
583
+ const radios = /* @__PURE__ */ new Map();
584
+ walk(form.children, (n, parents) => {
585
+ if (n.type !== "JSXElement") return;
586
+ const tag = tagName(n);
587
+ if (!tag || !(FIELD_TAGS.has(tag) || isComponent(tag))) return;
588
+ const own = attributes(n, [...parents], mod.scope);
589
+ const use = new Map(own.filter((a) => a !== null));
590
+ if (!use.has("name")) return;
591
+ let el = { tag, attrs: use };
592
+ if (isComponent(tag)) el = throughComponent(tag, use, mod, ctx, 1) ?? { tag: "input", attrs: use };
593
+ const nameVal = el.attrs.get("name");
594
+ const names = nameVal?.kind === "string" ? [nameVal.value] : nameVal?.kind === "expr" ? namesOf(nameVal.node, nameVal.parents, nameVal.scope) : void 0;
595
+ if (!names) return void unresolved.push(`${mod.scope.file}:${lineOf(n)}: a field name from runtime data`);
596
+ const conditional = parents.some((p) => p.type === "LogicalExpression" || p.type === "ConditionalExpression");
597
+ const type = str(el.attrs.get("type"))?.toLowerCase();
598
+ const value = el.attrs.get("value");
599
+ const field = {};
600
+ if (el.tag === "input" && (type === "number" || type === "range")) field.type = "number";
601
+ if (el.tag === "input" && type === "checkbox") field.type = "boolean";
602
+ if (el.tag === "input" && type === "file") field.type = "file";
603
+ const stringified = value?.kind === "expr" && value.node.type === "CallExpression" && keyName(value.node.callee?.object) === "JSON" && keyName(value.node.callee?.property) === "stringify";
604
+ if (stringified) field.type = "json";
605
+ if (el.tag === "select" && !isComponent(tag)) {
606
+ const options = selectOptions(n, mod.scope);
607
+ if (options) field.enum = options;
608
+ }
609
+ if (el.tag === "input" && type === "radio" && str(value) !== void 0) {
610
+ for (const name of names) radios.set(name, [...radios.get(name) ?? [], str(value)]);
611
+ }
612
+ const required = truthy(el.attrs.get("required")) || el.tag === "input" && type === "hidden";
613
+ if (required && !conditional && type !== "checkbox") field.required = true;
614
+ for (const k of ["min", "max", "minLength", "maxLength"]) {
615
+ const v = num(el.attrs.get(k) ?? el.attrs.get(k.toLowerCase()));
616
+ if (v !== void 0) field[k] = v;
617
+ }
618
+ const pattern = str(el.attrs.get("pattern"));
619
+ if (pattern) field.pattern = pattern;
620
+ for (const name of names) fields.set(name, { name, ...fields.get(name), ...field });
621
+ });
622
+ for (const [name, values] of radios) {
623
+ const f = fields.get(name);
624
+ if (f) f.enum = [...new Set(values)];
625
+ }
626
+ return [...fields.values()];
627
+ }
628
+ function findFormUses(source, file, actions, load, ctx = { ...load ? { load } : {}, modules: /* @__PURE__ */ new Map() }) {
629
+ const mod = moduleFor(file, source, ctx);
630
+ if (!mod) return [];
631
+ const { program } = mod;
632
+ const imported = /* @__PURE__ */ new Map();
633
+ const namespaces = /* @__PURE__ */ new Map();
634
+ for (const stmt of program.body) {
635
+ if (stmt.type !== "ImportDeclaration" || stmt.importKind === "type" || !load) continue;
636
+ const target = load(stmt.source.value, file);
637
+ const exports2 = target && actions.get(target.file);
638
+ if (!target || !exports2) continue;
639
+ for (const spec of stmt.specifiers) {
640
+ if (spec.type === "ImportNamespaceSpecifier") namespaces.set(spec.local.name, target.file);
641
+ const name = spec.type === "ImportDefaultSpecifier" ? "default" : spec.type === "ImportSpecifier" ? keyName(spec.imported) : void 0;
642
+ if (name && exports2.has(name)) imported.set(spec.local.name, { file: target.file, export: name });
643
+ }
644
+ }
645
+ if (imported.size === 0 && namespaces.size === 0) return [];
646
+ const aliases = /* @__PURE__ */ new Map();
647
+ const refOf = (e) => {
648
+ e = unwrapTs(e);
649
+ if (!e) return void 0;
650
+ if (e.type === "Identifier") return aliases.get(e.name) ?? (imported.has(e.name) ? { ...imported.get(e.name), bound: 0, prev: false } : void 0);
651
+ if (e.type === "MemberExpression" && unwrapTs(e.object)?.type === "Identifier" && namespaces.has(unwrapTs(e.object).name)) {
652
+ const name = keyName(e.property);
653
+ const target = namespaces.get(unwrapTs(e.object).name);
654
+ return name && actions.get(target)?.has(name) ? { file: target, export: name, bound: 0, prev: false } : void 0;
655
+ }
656
+ if (e.type === "CallExpression" && e.callee.type === "MemberExpression" && keyName(e.callee.property) === "bind") {
657
+ const inner = refOf(e.callee.object);
658
+ return inner && { ...inner, bound: inner.bound + Math.max(0, e.arguments.length - 1) };
659
+ }
660
+ return void 0;
661
+ };
662
+ const refIn = (e) => {
663
+ let found;
664
+ walk(e, (n, parents) => {
665
+ if (found || !(n.type === "Identifier" || n.type === "MemberExpression" || n.type === "CallExpression")) return;
666
+ const ref = refOf(n);
667
+ if (!ref) return;
668
+ const call = parents[parents.length - 1];
669
+ const called = call?.type === "CallExpression" && call.callee === n && !ref.prev;
670
+ found = called ? { ...ref, bound: ref.bound + Math.max(0, call.arguments.length - 1) } : ref;
671
+ });
672
+ return found;
673
+ };
674
+ walk(program, (n) => {
675
+ if (n.type !== "VariableDeclarator") return;
676
+ const init = unwrapTs(n.init);
677
+ if (n.id.type === "ArrayPattern" && init?.type === "CallExpression") {
678
+ const hook = init.callee.type === "Identifier" ? init.callee.name : keyName(init.callee.property);
679
+ const ref = hook && STATE_HOOKS.has(hook) ? refOf(init.arguments[0]) : void 0;
680
+ const id = n.id.elements[1];
681
+ if (ref && id?.type === "Identifier") aliases.set(id.name, { ...ref, prev: true });
682
+ }
683
+ if (n.id.type === "Identifier" && init) {
684
+ const ref = refOf(init);
685
+ if (ref) aliases.set(n.id.name, ref);
686
+ }
687
+ });
688
+ const out = [];
689
+ const unresolvedFor = /* @__PURE__ */ new Map();
690
+ const fieldsFor = (form) => {
691
+ if (!unresolvedFor.has(form)) unresolvedFor.set(form, []);
692
+ return formFields(form, mod, ctx, unresolvedFor.get(form));
693
+ };
694
+ walk(program, (n, parents) => {
695
+ if (n.type !== "JSXElement") return;
696
+ const tag = tagName(n);
697
+ const attr = (name) => n.openingElement.attributes.find((a) => a.type === "JSXAttribute" && attrName(a) === name);
698
+ const refs = [];
699
+ if (tag === "form") {
700
+ for (const name of ["action", "onSubmit"]) {
701
+ const ref = refIn(attr(name)?.value);
702
+ if (ref) refs.push(ref);
703
+ }
704
+ }
705
+ const formAction = (tag === "button" || tag === "input") && refIn(attr("formAction")?.value);
706
+ const form = formAction ? [...parents].reverse().find((p) => p.type === "JSXElement" && tagName(p) === "form") : n;
707
+ if (formAction) refs.push(formAction);
708
+ if (!form || refs.length === 0) return;
709
+ const fields = fieldsFor(form);
710
+ for (const ref of refs.filter((r, i) => refs.findIndex((o) => o.file === r.file && o.export === r.export) === i)) {
711
+ out.push({ action: { file: ref.file, export: ref.export }, file, line: lineOf(form), bound: ref.bound, prev: ref.prev, fields, unresolved: unresolvedFor.get(form) });
712
+ }
713
+ });
714
+ return out;
715
+ }
716
+ function linkForms(action, uses) {
717
+ if (uses.length === 0) return action;
718
+ const fromForms = /* @__PURE__ */ new Map();
719
+ for (const use of uses) {
720
+ for (const f of use.fields) {
721
+ const seen = fromForms.get(f.name);
722
+ fromForms.set(f.name, { ...seen, ...f, enum: seen?.enum ?? f.enum, requiredIn: (seen?.requiredIn ?? 0) + (f.required ? 1 : 0) });
723
+ }
724
+ }
725
+ const formsWith = (name) => uses.filter((u) => u.fields.some((f) => f.name === name)).length;
726
+ const merged = (base, f) => {
727
+ const type = f.type === "file" ? "file" : base?.type ?? f.type ?? "string";
728
+ const values = base?.enum ?? f.enum;
729
+ const required = base?.required || f.requiredIn === formsWith(f.name);
730
+ return {
731
+ name: f.name,
732
+ type,
733
+ ...base?.array ? { array: true } : {},
734
+ ...values ? { enum: values } : {},
735
+ ...required ? { required: true } : {},
736
+ ...type === "number" && f.min !== void 0 ? { min: f.min } : {},
737
+ ...type === "number" && f.max !== void 0 ? { max: f.max } : {},
738
+ ...type === "string" && f.minLength !== void 0 ? { minLength: f.minLength } : {},
739
+ ...type === "string" && f.maxLength !== void 0 ? { maxLength: f.maxLength } : {},
740
+ ...type === "string" && f.pattern !== void 0 ? { pattern: f.pattern } : {},
741
+ origin: base ? "both" : "form"
742
+ };
743
+ };
744
+ const fields = action.fields.map((a) => {
745
+ const f = fromForms.get(a.name);
746
+ return f ? merged(a, f) : { ...a, origin: "action" };
747
+ });
748
+ if (action.readsAll) {
749
+ for (const f of fromForms.values()) if (!action.fields.some((a) => a.name === f.name)) fields.push(merged(void 0, f));
750
+ }
751
+ return {
752
+ ...action,
753
+ fields,
754
+ // A form's unreadable names matter only when the action reads whatever the form sends.
755
+ unresolved: [...action.unresolved, ...action.readsAll ? uses.flatMap((u) => u.unresolved) : []],
756
+ forms: uses.map((u) => ({ file: u.file, line: u.line }))
757
+ };
758
+ }
759
+
760
+ // src/hosts.ts
761
+ var import_node_fs = require("fs");
762
+ var import_node_path = require("path");
763
+
764
+ // src/routes.ts
765
+ var HTTP_METHODS = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
766
+ var DEFAULT_PAGE_EXTENSIONS = ["tsx", "ts", "jsx", "js"];
767
+ function appRoute(dirSegments) {
768
+ const parts = [];
769
+ const params = [];
770
+ for (const seg of dirSegments) {
771
+ if (seg.startsWith("_") || seg.startsWith("@") || seg.startsWith("(.")) return null;
772
+ if (seg.startsWith("(") && seg.endsWith(")")) continue;
773
+ const part = dynamicSegment(seg, params);
774
+ if (part === null) return null;
775
+ parts.push(part);
776
+ }
777
+ return { path: `/${parts.join("/")}`, params };
778
+ }
779
+ function pagesApiRoute(segments) {
780
+ if (segments[0] !== "api") return null;
781
+ const segs = segments.at(-1) === "index" ? segments.slice(0, -1) : segments;
782
+ const parts = [];
783
+ const params = [];
784
+ for (const seg of segs) {
785
+ if (seg.startsWith("_")) return null;
786
+ const part = dynamicSegment(seg, params);
787
+ if (part === null) return null;
788
+ parts.push(part);
789
+ }
790
+ return { path: `/${parts.join("/")}`, params };
791
+ }
792
+ function dynamicSegment(seg, params) {
793
+ const optionalCatchAll = /^\[\[\.\.\.([A-Za-z0-9_]+)\]\]$/.exec(seg);
794
+ if (optionalCatchAll) {
795
+ params.push(optionalCatchAll[1]);
796
+ return `:${optionalCatchAll[1]}*?`;
797
+ }
798
+ const catchAll = /^\[\.\.\.([A-Za-z0-9_]+)\]$/.exec(seg);
799
+ if (catchAll) {
800
+ params.push(catchAll[1]);
801
+ return `:${catchAll[1]}*`;
802
+ }
803
+ const dynamic = /^\[([A-Za-z0-9_]+)\]$/.exec(seg);
804
+ if (dynamic) {
805
+ params.push(dynamic[1]);
806
+ return `:${dynamic[1]}`;
807
+ }
808
+ return seg.includes("[") ? null : seg;
809
+ }
810
+
811
+ // src/hosts.ts
812
+ function edgesOf(source, file, load) {
813
+ const program = parseModule(source, file);
814
+ if (!program) return [];
815
+ const edges = [];
816
+ const add = (specifier2, names) => {
817
+ if (typeof specifier2 !== "string") return;
818
+ const target = load(specifier2, file);
819
+ if (target) edges.push({ file: target.file, names });
820
+ };
821
+ for (const stmt of program.body) {
822
+ if (stmt.type === "ImportDeclaration" && stmt.importKind !== "type") {
823
+ const specs = stmt.specifiers;
824
+ if (specs.some((s) => s.type === "ImportNamespaceSpecifier")) add(stmt.source.value, "*");
825
+ else add(stmt.source.value, new Set(specs.filter((s) => s.importKind !== "type").map((s) => s.type === "ImportDefaultSpecifier" ? "default" : keyName(s.imported) ?? "").filter(Boolean)));
826
+ } else if (stmt.type === "ExportNamedDeclaration" && stmt.source && stmt.exportKind !== "type") {
827
+ add(stmt.source.value, new Set(stmt.specifiers.map((s) => s.type === "ExportNamespaceSpecifier" ? "*" : keyName(s.local) ?? "").filter(Boolean)));
828
+ } else if (stmt.type === "ExportAllDeclaration" && stmt.exportKind !== "type") {
829
+ add(stmt.source.value, "*");
830
+ }
831
+ }
832
+ walk(program, (n) => {
833
+ if (n.type === "CallExpression" && n.callee?.type === "Import") add(n.arguments?.[0]?.value, "*");
834
+ if (n.type === "ImportExpression") add(n.source?.value, "*");
835
+ });
836
+ return edges;
837
+ }
838
+ function moduleGraph(root, load) {
839
+ const cache = /* @__PURE__ */ new Map();
840
+ const edges = (file) => {
841
+ let e = cache.get(file);
842
+ if (!e) {
843
+ let source;
844
+ try {
845
+ source = (0, import_node_fs.readFileSync)((0, import_node_path.join)(root, file), "utf8");
846
+ } catch {
847
+ source = void 0;
848
+ }
849
+ e = source === void 0 ? [] : edgesOf(source, file, load);
850
+ cache.set(file, e);
851
+ }
852
+ return e;
853
+ };
854
+ return {
855
+ edges,
856
+ reachable(roots) {
857
+ const seen = /* @__PURE__ */ new Set();
858
+ const queue = [...roots];
859
+ while (queue.length > 0 && seen.size < 2e3) {
860
+ const file = queue.shift();
861
+ if (seen.has(file)) continue;
862
+ seen.add(file);
863
+ for (const edge of edges(file)) if (!seen.has(edge.file)) queue.push(edge.file);
864
+ }
865
+ return seen;
866
+ }
867
+ };
868
+ }
869
+ function pageEntries(appDir, files, exts) {
870
+ const extPattern = exts.map((e) => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
871
+ const entry = new RegExp(`^(page|layout|template)\\.(${extPattern})$`);
872
+ const base = (f) => f.slice(f.lastIndexOf("/") + 1);
873
+ const inApp = files.filter((f) => f.startsWith(`${appDir}/`) && entry.test(base(f)));
874
+ const layouts = /* @__PURE__ */ new Map();
875
+ for (const f of inApp) {
876
+ if (/^page\./.test(base(f))) continue;
877
+ const dir = f.slice(0, f.lastIndexOf("/"));
878
+ layouts.set(dir, [...layouts.get(dir) ?? [], f]);
879
+ }
880
+ const out = [];
881
+ for (const page of inApp.filter((f) => /^page\./.test(base(f)))) {
882
+ const dir = page.slice(0, page.lastIndexOf("/"));
883
+ const route = appRoute(dir === appDir ? [] : dir.slice(appDir.length + 1).split("/"));
884
+ if (!route) continue;
885
+ const roots = [page];
886
+ for (let d = dir; d.length >= appDir.length; d = d.slice(0, Math.max(d.lastIndexOf("/"), 0))) {
887
+ roots.push(...layouts.get(d) ?? []);
888
+ if (d === appDir) break;
889
+ }
890
+ out.push({ file: page, path: route.path, params: route.params, roots });
891
+ }
892
+ return out;
893
+ }
894
+ function actionHosts(pages, graph, actionExports) {
895
+ const best = /* @__PURE__ */ new Map();
896
+ for (const page of pages) {
897
+ const used = /* @__PURE__ */ new Set();
898
+ for (const file of graph.reachable(page.roots)) {
899
+ for (const edge of graph.edges(file)) {
900
+ const exported = actionExports.get(edge.file);
901
+ if (exported) {
902
+ for (const name of edge.names === "*" ? exported : edge.names) if (exported.has(name)) used.add(`${edge.file}#${name}`);
903
+ }
904
+ }
905
+ }
906
+ for (const key of used) {
907
+ const current = best.get(key);
908
+ const candidate = { path: page.path, params: page.params.length };
909
+ if (!current || candidate.params < current.params || candidate.params === current.params && (candidate.path.length < current.path.length || candidate.path.length === current.path.length && candidate.path < current.path)) {
910
+ best.set(key, candidate);
911
+ }
912
+ }
913
+ }
914
+ return new Map([...best].map(([k, v]) => [k, v.path]));
915
+ }
916
+
917
+ // src/login.ts
918
+ var import_node_fs2 = require("fs");
919
+ var import_node_path2 = require("path");
920
+ var import_core = require("@exposify/core");
921
+ var SESSION_CODE = /cookies\(\)\s*\.set|\.cookies\.set\(|cookieStore\.set|set-cookie|setCookie|\bjwt\.sign\(|new\s+SignJWT|\bsign\(|createSession|session\.(save|create)|\.login\(/i;
922
+ function routeBodyFields(source) {
923
+ const names = /* @__PURE__ */ new Set();
924
+ let body = null;
925
+ for (const m of source.matchAll(/(?:const|let|var)\s+(\w+)\s*=\s*(?:await\s+)?\w+\.formData\(\)/g)) {
926
+ body ??= "form";
927
+ for (const read2 of source.matchAll(new RegExp(`\\b${m[1]}\\.get\\(\\s*["'\`]([A-Za-z0-9_.\\-]+)["'\`]`, "g"))) names.add(read2[1]);
928
+ }
929
+ for (const m of source.matchAll(/(?:const|let|var)\s*\{([^}]*)\}\s*=\s*(?:await\s+)?\w+\.(json|formData)\(\)/g)) {
930
+ body = m[2] === "json" ? "json" : "form";
931
+ for (const part of m[1].split(",")) {
932
+ const name = part.split(/[:=]/)[0].trim().replace(/^\.\.\./, "");
933
+ if (/^[A-Za-z_$][\w$]*$/.test(name)) names.add(name);
934
+ }
935
+ }
936
+ for (const m of source.matchAll(/(?:const|let|var)\s+(\w+)\s*=\s*(?:await\s+)?\w+\.json\(\)/g)) {
937
+ body ??= "json";
938
+ for (const read2 of source.matchAll(new RegExp(`\\b${m[1]}\\.(\\w+)`, "g"))) names.add(read2[1]);
939
+ }
940
+ return names.size && body ? { fields: [...names], body } : null;
941
+ }
942
+ function actionCandidate(a) {
943
+ if (import_core.NOT_A_LOGIN.test(a.key) || import_core.NOT_A_LOGIN.test(a.action.name)) return null;
944
+ if (!a.action.params.some((p) => p.role === "formData" || p.role === "object")) return null;
945
+ const fields = (0, import_core.loginFields)(a.action.fields.map((f) => f.name));
946
+ if (!fields) return null;
947
+ if (!a.action.setsCookies && !import_core.LOGIN_NAME.test(a.action.name)) return null;
948
+ return { kind: "action", key: a.key, export: a.action.name, file: a.file, fields, ...a.host ? { host: a.host } : {} };
949
+ }
950
+ function routeCandidate(root, r) {
951
+ if (r.router !== "app" || !r.module.methods.includes("POST") || r.params.length > 0 || import_core.NOT_A_LOGIN.test(r.path)) return null;
952
+ let source;
953
+ try {
954
+ source = (0, import_node_fs2.readFileSync)((0, import_node_path2.join)(root, r.file), "utf8");
955
+ } catch {
956
+ return null;
957
+ }
958
+ const read2 = routeBodyFields(source);
959
+ const fields = read2 ? (0, import_core.loginFields)(read2.fields) : null;
960
+ if (!read2 || !fields) return null;
961
+ if (!import_core.LOGIN_NAME.test(r.path) && !SESSION_CODE.test(source)) return null;
962
+ return { kind: "route", path: r.path, file: r.file, fields, body: read2.body };
963
+ }
964
+ var named = (l) => import_core.LOGIN_NAME.test(l.kind === "action" ? l.export : l.path);
965
+ function findLogin(d) {
966
+ const candidates = [];
967
+ for (const a of d.actions) {
968
+ const c = actionCandidate(a);
969
+ if (c) candidates.push(c);
970
+ }
971
+ for (const r of d.routes) {
972
+ const c = routeCandidate(d.root, r);
973
+ if (c) candidates.push(c);
974
+ }
975
+ if (candidates.length === 0) return null;
976
+ const score = (l) => (named(l) ? 0 : 4) + (l.kind === "action" && l.host ? 0 : 2) + l.fields.length / 100;
977
+ candidates.sort((a, b) => score(a) - score(b));
978
+ return { login: candidates[0], others: candidates.slice(1) };
979
+ }
980
+
981
+ // src/guards.ts
982
+ var import_node_fs3 = require("fs");
983
+ var import_node_path3 = require("path");
984
+ var STOPS = /* @__PURE__ */ new Set(["notFound", "redirect", "permanentRedirect", "forbidden", "unauthorized"]);
985
+ var MAX_DEPTH = 4;
986
+ function analyse(source, file) {
987
+ const program = parseModule(source, file);
988
+ if (!program) return null;
989
+ const imports = /* @__PURE__ */ new Map();
990
+ const stops = /* @__PURE__ */ new Set();
991
+ const functions = /* @__PURE__ */ new Map();
992
+ const exports2 = /* @__PURE__ */ new Map();
993
+ const fn = (n) => n && FUNCTION_TYPES.has(n.type) ? n : void 0;
994
+ for (const stmt of program.body) {
995
+ if (stmt.type === "ImportDeclaration" && stmt.importKind !== "type") {
996
+ const specifier2 = stmt.source.value;
997
+ for (const s of stmt.specifiers) {
998
+ const local = s.local?.name;
999
+ if (!local) continue;
1000
+ const name = s.type === "ImportDefaultSpecifier" ? "default" : s.type === "ImportNamespaceSpecifier" ? "*" : keyName(s.imported) ?? local;
1001
+ if (specifier2 === "next/navigation" && STOPS.has(name)) stops.add(local);
1002
+ else imports.set(local, { specifier: specifier2, name });
1003
+ }
1004
+ continue;
1005
+ }
1006
+ const decl = stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportDefaultDeclaration" ? stmt.declaration : stmt;
1007
+ if (decl?.type === "FunctionDeclaration" && decl.id) {
1008
+ functions.set(decl.id.name, decl);
1009
+ if (stmt.type === "ExportNamedDeclaration") exports2.set(decl.id.name, decl.id.name);
1010
+ }
1011
+ if (stmt.type === "ExportDefaultDeclaration") {
1012
+ if (decl?.type === "FunctionDeclaration" && decl.id) exports2.set("default", decl.id.name);
1013
+ else if (fn(decl)) {
1014
+ functions.set("default", decl);
1015
+ exports2.set("default", "default");
1016
+ } else if (decl?.type === "Identifier") exports2.set("default", decl.name);
1017
+ }
1018
+ if (decl?.type === "VariableDeclaration") {
1019
+ for (const d of decl.declarations) {
1020
+ const init = fn(d.init) ?? fn(d.init?.arguments?.[0]);
1021
+ if (d.id?.type === "Identifier" && init) {
1022
+ functions.set(d.id.name, init);
1023
+ if (stmt.type === "ExportNamedDeclaration") exports2.set(d.id.name, d.id.name);
1024
+ }
1025
+ }
1026
+ }
1027
+ if (stmt.type === "ExportNamedDeclaration" && !stmt.source) {
1028
+ for (const s of stmt.specifiers ?? []) {
1029
+ const exported = keyName(s.exported);
1030
+ if (exported && s.local?.name) exports2.set(exported, s.local.name);
1031
+ }
1032
+ }
1033
+ }
1034
+ return { program, imports, stops, functions, exports: exports2 };
1035
+ }
1036
+ function guardFinder(root, load) {
1037
+ const parsed = /* @__PURE__ */ new Map();
1038
+ const known = /* @__PURE__ */ new Map();
1039
+ const parse2 = (file, source) => {
1040
+ if (!parsed.has(file)) {
1041
+ let text2 = source;
1042
+ if (text2 === void 0) {
1043
+ try {
1044
+ text2 = (0, import_node_fs3.readFileSync)((0, import_node_path3.join)(root, file), "utf8");
1045
+ } catch {
1046
+ text2 = void 0;
1047
+ }
1048
+ }
1049
+ parsed.set(file, text2 === void 0 ? null : analyse(text2, file));
1050
+ }
1051
+ return parsed.get(file);
1052
+ };
1053
+ const stopsLocally = (file, p, local, depth, seen) => {
1054
+ const body = p.functions.get(local);
1055
+ if (!body || seen.has(`${file}#${local}`)) return false;
1056
+ seen.add(`${file}#${local}`);
1057
+ let found = false;
1058
+ walk(body, (n) => {
1059
+ if (found || n.type !== "CallExpression" || n.callee?.type !== "Identifier") return;
1060
+ const name = n.callee.name;
1061
+ if (p.stops.has(name)) found = true;
1062
+ else if (p.functions.has(name) && name !== local) found = stopsLocally(file, p, name, depth, seen);
1063
+ else if (p.imports.has(name)) found = guardKey(file, p, name, depth + 1) !== null;
1064
+ });
1065
+ return found;
1066
+ };
1067
+ const guardKey = (file, p, local, depth) => {
1068
+ if (depth > MAX_DEPTH) return null;
1069
+ const imp = p.imports.get(local);
1070
+ if (!imp || imp.name === "*") return null;
1071
+ const target = load(imp.specifier, file);
1072
+ if (!target) return null;
1073
+ const key = `${target.file}#${imp.name}`;
1074
+ const cached = known.get(key);
1075
+ if (cached !== void 0) return cached ? key : null;
1076
+ known.set(key, false);
1077
+ const tp = parse2(target.file, target.source);
1078
+ const localName = tp?.exports.get(imp.name);
1079
+ const is = Boolean(tp && localName && stopsLocally(target.file, tp, localName, depth, /* @__PURE__ */ new Set()));
1080
+ known.set(key, is);
1081
+ return is ? key : null;
1082
+ };
1083
+ const guardsUnder = (file, p, node, visited = /* @__PURE__ */ new Set()) => {
1084
+ const out = /* @__PURE__ */ new Set();
1085
+ visited.add(node);
1086
+ walk(node, (n) => {
1087
+ if (n.type !== "CallExpression" || n.callee?.type !== "Identifier") return;
1088
+ const name = n.callee.name;
1089
+ if (p.imports.has(name)) {
1090
+ const key = guardKey(file, p, name, 0);
1091
+ if (key) out.add(key);
1092
+ } else if (p.functions.has(name)) {
1093
+ const body = p.functions.get(name);
1094
+ if (!visited.has(body)) for (const k of guardsUnder(file, p, body, visited)) out.add(k);
1095
+ }
1096
+ });
1097
+ return [...out];
1098
+ };
1099
+ return {
1100
+ /** Guards a page runs: in its file and the layouts above it (every function there, so a check inside a Suspense'd component counts). */
1101
+ page(files) {
1102
+ const out = /* @__PURE__ */ new Set();
1103
+ for (const file of files) {
1104
+ const p = parse2(file);
1105
+ if (p) for (const k of guardsUnder(file, p, p.program)) out.add(k);
1106
+ }
1107
+ return [...out].sort();
1108
+ },
1109
+ /** Guards a Server Action runs: in its own body. */
1110
+ action(file, exportName) {
1111
+ const p = parse2(file);
1112
+ const local = p?.exports.get(exportName);
1113
+ const body = local ? p.functions.get(local) : void 0;
1114
+ return p && body ? guardsUnder(file, p, body).sort() : [];
1115
+ }
1116
+ };
1117
+ }
1118
+
1119
+ // src/pages.ts
1120
+ var import_node_fs4 = require("fs");
1121
+ var import_node_path4 = require("path");
1122
+ var read = (root, file) => {
1123
+ try {
1124
+ return (0, import_node_fs4.readFileSync)((0, import_node_path4.join)(root, file), "utf8");
1125
+ } catch {
1126
+ return void 0;
1127
+ }
1128
+ };
1129
+ function defaultExportFunction(program) {
1130
+ const locals = /* @__PURE__ */ new Map();
1131
+ for (const stmt of program.body) {
1132
+ if (stmt.type === "FunctionDeclaration" && stmt.id) locals.set(stmt.id.name, stmt);
1133
+ if (stmt.type === "VariableDeclaration") {
1134
+ for (const d of stmt.declarations) if (d.id?.type === "Identifier" && FUNCTION_TYPES.has(unwrapTs(d.init)?.type ?? "")) locals.set(d.id.name, unwrapTs(d.init));
1135
+ }
1136
+ }
1137
+ for (const stmt of program.body) {
1138
+ if (stmt.type !== "ExportDefaultDeclaration") continue;
1139
+ const d = unwrapTs(stmt.declaration);
1140
+ if (d && FUNCTION_TYPES.has(d.type)) return d;
1141
+ if (d?.type === "Identifier") return locals.get(d.name);
1142
+ }
1143
+ return void 0;
1144
+ }
1145
+ var AWAITED = (n) => {
1146
+ const u = unwrapTs(n);
1147
+ if (u?.type === "AwaitExpression") return unwrapTs(u.argument);
1148
+ if (u?.type === "CallExpression" && u.callee?.type === "Identifier" && u.callee.name === "use") return unwrapTs(u.arguments?.[0]);
1149
+ return u;
1150
+ };
1151
+ var patternKeys = (p) => p.type === "ObjectPattern" ? p.properties.filter((x) => x.type === "ObjectProperty").map((x) => keyName(x.key)).filter((k) => Boolean(k)) : [];
1152
+ function pageSearchParams(program) {
1153
+ const names = /* @__PURE__ */ new Set();
1154
+ const sources = /* @__PURE__ */ new Set();
1155
+ const propsNames = /* @__PURE__ */ new Set();
1156
+ walk(program, (fn) => {
1157
+ if (!FUNCTION_TYPES.has(fn.type)) return;
1158
+ const param = fn.params?.[0];
1159
+ if (param?.type === "ObjectPattern") {
1160
+ for (const prop of param.properties) {
1161
+ if (prop.type !== "ObjectProperty" || keyName(prop.key) !== "searchParams") continue;
1162
+ const value = prop.value.type === "AssignmentPattern" ? prop.value.left : prop.value;
1163
+ if (value.type === "Identifier") sources.add(value.name);
1164
+ for (const k of patternKeys(value)) names.add(k);
1165
+ }
1166
+ } else if (param?.type === "Identifier" && fn === defaultExportFunction(program)) propsNames.add(param.name);
1167
+ });
1168
+ const isSource = (n) => {
1169
+ const a = AWAITED(n);
1170
+ if (!a) return false;
1171
+ if (a.type === "Identifier") return sources.has(a.name);
1172
+ return a.type === "MemberExpression" && unwrapTs(a.object)?.type === "Identifier" && propsNames.has(unwrapTs(a.object).name) && keyName(a.property) === "searchParams";
1173
+ };
1174
+ const promiseAll = (n) => {
1175
+ const a = AWAITED(n);
1176
+ const callee = a?.type === "CallExpression" ? a.callee : void 0;
1177
+ return callee?.type === "MemberExpression" && keyName(callee.object) === "Promise" && keyName(callee.property) === "all" && a.arguments?.[0]?.type === "ArrayExpression" ? a.arguments[0].elements : void 0;
1178
+ };
1179
+ walk(program, (n) => {
1180
+ if (n.type !== "VariableDeclarator") return;
1181
+ if (isSource(n.init)) {
1182
+ if (n.id.type === "Identifier") sources.add(n.id.name);
1183
+ for (const k of patternKeys(n.id)) names.add(k);
1184
+ }
1185
+ const all = promiseAll(n.init);
1186
+ if (all && n.id.type === "ArrayPattern") {
1187
+ n.id.elements.forEach((el, i) => {
1188
+ if (!el || !isSource(all[i])) return;
1189
+ if (el.type === "Identifier") sources.add(el.name);
1190
+ for (const k of patternKeys(el)) names.add(k);
1191
+ });
1192
+ }
1193
+ });
1194
+ walk(program, (n) => {
1195
+ if (n.type !== "MemberExpression" || !isSource(n.object)) return;
1196
+ const k = n.computed ? stringValue(n.property) : keyName(n.property);
1197
+ if (k) names.add(k);
1198
+ });
1199
+ return [...names];
1200
+ }
1201
+ function hookSearchParams(program) {
1202
+ const names = /* @__PURE__ */ new Set();
1203
+ const holders = /* @__PURE__ */ new Set();
1204
+ const isHook = (n) => {
1205
+ const u = unwrapTs(n);
1206
+ return u?.type === "CallExpression" && u.callee?.type === "Identifier" && u.callee.name === "useSearchParams";
1207
+ };
1208
+ walk(program, (n) => {
1209
+ if (n.type === "VariableDeclarator" && n.id.type === "Identifier" && isHook(n.init)) holders.add(n.id.name);
1210
+ });
1211
+ walk(program, (n) => {
1212
+ if (n.type !== "CallExpression" || n.callee?.type !== "MemberExpression") return;
1213
+ const method = keyName(n.callee.property);
1214
+ if (method !== "get" && method !== "getAll" && method !== "has") return;
1215
+ const target = unwrapTs(n.callee.object);
1216
+ const fromHook = isHook(target) || target?.type === "Identifier" && holders.has(target.name);
1217
+ const name = stringValue(n.arguments?.[0]);
1218
+ if (fromHook && name) names.add(name);
1219
+ });
1220
+ return [...names];
1221
+ }
1222
+ function staticMetadata(program) {
1223
+ for (const stmt of program.body) {
1224
+ if (stmt.type !== "ExportNamedDeclaration" || stmt.declaration?.type !== "VariableDeclaration") continue;
1225
+ for (const d of stmt.declaration.declarations) {
1226
+ const init = unwrapTs(d.init);
1227
+ if (d.id?.type !== "Identifier" || d.id.name !== "metadata" || init?.type !== "ObjectExpression") continue;
1228
+ const out = {};
1229
+ for (const prop of init.properties) {
1230
+ if (prop.type !== "ObjectProperty") continue;
1231
+ const key = keyName(prop.key);
1232
+ const value = unwrapTs(prop.value);
1233
+ if (key === "description") {
1234
+ const s = stringValue(value);
1235
+ if (s) out.description = s;
1236
+ }
1237
+ if (key === "title") {
1238
+ let s = stringValue(value);
1239
+ if (!s && value?.type === "ObjectExpression") {
1240
+ const byKey = new Map(value.properties.filter((p) => p.type === "ObjectProperty").map((p) => [keyName(p.key), stringValue(unwrapTs(p.value))]));
1241
+ s = byKey.get("absolute") ?? byKey.get("default");
1242
+ }
1243
+ if (s) out.title = s;
1244
+ }
1245
+ }
1246
+ return out;
1247
+ }
1248
+ }
1249
+ return {};
1250
+ }
1251
+ function readsData(program, file, load, actionFiles) {
1252
+ const imported = /* @__PURE__ */ new Set();
1253
+ for (const stmt of program.body) {
1254
+ if (stmt.type !== "ImportDeclaration" || stmt.importKind === "type") continue;
1255
+ const source = stmt.source.value;
1256
+ if (/^next(\/|$)/.test(source) || source === "react") continue;
1257
+ if (actionFiles.has(load(source, file)?.file ?? "")) continue;
1258
+ for (const s of stmt.specifiers) if (!/^[A-Z]/.test(s.local.name)) imported.add(s.local.name);
1259
+ }
1260
+ let reads = false;
1261
+ walk(program, (n, parents) => {
1262
+ if (reads) return;
1263
+ if (n.type === "AwaitExpression") reads = true;
1264
+ if (n.type !== "Identifier" || !imported.has(n.name)) return;
1265
+ const parent = parents[parents.length - 1];
1266
+ if (!parent || parent.type.startsWith("Import")) return;
1267
+ if (parent.type === "CallExpression" && parent.callee === n) return;
1268
+ if (parent.type === "MemberExpression" && parent.property === n && !parent.computed || parent.type === "ObjectProperty" && parent.key === n && !parent.computed) return;
1269
+ reads = true;
1270
+ });
1271
+ return reads;
1272
+ }
1273
+ var directive = (program) => program.directives?.[0]?.value?.value;
1274
+ function pageToolName(path, snake) {
1275
+ const words = path.split("/").filter(Boolean).map((seg) => seg.replace(/^:/, "").replace(/\*\??$/, ""));
1276
+ return `view_${snake(words.join("_")) || "home"}`;
1277
+ }
1278
+ function discoverPages(root, entries, graph, load, actionFiles, formFiles, taken, snake) {
1279
+ const hookCache = /* @__PURE__ */ new Map();
1280
+ const hookNames = (file) => {
1281
+ let names = hookCache.get(file);
1282
+ if (!names) {
1283
+ const source = read(root, file);
1284
+ const program = source?.includes("useSearchParams") ? parseModule(source, file) : null;
1285
+ names = program ? hookSearchParams(program) : [];
1286
+ hookCache.set(file, names);
1287
+ }
1288
+ return names;
1289
+ };
1290
+ const used = new Set(taken);
1291
+ const out = [];
1292
+ for (const entry of [...entries].sort((a, b) => a.path.localeCompare(b.path))) {
1293
+ const source = read(root, entry.file);
1294
+ const program = source ? parseModule(source, entry.file) : null;
1295
+ const reachable = graph.reachable([entry.file]);
1296
+ const search = new Set(program ? pageSearchParams(program) : []);
1297
+ for (const file of reachable) for (const n of hookNames(file)) search.add(n);
1298
+ const meta = program ? staticMetadata(program) : {};
1299
+ const hasForm = [...reachable].some((f) => formFiles.has(f));
1300
+ const formOnly = hasForm && Boolean(program) && ![...reachable].some((f) => {
1301
+ if (!/\.[jt]sx$/.test(f) && f !== entry.file) return false;
1302
+ const prog = f === entry.file ? program : parseModule(read(root, f) ?? "", f);
1303
+ return prog ? directive(prog) === void 0 && readsData(prog, f, load, actionFiles) : false;
1304
+ });
1305
+ let name = pageToolName(entry.path, snake);
1306
+ for (let i = 2; used.has(name); i++) name = `${pageToolName(entry.path, snake)}_${i}`;
1307
+ used.add(name);
1308
+ out.push({
1309
+ file: entry.file,
1310
+ path: entry.path,
1311
+ params: entry.params,
1312
+ name,
1313
+ search: [...search].filter((s) => !entry.params.includes(s)).sort(),
1314
+ ...meta,
1315
+ ...formOnly ? { formOnly: true } : {}
1316
+ });
1317
+ }
1318
+ return out;
1319
+ }
1320
+
1321
+ // src/resolve.ts
1322
+ var import_node_fs5 = require("fs");
1323
+ var import_node_path5 = require("path");
1324
+ var EXTENSIONS = [".ts", ".tsx", ".mts", ".js", ".jsx", ".mjs"];
1325
+ function readTsconfig(root) {
1326
+ try {
1327
+ const raw = (0, import_node_fs5.readFileSync)((0, import_node_path5.join)(root, "tsconfig.json"), "utf8").replace(/("(?:\\.|[^"\\])*")|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, (_m, str2) => str2 ?? "").replace(/,(\s*[}\]])/g, "$1");
1328
+ const options = JSON.parse(raw).compilerOptions ?? {};
1329
+ return { ...options.baseUrl ? { baseUrl: options.baseUrl } : {}, ...options.paths ? { paths: options.paths } : {} };
1330
+ } catch {
1331
+ return {};
1332
+ }
1333
+ }
1334
+ function asFile(path) {
1335
+ const candidates = [path, ...EXTENSIONS.map((e) => path + e), ...EXTENSIONS.map((e) => (0, import_node_path5.join)(path, `index${e}`))];
1336
+ if (/\.[mc]?jsx?$/.test(path)) candidates.push(...EXTENSIONS.map((e) => path.replace(/\.[mc]?jsx?$/, e)));
1337
+ return candidates.find((c) => (0, import_node_fs5.existsSync)(c) && (0, import_node_fs5.statSync)(c).isFile());
1338
+ }
1339
+ function moduleLoader(root) {
1340
+ const { baseUrl, paths } = readTsconfig(root);
1341
+ const base = (0, import_node_path5.join)(root, baseUrl ?? ".");
1342
+ const cache = /* @__PURE__ */ new Map();
1343
+ const candidates = (specifier2, fromFile) => {
1344
+ if (specifier2.startsWith(".")) return [(0, import_node_path5.resolve)(root, (0, import_node_path5.dirname)(fromFile), specifier2)];
1345
+ for (const [pattern, targets] of Object.entries(paths ?? {})) {
1346
+ const star = pattern.indexOf("*");
1347
+ const prefix = star < 0 ? pattern : pattern.slice(0, star);
1348
+ const suffix = star < 0 ? "" : pattern.slice(star + 1);
1349
+ const matches = star < 0 ? specifier2 === pattern : specifier2.startsWith(prefix) && specifier2.endsWith(suffix) && specifier2.length >= prefix.length + suffix.length;
1350
+ if (!matches) continue;
1351
+ const middle = star < 0 ? "" : specifier2.slice(prefix.length, specifier2.length - suffix.length);
1352
+ return targets.map((t) => (0, import_node_path5.join)(base, t.replace("*", middle)));
1353
+ }
1354
+ return baseUrl ? [(0, import_node_path5.join)(base, specifier2)] : [];
1355
+ };
1356
+ return (specifier2, fromFile) => {
1357
+ const key = `${fromFile}\0${specifier2}`;
1358
+ if (cache.has(key)) return cache.get(key);
1359
+ let found;
1360
+ for (const candidate of candidates(specifier2, fromFile)) {
1361
+ const path = asFile(candidate);
1362
+ const rel = path && (0, import_node_path5.relative)(root, path);
1363
+ if (!path || !rel || rel.startsWith("..") || rel.split(import_node_path5.sep).includes("node_modules")) continue;
1364
+ found = { file: rel.split(import_node_path5.sep).join("/"), source: (0, import_node_fs5.readFileSync)(path, "utf8") };
1365
+ break;
1366
+ }
1367
+ cache.set(key, found);
1368
+ return found;
1369
+ };
1370
+ }
1371
+
1372
+ // src/parse.ts
1373
+ var SOURCE_CAP = 6e3;
1374
+ var PACKAGES = /* @__PURE__ */ new Set(["@exposify/next", "@exposify/core"]);
1375
+ var METHOD_SET = new Set(HTTP_METHODS);
1376
+ var cap = (s) => s === void 0 ? void 0 : s.length > SOURCE_CAP ? `${s.slice(0, SOURCE_CAP)}
1377
+ /* \u2026 truncated */` : s;
1378
+ function ourImports(program) {
1379
+ const locals = /* @__PURE__ */ new Map();
1380
+ for (const stmt of program.body) {
1381
+ if (stmt.type !== "ImportDeclaration" || !PACKAGES.has(stmt.source.value)) continue;
1382
+ for (const spec of stmt.specifiers) {
1383
+ if (spec.type === "ImportSpecifier") locals.set(spec.local.name, keyName(spec.imported) ?? spec.local.name);
1384
+ }
1385
+ }
1386
+ return locals;
1387
+ }
1388
+ function writtenInput(n) {
1389
+ n = unwrapTs(n);
1390
+ if (n?.type !== "ObjectExpression") return "opaque";
1391
+ const out = {};
1392
+ for (const prop of n.properties) {
1393
+ const key = prop.type === "ObjectProperty" ? keyName(prop.key) : void 0;
1394
+ if (!key) continue;
1395
+ let values;
1396
+ walk(prop.value, (c) => {
1397
+ if (values || c.type !== "CallExpression" || keyName(c.callee?.property) !== "enum") return;
1398
+ const list = unwrapTs(c.arguments[0]);
1399
+ const items = list?.type === "ArrayExpression" ? list.elements.map((e) => stringValue(e)) : [];
1400
+ if (items.length > 0 && items.every((v) => v !== void 0)) values = items;
1401
+ });
1402
+ out[key] = values ? { enum: values } : {};
1403
+ }
1404
+ return out;
1405
+ }
1406
+ function markerOf(init, imports) {
1407
+ const call = unwrapTs(init);
1408
+ if (call?.type !== "CallExpression" || call.callee.type !== "Identifier") return { handler: call };
1409
+ const imported = imports.get(call.callee.name);
1410
+ if (imported !== "exposed" && imported !== "hidden" && imported !== "describe") return { handler: call };
1411
+ const handler = call.arguments[0];
1412
+ if (imported !== "describe") return { marker: { kind: imported }, handler };
1413
+ const meta = call.arguments[1];
1414
+ const marker = { kind: "described" };
1415
+ if (meta?.type === "ObjectExpression") {
1416
+ for (const prop of meta.properties) {
1417
+ if (prop.type !== "ObjectProperty") continue;
1418
+ const key = keyName(prop.key);
1419
+ const value = stringValue(prop.value);
1420
+ if (key === "name" && value) marker.name = value;
1421
+ if (key === "description" && value) marker.description = value;
1422
+ if (key === "input") marker.input = writtenInput(prop.value);
1423
+ }
1424
+ }
1425
+ return { marker, handler };
1426
+ }
1427
+ function analyzeRouteModule(source, file) {
1428
+ const out = { methods: [], edge: false, mcpRoute: false, markers: {}, sources: {} };
1429
+ const program = parseModule(source, file);
1430
+ if (!program) return out;
1431
+ const imports = ourImports(program);
1432
+ out.mcpRoute = [...imports.values()].includes("aiReady");
1433
+ const found = /* @__PURE__ */ new Set();
1434
+ const record = (name, init, whole) => {
1435
+ if (!METHOD_SET.has(name)) return;
1436
+ found.add(name);
1437
+ const { marker, handler } = markerOf(init, imports);
1438
+ if (marker) out.markers[name] = marker;
1439
+ const src = cap(text(source, handler ?? whole));
1440
+ if (src) out.sources[name] = src;
1441
+ };
1442
+ for (const stmt of program.body) {
1443
+ if (stmt.type !== "ExportNamedDeclaration") continue;
1444
+ const decl = stmt.declaration;
1445
+ if (decl?.type === "FunctionDeclaration" && decl.id) record(decl.id.name, void 0, decl);
1446
+ if (decl?.type === "VariableDeclaration") {
1447
+ for (const d of decl.declarations) {
1448
+ if (d.id.type === "Identifier") {
1449
+ record(d.id.name, d.init, d.init);
1450
+ if (d.id.name === "runtime" && stringValue(d.init) === "edge") out.edge = true;
1451
+ } else if (d.id.type === "ObjectPattern") {
1452
+ for (const p of d.id.properties) if (p.type === "ObjectProperty" && p.value.type === "Identifier") record(p.value.name);
1453
+ }
1454
+ }
1455
+ }
1456
+ for (const spec of stmt.specifiers ?? []) {
1457
+ const exported = keyName(spec.exported);
1458
+ if (exported) record(exported);
1459
+ }
1460
+ }
1461
+ out.methods = HTTP_METHODS.filter((m) => found.has(m));
1462
+ return out;
1463
+ }
1464
+ function detectPagesMethods(source, file) {
1465
+ const program = parseModule(source, file);
1466
+ if (!program) return null;
1467
+ const usesNextConnect = program.body.some((s) => s.type === "ImportDeclaration" && s.source.value === "next-connect");
1468
+ if (usesNextConnect) {
1469
+ const methods2 = /* @__PURE__ */ new Set();
1470
+ walk(program, (n) => {
1471
+ if (n.type !== "CallExpression" || n.callee.type !== "MemberExpression") return;
1472
+ const name = keyName(n.callee.property)?.toUpperCase();
1473
+ if (name && METHOD_SET.has(name) && name !== "OPTIONS") methods2.add(name);
1474
+ });
1475
+ if (methods2.size > 0) return { methods: HTTP_METHODS.filter((m) => methods2.has(m)), via: "next-connect" };
1476
+ }
1477
+ const aliases = /* @__PURE__ */ new Set();
1478
+ walk(program, (n) => {
1479
+ if (n.type !== "VariableDeclarator") return;
1480
+ if (n.id.type === "ObjectPattern" && n.init?.type === "Identifier") {
1481
+ for (const p of n.id.properties) if (p.type === "ObjectProperty" && keyName(p.key) === "method" && p.value.type === "Identifier") aliases.add(p.value.name);
1482
+ }
1483
+ if (n.id.type === "Identifier" && isReqMethod(n.init, aliases)) aliases.add(n.id.name);
1484
+ });
1485
+ const methods = /* @__PURE__ */ new Set();
1486
+ let via = "if";
1487
+ const add = (value) => {
1488
+ const m = value?.toUpperCase();
1489
+ if (m && METHOD_SET.has(m)) methods.add(m);
1490
+ };
1491
+ walk(program, (n) => {
1492
+ if (n.type === "BinaryExpression" && ["===", "==", "!==", "!="].includes(n.operator)) {
1493
+ if (isReqMethod(n.left, aliases)) add(stringValue(n.right));
1494
+ else if (isReqMethod(n.right, aliases)) add(stringValue(n.left));
1495
+ } else if (n.type === "SwitchStatement" && isReqMethod(n.discriminant, aliases)) {
1496
+ for (const c of n.cases) add(stringValue(c.test));
1497
+ if (methods.size > 0) via = "switch";
1498
+ } else if (n.type === "CallExpression" && n.callee.type === "MemberExpression" && keyName(n.callee.property) === "includes" && n.callee.object.type === "ArrayExpression" && isReqMethod(n.arguments[0], aliases)) {
1499
+ for (const el of n.callee.object.elements) add(stringValue(el));
1500
+ }
1501
+ });
1502
+ methods.delete("OPTIONS");
1503
+ return methods.size > 0 ? { methods: HTTP_METHODS.filter((m) => methods.has(m)), via } : null;
1504
+ }
1505
+ function isReqMethod(n, aliases) {
1506
+ n = unwrapTs(n);
1507
+ if (!n) return false;
1508
+ if (n.type === "Identifier") return aliases.has(n.name);
1509
+ if (n.type === "OptionalMemberExpression" || n.type === "MemberExpression") return keyName(n.property) === "method" && !n.computed;
1510
+ return false;
1511
+ }
1512
+ function defaultExportSource(source, file) {
1513
+ const program = parseModule(source, file);
1514
+ const stmt = program?.body?.find((s) => s.type === "ExportDefaultDeclaration");
1515
+ if (!stmt) return void 0;
1516
+ let decl = stmt.declaration;
1517
+ if (decl.type === "Identifier") {
1518
+ for (const s of program.body) {
1519
+ const d = s.type === "ExportNamedDeclaration" ? s.declaration : s;
1520
+ if (d?.type === "FunctionDeclaration" && d.id?.name === decl.name) return cap(text(source, d));
1521
+ if (d?.type === "VariableDeclaration") {
1522
+ const v = d.declarations.find((x) => x.id.type === "Identifier" && x.id.name === decl.name);
1523
+ if (v) return cap(text(source, v.init));
1524
+ }
1525
+ }
1526
+ }
1527
+ return cap(text(source, decl));
1528
+ }
1529
+ var FORM_READS2 = /* @__PURE__ */ new Set(["get", "getAll", "has"]);
1530
+ var PREV_STATE_NAME = /^_*(prev|previous|state|prevState|previousState|initialState|currentState)?$/i;
1531
+ var SEARCH_DEPTH2 = 3;
1532
+ function isActionModule(program) {
1533
+ return program.directives?.[0]?.value?.value === "use server";
1534
+ }
1535
+ function localFunctions(program, imports) {
1536
+ const out = /* @__PURE__ */ new Map();
1537
+ for (const stmt of program.body) {
1538
+ const decl = stmt.type === "ExportNamedDeclaration" ? stmt.declaration : stmt;
1539
+ if (decl?.type === "FunctionDeclaration" && decl.id) out.set(decl.id.name, { fn: decl });
1540
+ if (decl?.type === "VariableDeclaration") {
1541
+ for (const d of decl.declarations) {
1542
+ if (d.id.type !== "Identifier") continue;
1543
+ const { handler } = markerOf(d.init, imports);
1544
+ const fn = unwrapTs(handler);
1545
+ if (fn && FUNCTION_TYPES.has(fn.type)) out.set(d.id.name, { fn, init: d.init });
1546
+ }
1547
+ }
1548
+ }
1549
+ return out;
1550
+ }
1551
+ var typedFormData = (p) => {
1552
+ const id = p.type === "AssignmentPattern" ? p.left : p;
1553
+ const t = id?.typeAnnotation?.typeAnnotation;
1554
+ return t?.type === "TSTypeReference" && t.typeName?.type === "Identifier" && t.typeName.name === "FormData";
1555
+ };
1556
+ function references(body, name) {
1557
+ let count = 0;
1558
+ walk(body, (n) => {
1559
+ if (n.type === "Identifier" && n.name === name) count++;
1560
+ if ((n.type === "MemberExpression" || n.type === "OptionalMemberExpression") && !n.computed && n.property.type === "Identifier" && n.property.name === name) count--;
1561
+ if (n.type === "ObjectProperty" && !n.computed && !n.shorthand && n.key.type === "Identifier" && n.key.name === name) count--;
1562
+ });
1563
+ return count;
1564
+ }
1565
+ function readsForm(body, name) {
1566
+ let found = false;
1567
+ walk(body, (n) => {
1568
+ if (found || n.type !== "CallExpression") return;
1569
+ const callee = n.callee;
1570
+ if (callee.type !== "MemberExpression" || callee.object.type !== "Identifier" || callee.object.name !== name) return;
1571
+ const method = keyName(callee.property);
1572
+ if (method && FORM_READS2.has(method) && stringValue(n.arguments[0]) !== void 0) found = true;
1573
+ });
1574
+ return found;
1575
+ }
1576
+ function actionParams(fn, shape) {
1577
+ const raw = fn.params.filter((p) => p.type !== "RestElement");
1578
+ if (shape) {
1579
+ const formIndex2 = shape.bound + (shape.prev ? 1 : 0);
1580
+ return raw.map((p, i) => {
1581
+ const name = paramName(p) ?? `arg${i}`;
1582
+ if (i < shape.bound) return { name, role: "input" };
1583
+ if (i === formIndex2) return { name, role: "formData" };
1584
+ if (shape.prev && i === shape.bound) return { name, role: "prev" };
1585
+ return { name, role: "input" };
1586
+ });
1587
+ }
1588
+ let formIndex = raw.findIndex(typedFormData);
1589
+ if (formIndex < 0) formIndex = raw.findIndex((p) => {
1590
+ const name = paramName(p);
1591
+ return name !== void 0 && readsForm(fn.body, name);
1592
+ });
1593
+ return raw.map((p, i) => {
1594
+ const name = paramName(p) ?? `arg${i}`;
1595
+ if (i === formIndex) return { name, role: "formData" };
1596
+ if (p.type === "ObjectPattern" || p.type === "AssignmentPattern" && p.left.type === "ObjectPattern") return { name: "input", role: "object" };
1597
+ if (formIndex === 1 && i === 0 && (PREV_STATE_NAME.test(name) || references(fn.body, name) === 0)) return { name, role: "prev" };
1598
+ return { name, role: "input" };
1599
+ });
1600
+ }
1601
+ function setsCookies(fn, program, functions) {
1602
+ const cookiesLocal = program.body.filter((s) => s.type === "ImportDeclaration" && s.source.value === "next/headers").flatMap((s) => s.specifiers).find((s) => s.type === "ImportSpecifier" && keyName(s.imported) === "cookies")?.local?.name;
1603
+ if (!cookiesLocal) return false;
1604
+ const isCookiesCall = (n) => {
1605
+ n = unwrapTs(n);
1606
+ if (n?.type === "AwaitExpression") n = unwrapTs(n.argument);
1607
+ return n?.type === "CallExpression" && n.callee.type === "Identifier" && n.callee.name === cookiesLocal;
1608
+ };
1609
+ const visited = /* @__PURE__ */ new Set();
1610
+ const search = (f, depth) => {
1611
+ if (visited.has(f) || depth > SEARCH_DEPTH2) return false;
1612
+ visited.add(f);
1613
+ const jars = /* @__PURE__ */ new Set();
1614
+ walk(f.body, (n) => {
1615
+ if (n.type === "VariableDeclarator" && n.id.type === "Identifier" && isCookiesCall(n.init)) jars.add(n.id.name);
1616
+ });
1617
+ let found = false;
1618
+ const callees = [];
1619
+ walk(f.body, (n) => {
1620
+ if (found || n.type !== "CallExpression") return;
1621
+ const callee = n.callee;
1622
+ if (callee.type === "Identifier") callees.push(callee.name);
1623
+ if (callee.type !== "MemberExpression" || !["set", "delete"].includes(keyName(callee.property) ?? "")) return;
1624
+ const target = unwrapTs(callee.object);
1625
+ if (isCookiesCall(target) || target?.type === "Identifier" && jars.has(target.name)) found = true;
1626
+ });
1627
+ if (found) return true;
1628
+ return callees.some((name) => {
1629
+ const local = functions.get(name);
1630
+ return local !== void 0 && search(local.fn, depth + 1);
1631
+ });
1632
+ };
1633
+ return search(fn, 0);
1634
+ }
1635
+ function analyzeActionModule(source, file, load, shapes) {
1636
+ const program = parseModule(source, file);
1637
+ if (!program || !isActionModule(program)) return null;
1638
+ const imports = ourImports(program);
1639
+ const functions = localFunctions(program, imports);
1640
+ const topLevel = new Map([...functions].map(([name, f]) => [name, f.fn]));
1641
+ const out = [];
1642
+ const record = (name, at, local) => {
1643
+ const line = lineOf(at);
1644
+ if (!local) {
1645
+ out.push({ name, line, params: [], setsCookies: false, resolved: false, fields: [], readsAll: false, unresolved: [`line ${line}: it's defined in another module, so its code wasn't read`] });
1646
+ return;
1647
+ }
1648
+ const { marker } = markerOf(local.init, imports);
1649
+ const src = cap(text(source, local.fn));
1650
+ const params = actionParams(local.fn, shapes?.get(name));
1651
+ out.push({
1652
+ name,
1653
+ line,
1654
+ params,
1655
+ ...analyzeFields(local.fn, params, { program, source, file, topLevel, ...load ? { load } : {} }),
1656
+ setsCookies: setsCookies(local.fn, program, functions),
1657
+ resolved: true,
1658
+ ...marker ? { marker } : {},
1659
+ ...src ? { source: src } : {}
1660
+ });
1661
+ };
1662
+ for (const stmt of program.body) {
1663
+ if (stmt.type === "ExportDefaultDeclaration") {
1664
+ const decl2 = stmt.declaration;
1665
+ if (FUNCTION_TYPES.has(decl2.type)) record("default", stmt, { fn: decl2 });
1666
+ else if (decl2.type === "Identifier") record("default", stmt, functions.get(decl2.name));
1667
+ else {
1668
+ const fn = unwrapTs(markerOf(decl2, imports).handler);
1669
+ record("default", stmt, fn && FUNCTION_TYPES.has(fn.type) ? { fn, init: decl2 } : void 0);
1670
+ }
1671
+ continue;
1672
+ }
1673
+ if (stmt.type !== "ExportNamedDeclaration") continue;
1674
+ if (stmt.exportKind === "type") continue;
1675
+ const decl = stmt.declaration;
1676
+ if (decl?.type === "FunctionDeclaration" && decl.id) record(decl.id.name, decl, functions.get(decl.id.name));
1677
+ if (decl?.type === "VariableDeclaration") {
1678
+ for (const d of decl.declarations) if (d.id.type === "Identifier") record(d.id.name, d, functions.get(d.id.name));
1679
+ }
1680
+ for (const spec of stmt.specifiers ?? []) {
1681
+ if (spec.type !== "ExportSpecifier" || spec.exportKind === "type") continue;
1682
+ const exported = keyName(spec.exported);
1683
+ if (!exported) continue;
1684
+ record(exported, spec, stmt.source ? void 0 : functions.get(spec.local.name));
1685
+ }
1686
+ }
1687
+ return out;
1688
+ }
1689
+ function findInlineActions(source, file) {
1690
+ const program = parseModule(source, file);
1691
+ if (!program) return [];
1692
+ const out = [];
1693
+ const named2 = /* @__PURE__ */ new Map();
1694
+ walk(program, (n) => {
1695
+ if (n.type === "VariableDeclarator" && n.id.type === "Identifier" && n.init) named2.set(unwrapTs(n.init), n.id.name);
1696
+ if (!FUNCTION_TYPES.has(n.type) || n.body?.type !== "BlockStatement") return;
1697
+ if (!n.body.directives.some((d) => d.value?.value === "use server")) return;
1698
+ if (isActionModule(program) && program.body.some((s) => s === n || s.declaration === n || s.declaration?.declarations?.some((d) => unwrapTs(d.init) === n))) return;
1699
+ const name = n.id?.name ?? named2.get(n);
1700
+ out.push({ ...name ? { name } : {}, line: n.loc?.start.line ?? 1 });
1701
+ });
1702
+ return out;
1703
+ }
1704
+
1705
+ // src/codegen.ts
1706
+ var DEFAULT_DOCS = ["README.md", "docs/**/*.md"];
1707
+ var actionRoute = (mcpPath, key) => `POST ${(mcpPath ?? "/mcp").replace(/\/$/, "")}/actions/${key}`;
1708
+ function actionSignature(route, source) {
1709
+ const [method, path] = route.split(" ");
1710
+ return (0, import_node_crypto.createHash)("sha256").update(JSON.stringify([method, path, [], source ?? ""])).digest("hex").slice(0, 16);
1711
+ }
1712
+ var posix = (p) => p.split(import_node_path6.sep).join("/");
1713
+ function firstDir(root, names) {
1714
+ return names.find((n) => (0, import_node_fs6.existsSync)((0, import_node_path6.join)(root, n)));
1715
+ }
1716
+ function filesUnder(root, dir) {
1717
+ const out = [];
1718
+ const visit = (rel) => {
1719
+ let entries;
1720
+ try {
1721
+ entries = (0, import_node_fs6.readdirSync)((0, import_node_path6.join)(root, rel), { withFileTypes: true });
1722
+ } catch {
1723
+ return;
1724
+ }
1725
+ for (const e of entries) {
1726
+ if (e.name === "node_modules") continue;
1727
+ const child = `${rel}/${e.name}`;
1728
+ if (e.isDirectory()) visit(child);
1729
+ else out.push(child);
1730
+ }
1731
+ };
1732
+ visit(dir);
1733
+ return out.sort();
1734
+ }
1735
+ function discover(root, opts = {}) {
1736
+ const exts = opts.pageExtensions ?? DEFAULT_PAGE_EXTENSIONS;
1737
+ const extPattern = new RegExp(`\\.(${exts.map((e) => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})$`);
1738
+ const appDir = firstDir(root, ["app", "src/app"]);
1739
+ const pagesDir = firstDir(root, ["pages", "src/pages"]);
1740
+ const out = { root, routes: [], actions: [], inlineActions: [], pages: [], ...appDir ? { appDir } : {}, ...pagesDir ? { pagesDir } : {} };
1741
+ if (appDir) {
1742
+ for (const file of filesUnder(root, appDir)) {
1743
+ const name = file.slice(file.lastIndexOf("/") + 1);
1744
+ if (!/^route\./.test(name) || !extPattern.test(name)) continue;
1745
+ const dirSegs = file.slice(appDir.length + 1).split("/").slice(0, -1);
1746
+ const route = appRoute(dirSegs);
1747
+ if (!route) continue;
1748
+ const source = (0, import_node_fs6.readFileSync)((0, import_node_path6.join)(root, file), "utf8");
1749
+ const module2 = analyzeRouteModule(source, file);
1750
+ if (module2.mcpRoute) {
1751
+ out.mcpRoute ??= { file, source };
1752
+ continue;
1753
+ }
1754
+ if (module2.methods.length === 0) continue;
1755
+ out.routes.push({ router: "app", file, ...route, module: module2 });
1756
+ }
1757
+ }
1758
+ if (pagesDir) {
1759
+ for (const file of filesUnder(root, `${pagesDir}/api`)) {
1760
+ if (!extPattern.test(file) || /\.d\.ts$/.test(file)) continue;
1761
+ const segs = file.slice(pagesDir.length + 1).replace(extPattern, "").split("/");
1762
+ const route = pagesApiRoute(segs);
1763
+ if (!route) continue;
1764
+ const source = (0, import_node_fs6.readFileSync)((0, import_node_path6.join)(root, file), "utf8");
1765
+ const src = defaultExportSource(source, file);
1766
+ out.routes.push({ router: "pages", file, ...route, detected: detectPagesMethods(source, file), ...src ? { source: src } : {} });
1767
+ }
1768
+ }
1769
+ out.routes.sort((a, b) => a.path.localeCompare(b.path) || a.file.localeCompare(b.file));
1770
+ if (appDir && out.mcpRoute) {
1771
+ const segs = out.mcpRoute.file.slice(appDir.length + 1).split("/").slice(0, -1);
1772
+ const route = appRoute(segs);
1773
+ const catchAll = route ? /^(.*)\/:[A-Za-z0-9_]+\*\??$/.exec(route.path) : null;
1774
+ if (catchAll) out.mcpPath = catchAll[1] || "/";
1775
+ }
1776
+ discoverActions(root, out, exts);
1777
+ const found = findLogin(out);
1778
+ if (found) {
1779
+ out.login = found.login;
1780
+ out.loginOthers = found.others;
1781
+ }
1782
+ return out;
1783
+ }
1784
+ var SOURCE_GLOB = "**/*.{ts,tsx,js,jsx,mjs,cjs}";
1785
+ var NOT_SOURCE = ["**/node_modules/**", ".next/**", "out/**", "build/**", "dist/**", "coverage/**", "**/*.d.ts"];
1786
+ function snakeCase(name) {
1787
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
1788
+ }
1789
+ function discoverActions(root, out, exts) {
1790
+ const files = (0, import_tinyglobby.globSync)(SOURCE_GLOB, { cwd: root, ignore: NOT_SOURCE }).sort();
1791
+ const load = moduleLoader(root);
1792
+ const modules = [];
1793
+ const components = /* @__PURE__ */ new Map();
1794
+ for (const file of files) {
1795
+ let source;
1796
+ try {
1797
+ source = (0, import_node_fs6.readFileSync)((0, import_node_path6.join)(root, file), "utf8");
1798
+ } catch {
1799
+ continue;
1800
+ }
1801
+ if (/\.[jt]sx$/.test(file)) components.set(file, source);
1802
+ if (!source.includes("use server")) continue;
1803
+ const actions = analyzeActionModule(source, file, load);
1804
+ if (actions) modules.push({ file, source, actions });
1805
+ for (const inline of findInlineActions(source, file)) out.inlineActions.push({ file, ...inline });
1806
+ }
1807
+ const exportsByFile = new Map(modules.map((m) => [m.file, new Set(m.actions.map((a) => a.name))]));
1808
+ const names = [...new Set(modules.flatMap((m) => m.actions.map((a) => a.name)))];
1809
+ const uses = [];
1810
+ const ctx = { load, modules: /* @__PURE__ */ new Map() };
1811
+ for (const [file, source] of components) {
1812
+ if (names.some((n) => source.includes(n === "default" ? "import" : n))) uses.push(...findFormUses(source, file, exportsByFile, load, ctx));
1813
+ }
1814
+ const found = [];
1815
+ for (const m of modules) {
1816
+ const mine = uses.filter((u) => u.action.file === m.file);
1817
+ const shapes = /* @__PURE__ */ new Map();
1818
+ for (const u of mine) if (!shapes.has(u.action.export)) shapes.set(u.action.export, { bound: u.bound, prev: u.prev });
1819
+ const actions = shapes.size > 0 ? analyzeActionModule(m.source, m.file, load, shapes) ?? m.actions : m.actions;
1820
+ for (const action of actions) found.push({ file: m.file, action: linkForms(action, mine.filter((u) => u.action.export === action.name)) });
1821
+ }
1822
+ const pages = out.appDir ? pageEntries(out.appDir, files, exts) : [];
1823
+ const graph = moduleGraph(root, load);
1824
+ const hosts = actionHosts(pages, graph, exportsByFile);
1825
+ const proposals = readProposedInputs(root);
1826
+ const stem = (file) => snakeCase(file.slice(file.lastIndexOf("/") + 1).replace(/\.[^.]+$/, ""));
1827
+ const base = (f) => (f.action.name === "default" ? stem(f.file) : snakeCase(f.action.name)) || "action";
1828
+ const counts = /* @__PURE__ */ new Map();
1829
+ for (const f of found) counts.set(base(f), (counts.get(base(f)) ?? 0) + 1);
1830
+ const taken = /* @__PURE__ */ new Set();
1831
+ for (const f of found) {
1832
+ let key = base(f);
1833
+ if (counts.get(key) > 1 && f.action.name !== "default") key = `${stem(f.file)}_${key}`;
1834
+ let candidate = key;
1835
+ for (let i = 2; taken.has(candidate); i++) candidate = `${key}_${i}`;
1836
+ taken.add(candidate);
1837
+ const route = actionRoute(out.mcpPath, candidate);
1838
+ const entry = proposals[route];
1839
+ const proposed = entry ? { fields: entry.fields, status: entry.signature === actionSignature(route, f.action.source) ? "current" : "stale" } : void 0;
1840
+ const host = hosts.get(`${f.file}#${f.action.name}`);
1841
+ out.actions.push({ ...f, key: candidate, ...proposed ? { proposed } : {}, ...host ? { host } : {} });
1842
+ }
1843
+ out.pages = discoverPages(root, pages, graph, load, new Set(modules.map((m) => m.file)), new Set(uses.map((u) => u.file)), new Set(out.actions.map((a) => a.key)), snakeCase);
1844
+ const guards = guardFinder(root, load);
1845
+ const rootsOf = new Map(pages.map((p) => [p.file, p.roots]));
1846
+ for (const page of out.pages) {
1847
+ const found2 = guards.page(rootsOf.get(page.file) ?? [page.file]);
1848
+ if (found2.length > 0) page.guards = found2;
1849
+ }
1850
+ for (const a of out.actions) {
1851
+ const found2 = guards.action(a.file, a.action.name);
1852
+ if (found2.length > 0) a.guards = found2;
1853
+ }
1854
+ }
1855
+ function readJson(path) {
1856
+ try {
1857
+ return JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
1858
+ } catch {
1859
+ return void 0;
1860
+ }
1861
+ }
1862
+ function readProposedInputs(root) {
1863
+ const path = (0, import_node_path6.join)(root, import_core2.DESCRIPTIONS_FILE);
1864
+ if (!(0, import_node_fs6.existsSync)(path)) return {};
1865
+ const parsed = (0, import_core2.parseDescriptions)((0, import_node_fs6.readFileSync)(path, "utf8"));
1866
+ return typeof parsed === "string" ? {} : parsed.inputs ?? {};
1867
+ }
1868
+ function readDescriptions(root, warn) {
1869
+ const path = (0, import_node_path6.join)(root, import_core2.DESCRIPTIONS_FILE);
1870
+ if (!(0, import_node_fs6.existsSync)(path)) return {};
1871
+ const parsed = (0, import_core2.parseDescriptions)((0, import_node_fs6.readFileSync)(path, "utf8"));
1872
+ if (typeof parsed === "string") {
1873
+ warn(`ignoring ${import_core2.DESCRIPTIONS_FILE}: ${parsed}`);
1874
+ return {};
1875
+ }
1876
+ return Object.fromEntries(Object.entries(parsed.descriptions).map(([k, e]) => [k, e.text.trim()]));
1877
+ }
1878
+ function readDocs(root, globs) {
1879
+ const paths = (0, import_tinyglobby.globSync)(globs, { cwd: root, ignore: ["**/node_modules/**", ".next/**"] }).sort();
1880
+ const docs = [];
1881
+ for (const path of paths) {
1882
+ try {
1883
+ docs.push({ path, content: (0, import_node_fs6.readFileSync)((0, import_node_path6.join)(root, path), "utf8") });
1884
+ } catch {
1885
+ }
1886
+ }
1887
+ return docs;
1888
+ }
1889
+ function defaultOutFile(root) {
1890
+ return (0, import_node_fs6.existsSync)((0, import_node_path6.join)(root, "tsconfig.json")) ? "exposify.generated.ts" : "exposify.generated.js";
1891
+ }
1892
+ function specifier(outFile, file) {
1893
+ const rel = posix((0, import_node_path6.relative)((0, import_node_path6.dirname)(outFile), file)).replace(/\.[^./]+$/, "");
1894
+ return rel.startsWith(".") ? rel : `./${rel}`;
1895
+ }
1896
+ function manifestLogin(l) {
1897
+ return l.kind === "action" ? { kind: "action", key: l.key, fields: l.fields, ...l.host ? { host: l.host } : {}, where: `${l.export} in ${l.file}` } : { kind: "route", path: l.path, fields: l.fields, body: l.body, where: `POST ${l.path} in ${l.file}` };
1898
+ }
1899
+ function renderManifest(d, opts) {
1900
+ const ts = opts.outFile.endsWith(".ts");
1901
+ const j = (v) => JSON.stringify(v);
1902
+ const routes = d.routes.map((r) => {
1903
+ const common = `router: ${j(r.router)}, path: ${j(r.path)}, params: ${j(r.params)}, file: ${j(r.file)}`;
1904
+ if (r.router === "pages") return ` { ${common} },`;
1905
+ const methods = `methods: ${j(r.module.methods)}`;
1906
+ if (r.module.edge) return ` { ${common}, ${methods}, edge: true },`;
1907
+ return ` { ${common}, ${methods}, load: () => import(${j(specifier(opts.outFile, r.file))}) },`;
1908
+ });
1909
+ const actions = d.actions.map(({ file, key, action, proposed, host, guards }) => {
1910
+ const flags = [
1911
+ action.setsCookies ? ", setsCookies: true" : "",
1912
+ `, fields: ${j(action.fields)}`,
1913
+ action.readsAll ? ", readsAll: true" : "",
1914
+ action.unresolved.length > 0 ? `, unresolved: ${j(action.unresolved)}` : "",
1915
+ action.forms && action.forms.length > 0 ? `, forms: ${j(action.forms)}` : "",
1916
+ proposed?.status === "current" ? `, proposed: ${j(proposed.fields)}` : "",
1917
+ host ? `, host: ${j(host)}` : "",
1918
+ guards ? `, guards: ${j(guards)}` : ""
1919
+ ].join("");
1920
+ return ` { key: ${j(key)}, file: ${j(file)}, export: ${j(action.name)}, params: ${j(action.params)}${flags}, load: () => import(${j(specifier(opts.outFile, file))}) },`;
1921
+ });
1922
+ const pages = d.pages.map((p) => {
1923
+ const extra = [
1924
+ p.search.length > 0 ? `, search: ${j(p.search)}` : "",
1925
+ p.title ? `, title: ${j(p.title)}` : "",
1926
+ p.description ? `, description: ${j(p.description)}` : "",
1927
+ p.formOnly ? ", formOnly: true" : "",
1928
+ p.guards ? `, guards: ${j(p.guards)}` : ""
1929
+ ].join("");
1930
+ return ` { name: ${j(p.name)}, path: ${j(p.path)}, params: ${j(p.params)}, file: ${j(p.file)}${extra} },`;
1931
+ });
1932
+ const lines = [
1933
+ "// Generated by exposify-next from your route files. Don't edit: withExposify()",
1934
+ "// in next.config regenerates it on `next dev` and `next build`, or run",
1935
+ "// `npx exposify-next generate`.",
1936
+ "/* eslint-disable */",
1937
+ ...ts ? ['import type { ExposifyManifest } from "@exposify/next";', ""] : ["", '/** @type {import("@exposify/next").ExposifyManifest} */'],
1938
+ `export const exposify${ts ? ": ExposifyManifest" : ""} = {`,
1939
+ ` info: ${j(opts.info)},`,
1940
+ ...opts.basePath ? [` basePath: ${j(opts.basePath)},`] : [],
1941
+ ...opts.trailingSlash ? [" trailingSlash: true,"] : [],
1942
+ ...d.mcpPath && (actions.length > 0 || d.login) ? [` mcpPath: ${j(d.mcpPath)},`] : [],
1943
+ " routes: [",
1944
+ ...routes,
1945
+ " ],",
1946
+ ...actions.length > 0 ? [" actions: [", ...actions, " ],"] : [],
1947
+ ...pages.length > 0 ? [" pages: [", ...pages, " ],"] : [],
1948
+ ...d.login ? [` login: ${j(manifestLogin(d.login))},`] : [],
1949
+ ...opts.authStacks?.length ? [` authStacks: ${j(opts.authStacks)},`] : [],
1950
+ ` docs: ${j(opts.docs)},`,
1951
+ ...Object.keys(opts.descriptions).length > 0 ? [` descriptions: ${JSON.stringify(opts.descriptions, null, 2).replace(/\n/g, "\n ")},`] : [],
1952
+ "};",
1953
+ ""
1954
+ ];
1955
+ return lines.join("\n");
1956
+ }
1957
+ function generate(root, opts = {}, warn = (m) => console.warn(`[exposify] ${m}`)) {
1958
+ const outFile = opts.out ?? defaultOutFile(root);
1959
+ const discovery = discover(root, opts);
1960
+ const pkg = readJson((0, import_node_path6.join)(root, "package.json"));
1961
+ const info = { name: typeof pkg?.name === "string" ? pkg.name : "next-app", version: typeof pkg?.version === "string" ? pkg.version : "0.0.0" };
1962
+ const content = renderManifest(discovery, {
1963
+ outFile,
1964
+ docs: readDocs(root, opts.docs ?? DEFAULT_DOCS),
1965
+ descriptions: readDescriptions(root, warn),
1966
+ info,
1967
+ ...opts.basePath ? { basePath: opts.basePath } : {},
1968
+ ...opts.trailingSlash ? { trailingSlash: true } : {},
1969
+ authStacks: (0, import_core2.authStacksOf)((0, import_core2.dependencyNames)(pkg)).map((s) => s.pkg)
1970
+ });
1971
+ for (const a of discovery.actions.filter((x) => x.proposed?.status === "stale")) {
1972
+ warn(`the input accepted for the ${a.action.name} server action (${a.file}) is out of date, since the action changed: it's not used until \`exposify-next describe\` reviews it again`);
1973
+ }
1974
+ if (discovery.actions.length > 0 && !discovery.mcpPath) {
1975
+ warn(
1976
+ discovery.mcpRoute ? `found ${discovery.actions.length} Server Action(s), but the MCP route (${discovery.mcpRoute.file}) isn't a catch-all route, which they're served under: move it to app/mcp/[[...transport]]/route.ts` : `found ${discovery.actions.length} Server Action(s), but no MCP route to serve them under: create app/mcp/[[...transport]]/route.ts (npx exposify-next setup)`
1977
+ );
1978
+ }
1979
+ const file = (0, import_node_path6.join)(root, outFile);
1980
+ let current;
1981
+ try {
1982
+ current = (0, import_node_fs6.readFileSync)(file, "utf8");
1983
+ } catch {
1984
+ current = void 0;
1985
+ }
1986
+ if (current !== content) (0, import_node_fs6.writeFileSync)(file, content);
1987
+ return { file, changed: current !== content, discovery };
1988
+ }
1989
+
1990
+ // src/config.ts
1991
+ var PHASE_DEV = "phase-development-server";
1992
+ var PHASE_BUILD = "phase-production-build";
1993
+ var WATCHING = /* @__PURE__ */ Symbol.for("exposify.next.watching");
1994
+ function withExposify(nextConfig = {}, options = {}) {
1995
+ return async (phase, ctx) => {
1996
+ const config = typeof nextConfig === "function" ? await nextConfig(phase, ctx) : nextConfig;
1997
+ if (phase !== PHASE_DEV && phase !== PHASE_BUILD) return config;
1998
+ const root = options.root ?? process.cwd();
1999
+ const c = config;
2000
+ const opts = {
2001
+ ...options.out ? { out: options.out } : {},
2002
+ ...options.docs ? { docs: options.docs } : {},
2003
+ ...c.pageExtensions ? { pageExtensions: c.pageExtensions } : {},
2004
+ ...c.basePath ? { basePath: c.basePath } : {},
2005
+ ...c.trailingSlash ? { trailingSlash: true } : {}
2006
+ };
2007
+ const run = () => {
2008
+ try {
2009
+ generate(root, opts);
2010
+ } catch (err) {
2011
+ console.error(`[exposify] couldn't generate the route table: ${err.message}`);
2012
+ }
2013
+ };
2014
+ run();
2015
+ if (phase === PHASE_DEV) watchRoutes(root, run);
2016
+ return config;
2017
+ };
2018
+ }
2019
+ function watchRoutes(root, run) {
2020
+ const g = globalThis;
2021
+ if (g[WATCHING]) return;
2022
+ g[WATCHING] = true;
2023
+ let timer;
2024
+ const schedule = () => {
2025
+ clearTimeout(timer);
2026
+ timer = setTimeout(run, 150);
2027
+ };
2028
+ for (const dir of ["app", "pages", "src", "lib", "actions", "server"]) {
2029
+ if (!(0, import_node_fs7.existsSync)((0, import_node_path7.join)(root, dir))) continue;
2030
+ try {
2031
+ (0, import_node_fs7.watch)((0, import_node_path7.join)(root, dir), { recursive: true }, schedule).unref();
2032
+ } catch {
2033
+ }
2034
+ }
2035
+ try {
2036
+ (0, import_node_fs7.watch)(root, (_event, name) => {
2037
+ if (name === import_core3.DESCRIPTIONS_FILE) schedule();
2038
+ }).unref();
2039
+ } catch {
2040
+ }
2041
+ }
2042
+ // Annotate the CommonJS export names for ESM import in node:
2043
+ 0 && (module.exports = {
2044
+ generate,
2045
+ withExposify
2046
+ });