@exposify/next 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -4
- package/dist/config.cjs +365 -94
- package/dist/config.d.cts +49 -3
- package/dist/config.d.ts +49 -3
- package/dist/config.js +359 -88
- package/dist/exposify-next.js +552 -118
- package/dist/index.cjs +190 -43
- package/dist/index.d.cts +40 -120
- package/dist/index.d.ts +40 -120
- package/dist/index.js +196 -43
- package/dist/manifest-DZQCvzQy.d.cts +176 -0
- package/dist/manifest-DZQCvzQy.d.ts +176 -0
- package/package.json +3 -3
- package/dist/fields-olvyFnxj.d.cts +0 -23
- package/dist/fields-olvyFnxj.d.ts +0 -23
package/dist/config.js
CHANGED
|
@@ -7,13 +7,13 @@ import {
|
|
|
7
7
|
|
|
8
8
|
// src/config.ts
|
|
9
9
|
import { existsSync as existsSync3, watch } from "fs";
|
|
10
|
-
import { join as
|
|
10
|
+
import { join as join8 } from "path";
|
|
11
11
|
import { DESCRIPTIONS_FILE as DESCRIPTIONS_FILE2 } from "@exposify/core";
|
|
12
12
|
|
|
13
13
|
// src/codegen.ts
|
|
14
14
|
import { createHash } from "crypto";
|
|
15
|
-
import { existsSync as existsSync2, readFileSync as
|
|
16
|
-
import { dirname as dirname2, join as
|
|
15
|
+
import { existsSync as existsSync2, readFileSync as readFileSync7, readdirSync, writeFileSync } from "fs";
|
|
16
|
+
import { dirname as dirname2, join as join7, relative as relative2, sep as sep2 } from "path";
|
|
17
17
|
import { DESCRIPTIONS_FILE, authStacksOf, dependencyNames, parseDescriptions } from "@exposify/core";
|
|
18
18
|
import { globSync } from "tinyglobby";
|
|
19
19
|
|
|
@@ -342,8 +342,8 @@ function readsOf(fn, formNames, st, depth) {
|
|
|
342
342
|
st.readsAll = true;
|
|
343
343
|
const decl = [...parents].reverse().find((p) => !["TSAsExpression", "TSSatisfiesExpression", "ParenthesizedExpression"].includes(p.type));
|
|
344
344
|
if (decl?.type === "VariableDeclarator" && decl.id.type === "ObjectPattern") {
|
|
345
|
-
for (const
|
|
346
|
-
const key =
|
|
345
|
+
for (const prop2 of decl.id.properties) {
|
|
346
|
+
const key = prop2.type === "ObjectProperty" ? keyName(prop2.key) : void 0;
|
|
347
347
|
if (key) addField(st, key, {}, false);
|
|
348
348
|
}
|
|
349
349
|
}
|
|
@@ -385,19 +385,19 @@ function analyzeFields(fn, roles, ctx) {
|
|
|
385
385
|
if (literal?.type === "TSTypeLiteral") {
|
|
386
386
|
for (const m of literal.members) if (m.type === "TSPropertySignature" && keyName(m.key)) members.set(keyName(m.key), m);
|
|
387
387
|
}
|
|
388
|
-
for (const
|
|
389
|
-
const key =
|
|
388
|
+
for (const prop2 of id.properties) {
|
|
389
|
+
const key = prop2.type === "ObjectProperty" ? keyName(prop2.key) : void 0;
|
|
390
390
|
if (!key) {
|
|
391
|
-
st.unresolved.push(`line ${lineOf(
|
|
391
|
+
st.unresolved.push(`line ${lineOf(prop2)}: a rest element in parameter ${role.name}`);
|
|
392
392
|
continue;
|
|
393
393
|
}
|
|
394
394
|
const member = members.get(key);
|
|
395
395
|
const hint = annotated(member?.typeAnnotation?.typeAnnotation);
|
|
396
396
|
if (!hint) {
|
|
397
|
-
st.unresolved.push(`line ${lineOf(
|
|
397
|
+
st.unresolved.push(`line ${lineOf(prop2)}: ${key} has no type it can read (string, number, boolean or a union of strings)`);
|
|
398
398
|
continue;
|
|
399
399
|
}
|
|
400
|
-
st.fields.set(key, { name: key, type: hint.type, ...hint.enum ? { enum: hint.enum } : {}, ...member?.optional ||
|
|
400
|
+
st.fields.set(key, { name: key, type: hint.type, ...hint.enum ? { enum: hint.enum } : {}, ...member?.optional || prop2.value.type === "AssignmentPattern" ? {} : { required: true } });
|
|
401
401
|
}
|
|
402
402
|
});
|
|
403
403
|
return { fields: [...st.fields.values()], readsAll: st.readsAll, unresolved: st.unresolved };
|
|
@@ -475,20 +475,20 @@ function renderedAttributes(el, fn, use, mod) {
|
|
|
475
475
|
for (const p of pattern.properties) {
|
|
476
476
|
if (p.type === "RestElement" && p.argument.type === "Identifier") restName = p.argument.name;
|
|
477
477
|
if (p.type !== "ObjectProperty") continue;
|
|
478
|
-
const
|
|
478
|
+
const prop2 = keyName(p.key);
|
|
479
479
|
const value = p.value.type === "AssignmentPattern" ? p.value.left : p.value;
|
|
480
|
-
if (!
|
|
480
|
+
if (!prop2 || value.type !== "Identifier") continue;
|
|
481
481
|
const fallback = p.value.type === "AssignmentPattern" ? attrValue({ type: "JSXAttribute", value: { type: "JSXExpressionContainer", expression: p.value.right } }, [], mod.scope) : void 0;
|
|
482
|
-
locals.set(value.name, { prop, ...fallback ? { fallback } : {} });
|
|
482
|
+
locals.set(value.name, { prop: prop2, ...fallback ? { fallback } : {} });
|
|
483
483
|
}
|
|
484
484
|
}
|
|
485
|
-
const
|
|
485
|
+
const named = new Set([...locals.values()].map((l) => l.prop));
|
|
486
486
|
const out = /* @__PURE__ */ new Map();
|
|
487
487
|
for (const a of el.openingElement.attributes) {
|
|
488
488
|
if (a.type === "JSXSpreadAttribute") {
|
|
489
489
|
const arg = unwrapTs(a.argument);
|
|
490
490
|
if (arg?.type === "Identifier" && (arg.name === restName || arg.name === propsName)) {
|
|
491
|
-
for (const [k, v] of use) if (arg.name === propsName || !
|
|
491
|
+
for (const [k, v] of use) if (arg.name === propsName || !named.has(k)) out.set(k, v);
|
|
492
492
|
}
|
|
493
493
|
continue;
|
|
494
494
|
}
|
|
@@ -496,14 +496,14 @@ function renderedAttributes(el, fn, use, mod) {
|
|
|
496
496
|
if (!key) continue;
|
|
497
497
|
const expr = a.value?.type === "JSXExpressionContainer" ? unwrapTs(a.value.expression) : void 0;
|
|
498
498
|
if (expr?.type === "Identifier" && locals.has(expr.name)) {
|
|
499
|
-
const { prop, fallback } = locals.get(expr.name);
|
|
500
|
-
const v = use.has(
|
|
499
|
+
const { prop: prop2, fallback } = locals.get(expr.name);
|
|
500
|
+
const v = use.has(prop2) ? use.get(prop2) : fallback;
|
|
501
501
|
if (v !== void 0) out.set(key, v);
|
|
502
502
|
continue;
|
|
503
503
|
}
|
|
504
504
|
if (expr?.type === "MemberExpression" && unwrapTs(expr.object)?.type === "Identifier" && unwrapTs(expr.object).name === propsName) {
|
|
505
|
-
const
|
|
506
|
-
if (
|
|
505
|
+
const prop2 = keyName(expr.property);
|
|
506
|
+
if (prop2 && use.has(prop2)) out.set(key, use.get(prop2));
|
|
507
507
|
continue;
|
|
508
508
|
}
|
|
509
509
|
out.set(key, attrValue(a, [], mod.scope));
|
|
@@ -850,8 +850,8 @@ function actionHosts(pages, graph, actionExports) {
|
|
|
850
850
|
// src/login.ts
|
|
851
851
|
import { readFileSync as readFileSync2 } from "fs";
|
|
852
852
|
import { join as join2 } from "path";
|
|
853
|
-
import { LOGIN_NAME, NOT_A_LOGIN, loginFields } from "@exposify/core";
|
|
854
|
-
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;
|
|
853
|
+
import { LOGIN_NAME, NOT_A_LOGIN, NOT_A_VERIFY, START_NAME, VERIFY_NAME, loginFields, startFields, verifyFields } from "@exposify/core";
|
|
854
|
+
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;
|
|
855
855
|
function routeBodyFields(source) {
|
|
856
856
|
const names = /* @__PURE__ */ new Set();
|
|
857
857
|
let body = null;
|
|
@@ -872,16 +872,27 @@ function routeBodyFields(source) {
|
|
|
872
872
|
}
|
|
873
873
|
return names.size && body ? { fields: [...names], body } : null;
|
|
874
874
|
}
|
|
875
|
-
|
|
876
|
-
|
|
875
|
+
var LOGIN_RULE = { not: NOT_A_LOGIN, name: LOGIN_NAME, mustBeNamed: false, fields: (n) => loginFields(n), session: true };
|
|
876
|
+
var VERIFY_RULE = { not: NOT_A_VERIFY, name: VERIFY_NAME, mustBeNamed: true, fields: (n, named) => verifyFields(n, named), session: true };
|
|
877
|
+
var START_RULE = { not: NOT_A_LOGIN, name: new RegExp(`${LOGIN_NAME.source}|${START_NAME.source}`, "i"), mustBeNamed: true, fields: (n) => startFields(n), session: false };
|
|
878
|
+
var knownKey = (l) => l.kind === "action" ? `${l.file}#${l.export}` : l.file;
|
|
879
|
+
function actionCandidate(a, rule, known) {
|
|
880
|
+
if (rule.not.test(a.key) || rule.not.test(a.action.name)) return null;
|
|
877
881
|
if (!a.action.params.some((p) => p.role === "formData" || p.role === "object")) return null;
|
|
878
|
-
const
|
|
882
|
+
const isNamed = rule.name.test(a.action.name);
|
|
883
|
+
if (rule.mustBeNamed && !isNamed) return null;
|
|
884
|
+
const fields = rule.fields(
|
|
885
|
+
a.action.fields.map((f) => f.name),
|
|
886
|
+
isNamed
|
|
887
|
+
);
|
|
879
888
|
if (!fields) return null;
|
|
880
|
-
if (!a.action.setsCookies && !LOGIN_NAME.test(a.action.name)) return null;
|
|
889
|
+
if (rule.session && !a.action.setsCookies && !LOGIN_NAME.test(a.action.name) && !(rule === LOGIN_RULE && known.has(`${a.file}#${a.action.name}`))) return null;
|
|
881
890
|
return { kind: "action", key: a.key, export: a.action.name, file: a.file, fields, ...a.host ? { host: a.host } : {} };
|
|
882
891
|
}
|
|
883
|
-
function routeCandidate(root, r) {
|
|
884
|
-
if (r.router !== "app" || !r.module.methods.includes("POST") || r.params.length > 0 ||
|
|
892
|
+
function routeCandidate(root, r, rule, known) {
|
|
893
|
+
if (r.router !== "app" || !r.module.methods.includes("POST") || r.params.length > 0 || rule.not.test(r.path)) return null;
|
|
894
|
+
const isNamed = rule.name.test(r.path);
|
|
895
|
+
if (rule.mustBeNamed && !isNamed) return null;
|
|
885
896
|
let source;
|
|
886
897
|
try {
|
|
887
898
|
source = readFileSync2(join2(root, r.file), "utf8");
|
|
@@ -889,26 +900,41 @@ function routeCandidate(root, r) {
|
|
|
889
900
|
return null;
|
|
890
901
|
}
|
|
891
902
|
const read2 = routeBodyFields(source);
|
|
892
|
-
const fields = read2 ?
|
|
903
|
+
const fields = read2 ? rule.fields(read2.fields, isNamed) : null;
|
|
893
904
|
if (!read2 || !fields) return null;
|
|
894
|
-
if (!LOGIN_NAME.test(r.path) && !SESSION_CODE.test(source)) return null;
|
|
905
|
+
if (rule.session && !LOGIN_NAME.test(r.path) && !SESSION_CODE.test(source) && !(rule === LOGIN_RULE && known.has(r.file))) return null;
|
|
895
906
|
return { kind: "route", path: r.path, file: r.file, fields, body: read2.body };
|
|
896
907
|
}
|
|
897
|
-
var
|
|
898
|
-
function
|
|
899
|
-
const
|
|
908
|
+
var nameOf = (l) => l.kind === "action" ? l.export : l.path;
|
|
909
|
+
function candidates(d, rule, known) {
|
|
910
|
+
const out = [];
|
|
900
911
|
for (const a of d.actions) {
|
|
901
|
-
const c = actionCandidate(a);
|
|
902
|
-
if (c)
|
|
912
|
+
const c = actionCandidate(a, rule, known);
|
|
913
|
+
if (c) out.push(c);
|
|
903
914
|
}
|
|
904
915
|
for (const r of d.routes) {
|
|
905
|
-
const c = routeCandidate(d.root, r);
|
|
906
|
-
if (c)
|
|
916
|
+
const c = routeCandidate(d.root, r, rule, known);
|
|
917
|
+
if (c) out.push(c);
|
|
918
|
+
}
|
|
919
|
+
const score = (l) => (rule === LOGIN_RULE && known.has(knownKey(l)) ? 0 : 8) + (rule.name.test(nameOf(l)) ? 0 : 4) + (l.kind === "action" && l.host ? 0 : 2) + l.fields.length / 100;
|
|
920
|
+
return out.sort((a, b) => score(a) - score(b));
|
|
921
|
+
}
|
|
922
|
+
function findLogin(d, known = /* @__PURE__ */ new Set()) {
|
|
923
|
+
const verifies = candidates(d, VERIFY_RULE, known);
|
|
924
|
+
const logins = candidates(d, LOGIN_RULE, known);
|
|
925
|
+
if (logins.length > 0) {
|
|
926
|
+
const [first2, ...others2] = logins;
|
|
927
|
+
return { login: { ...first2, ...verifies[0] ? { verify: verifies[0] } : {} }, others: others2 };
|
|
928
|
+
}
|
|
929
|
+
const pairs = [];
|
|
930
|
+
for (const start of candidates(d, START_RULE, known)) {
|
|
931
|
+
const who = new Set(start.fields.filter((f) => f.kind === "identity").map((f) => f.name));
|
|
932
|
+
const verify = verifies.find((v) => v.fields.some((f) => who.has(f.name))) ?? verifies.find((v) => !v.fields.some((f) => f.kind === "identity"));
|
|
933
|
+
if (verify) pairs.push({ ...start, passwordless: true, verify });
|
|
907
934
|
}
|
|
908
|
-
if (
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
return { login: candidates[0], others: candidates.slice(1) };
|
|
935
|
+
if (pairs.length === 0) return null;
|
|
936
|
+
const [first, ...others] = pairs;
|
|
937
|
+
return { login: first, others };
|
|
912
938
|
}
|
|
913
939
|
|
|
914
940
|
// src/guards.ts
|
|
@@ -1090,9 +1116,9 @@ function pageSearchParams(program) {
|
|
|
1090
1116
|
if (!FUNCTION_TYPES.has(fn.type)) return;
|
|
1091
1117
|
const param = fn.params?.[0];
|
|
1092
1118
|
if (param?.type === "ObjectPattern") {
|
|
1093
|
-
for (const
|
|
1094
|
-
if (
|
|
1095
|
-
const value =
|
|
1119
|
+
for (const prop2 of param.properties) {
|
|
1120
|
+
if (prop2.type !== "ObjectProperty" || keyName(prop2.key) !== "searchParams") continue;
|
|
1121
|
+
const value = prop2.value.type === "AssignmentPattern" ? prop2.value.left : prop2.value;
|
|
1096
1122
|
if (value.type === "Identifier") sources.add(value.name);
|
|
1097
1123
|
for (const k of patternKeys(value)) names.add(k);
|
|
1098
1124
|
}
|
|
@@ -1159,10 +1185,10 @@ function staticMetadata(program) {
|
|
|
1159
1185
|
const init = unwrapTs(d.init);
|
|
1160
1186
|
if (d.id?.type !== "Identifier" || d.id.name !== "metadata" || init?.type !== "ObjectExpression") continue;
|
|
1161
1187
|
const out = {};
|
|
1162
|
-
for (const
|
|
1163
|
-
if (
|
|
1164
|
-
const key = keyName(
|
|
1165
|
-
const value = unwrapTs(
|
|
1188
|
+
for (const prop2 of init.properties) {
|
|
1189
|
+
if (prop2.type !== "ObjectProperty") continue;
|
|
1190
|
+
const key = keyName(prop2.key);
|
|
1191
|
+
const value = unwrapTs(prop2.value);
|
|
1166
1192
|
if (key === "description") {
|
|
1167
1193
|
const s = stringValue(value);
|
|
1168
1194
|
if (s) out.description = s;
|
|
@@ -1265,15 +1291,15 @@ function readTsconfig(root) {
|
|
|
1265
1291
|
}
|
|
1266
1292
|
}
|
|
1267
1293
|
function asFile(path) {
|
|
1268
|
-
const
|
|
1269
|
-
if (/\.[mc]?jsx?$/.test(path))
|
|
1270
|
-
return
|
|
1294
|
+
const candidates2 = [path, ...EXTENSIONS.map((e) => path + e), ...EXTENSIONS.map((e) => join5(path, `index${e}`))];
|
|
1295
|
+
if (/\.[mc]?jsx?$/.test(path)) candidates2.push(...EXTENSIONS.map((e) => path.replace(/\.[mc]?jsx?$/, e)));
|
|
1296
|
+
return candidates2.find((c) => existsSync(c) && statSync(c).isFile());
|
|
1271
1297
|
}
|
|
1272
1298
|
function moduleLoader(root) {
|
|
1273
1299
|
const { baseUrl, paths } = readTsconfig(root);
|
|
1274
1300
|
const base = join5(root, baseUrl ?? ".");
|
|
1275
1301
|
const cache = /* @__PURE__ */ new Map();
|
|
1276
|
-
const
|
|
1302
|
+
const candidates2 = (specifier2, fromFile) => {
|
|
1277
1303
|
if (specifier2.startsWith(".")) return [resolve(root, dirname(fromFile), specifier2)];
|
|
1278
1304
|
for (const [pattern, targets] of Object.entries(paths ?? {})) {
|
|
1279
1305
|
const star = pattern.indexOf("*");
|
|
@@ -1290,7 +1316,7 @@ function moduleLoader(root) {
|
|
|
1290
1316
|
const key = `${fromFile}\0${specifier2}`;
|
|
1291
1317
|
if (cache.has(key)) return cache.get(key);
|
|
1292
1318
|
let found;
|
|
1293
|
-
for (const candidate of
|
|
1319
|
+
for (const candidate of candidates2(specifier2, fromFile)) {
|
|
1294
1320
|
const path = asFile(candidate);
|
|
1295
1321
|
const rel = path && relative(root, path);
|
|
1296
1322
|
if (!path || !rel || rel.startsWith("..") || rel.split(sep).includes("node_modules")) continue;
|
|
@@ -1322,11 +1348,11 @@ function writtenInput(n) {
|
|
|
1322
1348
|
n = unwrapTs(n);
|
|
1323
1349
|
if (n?.type !== "ObjectExpression") return "opaque";
|
|
1324
1350
|
const out = {};
|
|
1325
|
-
for (const
|
|
1326
|
-
const key =
|
|
1351
|
+
for (const prop2 of n.properties) {
|
|
1352
|
+
const key = prop2.type === "ObjectProperty" ? keyName(prop2.key) : void 0;
|
|
1327
1353
|
if (!key) continue;
|
|
1328
1354
|
let values;
|
|
1329
|
-
walk(
|
|
1355
|
+
walk(prop2.value, (c) => {
|
|
1330
1356
|
if (values || c.type !== "CallExpression" || keyName(c.callee?.property) !== "enum") return;
|
|
1331
1357
|
const list = unwrapTs(c.arguments[0]);
|
|
1332
1358
|
const items = list?.type === "ArrayExpression" ? list.elements.map((e) => stringValue(e)) : [];
|
|
@@ -1346,13 +1372,13 @@ function markerOf(init, imports) {
|
|
|
1346
1372
|
const meta = call.arguments[1];
|
|
1347
1373
|
const marker = { kind: "described" };
|
|
1348
1374
|
if (meta?.type === "ObjectExpression") {
|
|
1349
|
-
for (const
|
|
1350
|
-
if (
|
|
1351
|
-
const key = keyName(
|
|
1352
|
-
const value = stringValue(
|
|
1375
|
+
for (const prop2 of meta.properties) {
|
|
1376
|
+
if (prop2.type !== "ObjectProperty") continue;
|
|
1377
|
+
const key = keyName(prop2.key);
|
|
1378
|
+
const value = stringValue(prop2.value);
|
|
1353
1379
|
if (key === "name" && value) marker.name = value;
|
|
1354
1380
|
if (key === "description" && value) marker.description = value;
|
|
1355
|
-
if (key === "input") marker.input = writtenInput(
|
|
1381
|
+
if (key === "input") marker.input = writtenInput(prop2.value);
|
|
1356
1382
|
}
|
|
1357
1383
|
}
|
|
1358
1384
|
return { marker, handler };
|
|
@@ -1623,18 +1649,241 @@ function findInlineActions(source, file) {
|
|
|
1623
1649
|
const program = parseModule(source, file);
|
|
1624
1650
|
if (!program) return [];
|
|
1625
1651
|
const out = [];
|
|
1626
|
-
const
|
|
1652
|
+
const named = /* @__PURE__ */ new Map();
|
|
1627
1653
|
walk(program, (n) => {
|
|
1628
|
-
if (n.type === "VariableDeclarator" && n.id.type === "Identifier" && n.init)
|
|
1654
|
+
if (n.type === "VariableDeclarator" && n.id.type === "Identifier" && n.init) named.set(unwrapTs(n.init), n.id.name);
|
|
1629
1655
|
if (!FUNCTION_TYPES.has(n.type) || n.body?.type !== "BlockStatement") return;
|
|
1630
1656
|
if (!n.body.directives.some((d) => d.value?.value === "use server")) return;
|
|
1631
1657
|
if (isActionModule(program) && program.body.some((s) => s === n || s.declaration === n || s.declaration?.declarations?.some((d) => unwrapTs(d.init) === n))) return;
|
|
1632
|
-
const name = n.id?.name ??
|
|
1658
|
+
const name = n.id?.name ?? named.get(n);
|
|
1633
1659
|
out.push({ ...name ? { name } : {}, line: n.loc?.start.line ?? 1 });
|
|
1634
1660
|
});
|
|
1635
1661
|
return out;
|
|
1636
1662
|
}
|
|
1637
1663
|
|
|
1664
|
+
// src/supabase.ts
|
|
1665
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
1666
|
+
import { join as join6 } from "path";
|
|
1667
|
+
var AUTH_CALLS = {
|
|
1668
|
+
signInWithPassword: "password",
|
|
1669
|
+
signInWithOtp: "email-code",
|
|
1670
|
+
verifyOtp: "email-code",
|
|
1671
|
+
signInWithOAuth: "social",
|
|
1672
|
+
signInWithIdToken: "other",
|
|
1673
|
+
signInWithSSO: "other",
|
|
1674
|
+
signInAnonymously: "other",
|
|
1675
|
+
signInWithWeb3: "other"
|
|
1676
|
+
};
|
|
1677
|
+
var MFA_CALLS = /* @__PURE__ */ new Set(["challenge", "verify", "challengeAndVerify"]);
|
|
1678
|
+
var APP_SERVER_FILE = /^(page|layout|template|route|default|loading|not-found|forbidden|unauthorized|error|global-error)\.[^.]+$/;
|
|
1679
|
+
var MIDDLEWARE_FILE = /^(src\/)?(middleware|proxy|instrumentation)\.[^./]+$/;
|
|
1680
|
+
var directive2 = (program) => program.directives?.[0]?.value?.value;
|
|
1681
|
+
var baseName = (f) => f.slice(f.lastIndexOf("/") + 1);
|
|
1682
|
+
function isAuthObject(n) {
|
|
1683
|
+
n = unwrapTs(n);
|
|
1684
|
+
if (n?.type === "Identifier") return n.name === "auth";
|
|
1685
|
+
return (n?.type === "MemberExpression" || n?.type === "OptionalMemberExpression") && keyName(n.property) === "auth" && !n.computed;
|
|
1686
|
+
}
|
|
1687
|
+
var propertyOf = (n) => n.computed ? stringValue(n.property) : keyName(n.property);
|
|
1688
|
+
function prop(obj, name) {
|
|
1689
|
+
obj = unwrapTs(obj);
|
|
1690
|
+
if (obj?.type !== "ObjectExpression") return void 0;
|
|
1691
|
+
return obj.properties.find((p) => p.type === "ObjectProperty" && keyName(p.key) === name)?.value;
|
|
1692
|
+
}
|
|
1693
|
+
function findSupabase(root, files, graph, dependencies, dirs) {
|
|
1694
|
+
const ssr = dependencies.includes("@supabase/ssr");
|
|
1695
|
+
if (!ssr) return dependencies.includes("@supabase/auth-helpers-nextjs") ? { authHelpers: true, signIns: [], callbacks: [] } : null;
|
|
1696
|
+
const facts = /* @__PURE__ */ new Map();
|
|
1697
|
+
const factsOf = (file) => {
|
|
1698
|
+
if (facts.has(file)) return facts.get(file);
|
|
1699
|
+
let found = null;
|
|
1700
|
+
try {
|
|
1701
|
+
const source = readFileSync6(join6(root, file), "utf8");
|
|
1702
|
+
const program = parseModule(source, file);
|
|
1703
|
+
if (program) {
|
|
1704
|
+
const d = directive2(program);
|
|
1705
|
+
found = {
|
|
1706
|
+
program,
|
|
1707
|
+
...d ? { directive: d } : {},
|
|
1708
|
+
serverClient: /\bcreateServerClient\s*\(|from\s+["']next\/headers["']/.test(source),
|
|
1709
|
+
browserClient: /\bcreateBrowserClient\s*\(/.test(source)
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
} catch {
|
|
1713
|
+
found = null;
|
|
1714
|
+
}
|
|
1715
|
+
facts.set(file, found);
|
|
1716
|
+
return found;
|
|
1717
|
+
};
|
|
1718
|
+
const isPagesPage = (f) => Boolean(dirs.pagesDir && f.startsWith(`${dirs.pagesDir}/`) && !f.startsWith(`${dirs.pagesDir}/api/`));
|
|
1719
|
+
const isPagesApi = (f) => Boolean(dirs.pagesDir && f.startsWith(`${dirs.pagesDir}/api/`));
|
|
1720
|
+
const isAppRoute = (f) => Boolean(dirs.appDir && f.startsWith(`${dirs.appDir}/`) && /^route\./.test(baseName(f)));
|
|
1721
|
+
const candidates2 = files.filter((f) => {
|
|
1722
|
+
try {
|
|
1723
|
+
return /\b(signIn\w*|verifyOtp|exchangeCodeForSession|mfa|auth-ui-react|storageKey|cookieOptions)\b/.test(readFileSync6(join6(root, f), "utf8"));
|
|
1724
|
+
} catch {
|
|
1725
|
+
return false;
|
|
1726
|
+
}
|
|
1727
|
+
});
|
|
1728
|
+
const clientRoots = [];
|
|
1729
|
+
const serverRoots = [];
|
|
1730
|
+
for (const f of files) {
|
|
1731
|
+
if (isPagesPage(f)) {
|
|
1732
|
+
clientRoots.push(f);
|
|
1733
|
+
continue;
|
|
1734
|
+
}
|
|
1735
|
+
const isEntry = isPagesApi(f) || MIDDLEWARE_FILE.test(f) || Boolean(dirs.appDir && f.startsWith(`${dirs.appDir}/`) && APP_SERVER_FILE.test(baseName(f)));
|
|
1736
|
+
let text2;
|
|
1737
|
+
try {
|
|
1738
|
+
text2 = readFileSync6(join6(root, f), "utf8");
|
|
1739
|
+
} catch {
|
|
1740
|
+
continue;
|
|
1741
|
+
}
|
|
1742
|
+
const head = /^\s*(?:\/\/[^\n]*\n|\/\*[\s\S]*?\*\/\s*)*\s*["']use (client|server)["']/.exec(text2)?.[1];
|
|
1743
|
+
if (head === "client") clientRoots.push(f);
|
|
1744
|
+
else if (head === "server" || isEntry) serverRoots.push(f);
|
|
1745
|
+
}
|
|
1746
|
+
const browserSet = graph.reachable(clientRoots);
|
|
1747
|
+
const serverSet = /* @__PURE__ */ new Set();
|
|
1748
|
+
const queue = [...serverRoots];
|
|
1749
|
+
while (queue.length > 0 && serverSet.size < 2e3) {
|
|
1750
|
+
const f = queue.shift();
|
|
1751
|
+
if (serverSet.has(f)) continue;
|
|
1752
|
+
if (!serverRoots.includes(f) && factsOf(f)?.directive === "use client") continue;
|
|
1753
|
+
serverSet.add(f);
|
|
1754
|
+
for (const e of graph.edges(f)) if (!serverSet.has(e.file)) queue.push(e.file);
|
|
1755
|
+
}
|
|
1756
|
+
const sideOf = (file, inServerFn) => {
|
|
1757
|
+
const f = factsOf(file);
|
|
1758
|
+
if (inServerFn || f?.directive === "use server") return { side: "server", why: "a Server Action" };
|
|
1759
|
+
if (isAppRoute(file) || isPagesApi(file)) return { side: "server", why: "a route handler" };
|
|
1760
|
+
if (f?.directive === "use client") return { side: "browser", why: '"use client"' };
|
|
1761
|
+
if (isPagesPage(file)) return { side: "browser", why: "a Pages Router page" };
|
|
1762
|
+
let server = f?.serverClient ?? false;
|
|
1763
|
+
let browser = f?.browserClient ?? false;
|
|
1764
|
+
let helper;
|
|
1765
|
+
if (!server && !browser) {
|
|
1766
|
+
for (const e of graph.edges(file)) {
|
|
1767
|
+
const g = factsOf(e.file);
|
|
1768
|
+
if (g?.serverClient && !g.browserClient) {
|
|
1769
|
+
server = true;
|
|
1770
|
+
helper = e.file;
|
|
1771
|
+
}
|
|
1772
|
+
if (g?.browserClient && !g.serverClient) {
|
|
1773
|
+
browser = true;
|
|
1774
|
+
helper = e.file;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
if (server !== browser) return { side: server ? "server" : "browser", why: `uses the ${server ? "server" : "browser"} client${helper ? ` from ${helper}` : ""}` };
|
|
1779
|
+
const inBrowser = browserSet.has(file);
|
|
1780
|
+
const inServer = serverSet.has(file);
|
|
1781
|
+
if (inBrowser && !inServer) return { side: "browser", why: "imported by browser code" };
|
|
1782
|
+
if (inServer && !inBrowser) return { side: "server", why: "imported by server code" };
|
|
1783
|
+
return { side: "unknown", why: inBrowser ? "imported by both browser and server code" : "not imported by a page or route we could follow" };
|
|
1784
|
+
};
|
|
1785
|
+
const out = { signIns: [], callbacks: [] };
|
|
1786
|
+
for (const file of candidates2) {
|
|
1787
|
+
const f = factsOf(file);
|
|
1788
|
+
if (!f) continue;
|
|
1789
|
+
const top = f.program;
|
|
1790
|
+
const topName = (n) => {
|
|
1791
|
+
const d = n.type === "ExportNamedDeclaration" ? n.declaration : n.type === "ExportDefaultDeclaration" ? n.declaration : n;
|
|
1792
|
+
if (n.type === "ExportDefaultDeclaration") return "default";
|
|
1793
|
+
if (d?.type === "FunctionDeclaration") return d.id?.name;
|
|
1794
|
+
if (d?.type === "VariableDeclaration") return keyName(d.declarations?.[0]?.id);
|
|
1795
|
+
return void 0;
|
|
1796
|
+
};
|
|
1797
|
+
let authUi;
|
|
1798
|
+
walk(top, (n, parents) => {
|
|
1799
|
+
if (n.type === "ImportDeclaration" && n.source?.value === "@supabase/auth-ui-react") {
|
|
1800
|
+
authUi = n.specifiers.find((s) => s.type === "ImportSpecifier" && keyName(s.imported) === "Auth")?.local?.name;
|
|
1801
|
+
}
|
|
1802
|
+
if (n.type === "ObjectProperty" && !out.cookieName) {
|
|
1803
|
+
const key = keyName(n.key);
|
|
1804
|
+
const value = stringValue(unwrapTs(n.value));
|
|
1805
|
+
const parentProp = parents.at(-2);
|
|
1806
|
+
if (key === "storageKey" && value) out.cookieName = value;
|
|
1807
|
+
if (key === "name" && value && parentProp?.type === "ObjectProperty" && keyName(parentProp.key) === "cookieOptions") out.cookieName = value;
|
|
1808
|
+
}
|
|
1809
|
+
const push = (call, method2, extra = {}) => {
|
|
1810
|
+
const inServerFn = parents.some((p) => FUNCTION_TYPES.has(p.type) && p.body?.directives?.some((d) => d.value?.value === "use server"));
|
|
1811
|
+
const { side, why } = sideOf(file, inServerFn);
|
|
1812
|
+
const holder = parents.find((p) => top.body.includes(p));
|
|
1813
|
+
const name2 = holder ? topName(holder) : void 0;
|
|
1814
|
+
out.signIns.push({
|
|
1815
|
+
method: method2,
|
|
1816
|
+
call,
|
|
1817
|
+
...extra,
|
|
1818
|
+
file,
|
|
1819
|
+
line: n.loc?.start.line ?? 1,
|
|
1820
|
+
side,
|
|
1821
|
+
why,
|
|
1822
|
+
...side === "server" && name2 && (isActionModule(top) || inServerFn) ? { action: `${file}#${name2}` } : {},
|
|
1823
|
+
...side === "server" && (isAppRoute(file) || isPagesApi(file)) ? { route: file } : {}
|
|
1824
|
+
});
|
|
1825
|
+
};
|
|
1826
|
+
if (n.type === "JSXOpeningElement" && authUi && n.name?.type === "JSXIdentifier" && n.name.name === authUi) {
|
|
1827
|
+
push("<Auth />", "password");
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
if (n.type !== "CallExpression" && n.type !== "OptionalCallExpression") return;
|
|
1831
|
+
const callee = unwrapTs(n.callee);
|
|
1832
|
+
if (callee?.type !== "MemberExpression" && callee?.type !== "OptionalMemberExpression") return;
|
|
1833
|
+
const name = propertyOf(callee);
|
|
1834
|
+
if (!name) return;
|
|
1835
|
+
const object = unwrapTs(callee.object);
|
|
1836
|
+
if (MFA_CALLS.has(name) && (object?.type === "MemberExpression" || object?.type === "OptionalMemberExpression") && propertyOf(object) === "mfa" && isAuthObject(object.object)) {
|
|
1837
|
+
push(`mfa.${name}`, "totp");
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1840
|
+
if (!isAuthObject(object)) return;
|
|
1841
|
+
const arg = n.arguments?.[0];
|
|
1842
|
+
if (name === "exchangeCodeForSession" || name === "verifyOtp" && prop(arg, "token_hash")) {
|
|
1843
|
+
if (!out.callbacks.includes(file)) out.callbacks.push(file);
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
const method = Object.hasOwn(AUTH_CALLS, name) ? AUTH_CALLS[name] : void 0;
|
|
1847
|
+
if (!method) return;
|
|
1848
|
+
const provider = name === "signInWithOAuth" ? stringValue(unwrapTs(prop(arg, "provider"))) : void 0;
|
|
1849
|
+
push(name, method, provider ? { provider } : {});
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
return out;
|
|
1853
|
+
}
|
|
1854
|
+
function signInLabel(s) {
|
|
1855
|
+
const side = s.side === "unknown" ? "side unclear, counted as server" : s.side;
|
|
1856
|
+
return `${s.call}${s.provider ? ` (${s.provider})` : ""} in ${s.file}:${s.line} (${side}: ${s.why})`;
|
|
1857
|
+
}
|
|
1858
|
+
function supabaseChoice(found, login, loginPages2 = []) {
|
|
1859
|
+
const onServer = (method) => found.signIns.filter((s) => s.method === method && s.side !== "browser");
|
|
1860
|
+
const direct = [];
|
|
1861
|
+
const unusable = [];
|
|
1862
|
+
for (const method of ["password", "email-code"]) {
|
|
1863
|
+
const server = onServer(method);
|
|
1864
|
+
if (server.length === 0) direct.push(method);
|
|
1865
|
+
else if (!login) unusable.push(...server.map(signInLabel));
|
|
1866
|
+
}
|
|
1867
|
+
direct.push("social");
|
|
1868
|
+
return {
|
|
1869
|
+
...found.cookieName ? { cookieName: found.cookieName } : {},
|
|
1870
|
+
direct,
|
|
1871
|
+
found: found.signIns.map(signInLabel),
|
|
1872
|
+
...unusable.length ? { unusable } : {},
|
|
1873
|
+
...found.callbacks.length ? { callbacks: found.callbacks } : {},
|
|
1874
|
+
...loginPages2.length ? { loginPages: loginPages2 } : {}
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
function knownLogins(found) {
|
|
1878
|
+
const out = /* @__PURE__ */ new Set();
|
|
1879
|
+
for (const s of found?.signIns ?? []) {
|
|
1880
|
+
if (s.method !== "password" || s.side === "browser") continue;
|
|
1881
|
+
if (s.action) out.add(s.action);
|
|
1882
|
+
if (s.route) out.add(s.route);
|
|
1883
|
+
}
|
|
1884
|
+
return out;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1638
1887
|
// src/codegen.ts
|
|
1639
1888
|
var DEFAULT_DOCS = ["README.md", "docs/**/*.md"];
|
|
1640
1889
|
var actionRoute = (mcpPath, key) => `POST ${(mcpPath ?? "/mcp").replace(/\/$/, "")}/actions/${key}`;
|
|
@@ -1644,14 +1893,14 @@ function actionSignature(route, source) {
|
|
|
1644
1893
|
}
|
|
1645
1894
|
var posix = (p) => p.split(sep2).join("/");
|
|
1646
1895
|
function firstDir(root, names) {
|
|
1647
|
-
return names.find((n) => existsSync2(
|
|
1896
|
+
return names.find((n) => existsSync2(join7(root, n)));
|
|
1648
1897
|
}
|
|
1649
1898
|
function filesUnder(root, dir) {
|
|
1650
1899
|
const out = [];
|
|
1651
1900
|
const visit = (rel) => {
|
|
1652
1901
|
let entries;
|
|
1653
1902
|
try {
|
|
1654
|
-
entries = readdirSync(
|
|
1903
|
+
entries = readdirSync(join7(root, rel), { withFileTypes: true });
|
|
1655
1904
|
} catch {
|
|
1656
1905
|
return;
|
|
1657
1906
|
}
|
|
@@ -1678,7 +1927,7 @@ function discover(root, opts = {}) {
|
|
|
1678
1927
|
const dirSegs = file.slice(appDir.length + 1).split("/").slice(0, -1);
|
|
1679
1928
|
const route = appRoute(dirSegs);
|
|
1680
1929
|
if (!route) continue;
|
|
1681
|
-
const source =
|
|
1930
|
+
const source = readFileSync7(join7(root, file), "utf8");
|
|
1682
1931
|
const module = analyzeRouteModule(source, file);
|
|
1683
1932
|
if (module.mcpRoute) {
|
|
1684
1933
|
out.mcpRoute ??= { file, source };
|
|
@@ -1694,7 +1943,7 @@ function discover(root, opts = {}) {
|
|
|
1694
1943
|
const segs = file.slice(pagesDir.length + 1).replace(extPattern, "").split("/");
|
|
1695
1944
|
const route = pagesApiRoute(segs);
|
|
1696
1945
|
if (!route) continue;
|
|
1697
|
-
const source =
|
|
1946
|
+
const source = readFileSync7(join7(root, file), "utf8");
|
|
1698
1947
|
const src = defaultExportSource(source, file);
|
|
1699
1948
|
out.routes.push({ router: "pages", file, ...route, detected: detectPagesMethods(source, file), ...src ? { source: src } : {} });
|
|
1700
1949
|
}
|
|
@@ -1706,14 +1955,22 @@ function discover(root, opts = {}) {
|
|
|
1706
1955
|
const catchAll = route ? /^(.*)\/:[A-Za-z0-9_]+\*\??$/.exec(route.path) : null;
|
|
1707
1956
|
if (catchAll) out.mcpPath = catchAll[1] || "/";
|
|
1708
1957
|
}
|
|
1709
|
-
discoverActions(root, out, exts);
|
|
1710
|
-
const
|
|
1958
|
+
const { files, graph, pages } = discoverActions(root, out, exts);
|
|
1959
|
+
const supabase = findSupabase(root, files, graph, dependencyNames(readJson(join7(root, "package.json"))), out);
|
|
1960
|
+
const found = findLogin(out, knownLogins(supabase));
|
|
1711
1961
|
if (found) {
|
|
1712
1962
|
out.login = found.login;
|
|
1713
1963
|
out.loginOthers = found.others;
|
|
1714
1964
|
}
|
|
1965
|
+
if (supabase && !supabase.authHelpers) out.supabase = { found: supabase, choice: supabaseChoice(supabase, out.login, loginPages(supabase, pages, graph)) };
|
|
1966
|
+
else if (supabase) out.supabase = { found: supabase, choice: { direct: [], found: [] } };
|
|
1715
1967
|
return out;
|
|
1716
1968
|
}
|
|
1969
|
+
function loginPages(found, pages, graph) {
|
|
1970
|
+
const files = new Set(found.signIns.filter((s) => s.method === "password" || s.method === "email-code").map((s) => s.file));
|
|
1971
|
+
if (files.size === 0) return [];
|
|
1972
|
+
return pages.filter((p) => p.params.length === 0 && [...graph.reachable(p.roots)].some((f) => files.has(f))).map((p) => p.path);
|
|
1973
|
+
}
|
|
1717
1974
|
var SOURCE_GLOB = "**/*.{ts,tsx,js,jsx,mjs,cjs}";
|
|
1718
1975
|
var NOT_SOURCE = ["**/node_modules/**", ".next/**", "out/**", "build/**", "dist/**", "coverage/**", "**/*.d.ts"];
|
|
1719
1976
|
function snakeCase(name) {
|
|
@@ -1727,7 +1984,7 @@ function discoverActions(root, out, exts) {
|
|
|
1727
1984
|
for (const file of files) {
|
|
1728
1985
|
let source;
|
|
1729
1986
|
try {
|
|
1730
|
-
source =
|
|
1987
|
+
source = readFileSync7(join7(root, file), "utf8");
|
|
1731
1988
|
} catch {
|
|
1732
1989
|
continue;
|
|
1733
1990
|
}
|
|
@@ -1784,24 +2041,25 @@ function discoverActions(root, out, exts) {
|
|
|
1784
2041
|
const found2 = guards.action(a.file, a.action.name);
|
|
1785
2042
|
if (found2.length > 0) a.guards = found2;
|
|
1786
2043
|
}
|
|
2044
|
+
return { files, graph, pages };
|
|
1787
2045
|
}
|
|
1788
2046
|
function readJson(path) {
|
|
1789
2047
|
try {
|
|
1790
|
-
return JSON.parse(
|
|
2048
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
1791
2049
|
} catch {
|
|
1792
2050
|
return void 0;
|
|
1793
2051
|
}
|
|
1794
2052
|
}
|
|
1795
2053
|
function readProposedInputs(root) {
|
|
1796
|
-
const path =
|
|
2054
|
+
const path = join7(root, DESCRIPTIONS_FILE);
|
|
1797
2055
|
if (!existsSync2(path)) return {};
|
|
1798
|
-
const parsed = parseDescriptions(
|
|
2056
|
+
const parsed = parseDescriptions(readFileSync7(path, "utf8"));
|
|
1799
2057
|
return typeof parsed === "string" ? {} : parsed.inputs ?? {};
|
|
1800
2058
|
}
|
|
1801
2059
|
function readDescriptions(root, warn) {
|
|
1802
|
-
const path =
|
|
2060
|
+
const path = join7(root, DESCRIPTIONS_FILE);
|
|
1803
2061
|
if (!existsSync2(path)) return {};
|
|
1804
|
-
const parsed = parseDescriptions(
|
|
2062
|
+
const parsed = parseDescriptions(readFileSync7(path, "utf8"));
|
|
1805
2063
|
if (typeof parsed === "string") {
|
|
1806
2064
|
warn(`ignoring ${DESCRIPTIONS_FILE}: ${parsed}`);
|
|
1807
2065
|
return {};
|
|
@@ -1813,22 +2071,28 @@ function readDocs(root, globs) {
|
|
|
1813
2071
|
const docs = [];
|
|
1814
2072
|
for (const path of paths) {
|
|
1815
2073
|
try {
|
|
1816
|
-
docs.push({ path, content:
|
|
2074
|
+
docs.push({ path, content: readFileSync7(join7(root, path), "utf8") });
|
|
1817
2075
|
} catch {
|
|
1818
2076
|
}
|
|
1819
2077
|
}
|
|
1820
2078
|
return docs;
|
|
1821
2079
|
}
|
|
1822
2080
|
function defaultOutFile(root) {
|
|
1823
|
-
return existsSync2(
|
|
2081
|
+
return existsSync2(join7(root, "tsconfig.json")) ? "exposify.generated.ts" : "exposify.generated.js";
|
|
1824
2082
|
}
|
|
1825
2083
|
function specifier(outFile, file) {
|
|
1826
2084
|
const rel = posix(relative2(dirname2(outFile), file)).replace(/\.[^./]+$/, "");
|
|
1827
2085
|
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
1828
2086
|
}
|
|
1829
2087
|
function manifestLogin(l) {
|
|
1830
|
-
|
|
2088
|
+
const verify = l.verify ? manifestStep(l.verify) : void 0;
|
|
2089
|
+
const where = l.verify ? `${whereOf(l)}, then a code: ${whereOf(l.verify)}` : whereOf(l);
|
|
2090
|
+
return { ...manifestStep(l), where, ...l.passwordless ? { passwordless: true } : {}, ...verify ? { verify } : {} };
|
|
1831
2091
|
}
|
|
2092
|
+
function manifestStep(l) {
|
|
2093
|
+
return l.kind === "action" ? { kind: "action", key: l.key, fields: l.fields, ...l.host ? { host: l.host } : {} } : { kind: "route", path: l.path, fields: l.fields, body: l.body };
|
|
2094
|
+
}
|
|
2095
|
+
var whereOf = (l) => l.kind === "action" ? `${l.export} in ${l.file}` : `POST ${l.path} in ${l.file}`;
|
|
1832
2096
|
function renderManifest(d, opts) {
|
|
1833
2097
|
const ts = opts.outFile.endsWith(".ts");
|
|
1834
2098
|
const j = (v) => JSON.stringify(v);
|
|
@@ -1863,22 +2127,23 @@ function renderManifest(d, opts) {
|
|
|
1863
2127
|
return ` { name: ${j(p.name)}, path: ${j(p.path)}, params: ${j(p.params)}, file: ${j(p.file)}${extra} },`;
|
|
1864
2128
|
});
|
|
1865
2129
|
const lines = [
|
|
1866
|
-
"// Generated by exposify
|
|
2130
|
+
"// Generated by @exposify/next from your route files. Don't edit: withExposify()",
|
|
1867
2131
|
"// in next.config regenerates it on `next dev` and `next build`, or run",
|
|
1868
|
-
"// `npx exposify
|
|
2132
|
+
"// `npx @exposify/next generate`.",
|
|
1869
2133
|
"/* eslint-disable */",
|
|
1870
2134
|
...ts ? ['import type { ExposifyManifest } from "@exposify/next";', ""] : ["", '/** @type {import("@exposify/next").ExposifyManifest} */'],
|
|
1871
2135
|
`export const exposify${ts ? ": ExposifyManifest" : ""} = {`,
|
|
1872
2136
|
` info: ${j(opts.info)},`,
|
|
1873
2137
|
...opts.basePath ? [` basePath: ${j(opts.basePath)},`] : [],
|
|
1874
2138
|
...opts.trailingSlash ? [" trailingSlash: true,"] : [],
|
|
1875
|
-
...d.mcpPath && (actions.length > 0 || d.login) ? [` mcpPath: ${j(d.mcpPath)},`] : [],
|
|
2139
|
+
...d.mcpPath && (actions.length > 0 || d.login || d.supabase) ? [` mcpPath: ${j(d.mcpPath)},`] : [],
|
|
1876
2140
|
" routes: [",
|
|
1877
2141
|
...routes,
|
|
1878
2142
|
" ],",
|
|
1879
2143
|
...actions.length > 0 ? [" actions: [", ...actions, " ],"] : [],
|
|
1880
2144
|
...pages.length > 0 ? [" pages: [", ...pages, " ],"] : [],
|
|
1881
2145
|
...d.login ? [` login: ${j(manifestLogin(d.login))},`] : [],
|
|
2146
|
+
...d.supabase && !d.supabase.found.authHelpers ? [` supabase: ${j(d.supabase.choice)},`] : [],
|
|
1882
2147
|
...opts.authStacks?.length ? [` authStacks: ${j(opts.authStacks)},`] : [],
|
|
1883
2148
|
` docs: ${j(opts.docs)},`,
|
|
1884
2149
|
...Object.keys(opts.descriptions).length > 0 ? [` descriptions: ${JSON.stringify(opts.descriptions, null, 2).replace(/\n/g, "\n ")},`] : [],
|
|
@@ -1890,7 +2155,7 @@ function renderManifest(d, opts) {
|
|
|
1890
2155
|
function generate(root, opts = {}, warn = (m) => console.warn(`[exposify] ${m}`)) {
|
|
1891
2156
|
const outFile = opts.out ?? defaultOutFile(root);
|
|
1892
2157
|
const discovery = discover(root, opts);
|
|
1893
|
-
const pkg = readJson(
|
|
2158
|
+
const pkg = readJson(join7(root, "package.json"));
|
|
1894
2159
|
const info = { name: typeof pkg?.name === "string" ? pkg.name : "next-app", version: typeof pkg?.version === "string" ? pkg.version : "0.0.0" };
|
|
1895
2160
|
const content = renderManifest(discovery, {
|
|
1896
2161
|
outFile,
|
|
@@ -1901,18 +2166,24 @@ function generate(root, opts = {}, warn = (m) => console.warn(`[exposify] ${m}`)
|
|
|
1901
2166
|
...opts.trailingSlash ? { trailingSlash: true } : {},
|
|
1902
2167
|
authStacks: authStacksOf(dependencyNames(pkg)).map((s) => s.pkg)
|
|
1903
2168
|
});
|
|
2169
|
+
if (discovery.supabase?.found.authHelpers) {
|
|
2170
|
+
warn("@supabase/auth-helpers-nextjs keeps its session in an older cookie format AI assistants can't sign in with; move to @supabase/ssr (Supabase's replacement for it) to use Connect");
|
|
2171
|
+
}
|
|
2172
|
+
for (const s of discovery.supabase?.choice.unusable ?? []) {
|
|
2173
|
+
warn(`your app signs users in to Supabase on the server (${s}), but not in a Server Action or route the sign-in form can run, so AI assistants can't sign in with a password; switch on "Sign in with Supabase" on the dashboard to have them sign in to Supabase directly`);
|
|
2174
|
+
}
|
|
1904
2175
|
for (const a of discovery.actions.filter((x) => x.proposed?.status === "stale")) {
|
|
1905
|
-
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
|
|
2176
|
+
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`);
|
|
1906
2177
|
}
|
|
1907
2178
|
if (discovery.actions.length > 0 && !discovery.mcpPath) {
|
|
1908
2179
|
warn(
|
|
1909
|
-
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
|
|
2180
|
+
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)`
|
|
1910
2181
|
);
|
|
1911
2182
|
}
|
|
1912
|
-
const file =
|
|
2183
|
+
const file = join7(root, outFile);
|
|
1913
2184
|
let current;
|
|
1914
2185
|
try {
|
|
1915
|
-
current =
|
|
2186
|
+
current = readFileSync7(file, "utf8");
|
|
1916
2187
|
} catch {
|
|
1917
2188
|
current = void 0;
|
|
1918
2189
|
}
|
|
@@ -1959,9 +2230,9 @@ function watchRoutes(root, run) {
|
|
|
1959
2230
|
timer = setTimeout(run, 150);
|
|
1960
2231
|
};
|
|
1961
2232
|
for (const dir of ["app", "pages", "src", "lib", "actions", "server"]) {
|
|
1962
|
-
if (!existsSync3(
|
|
2233
|
+
if (!existsSync3(join8(root, dir))) continue;
|
|
1963
2234
|
try {
|
|
1964
|
-
watch(
|
|
2235
|
+
watch(join8(root, dir), { recursive: true }, schedule).unref();
|
|
1965
2236
|
} catch {
|
|
1966
2237
|
}
|
|
1967
2238
|
}
|