@exposify/next 0.0.3 → 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/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 join7 } from "path";
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 readFileSync6, readdirSync, writeFileSync } from "fs";
16
- import { dirname as dirname2, join as join6, relative as relative2, sep as sep2 } from "path";
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 prop of decl.id.properties) {
346
- const key = prop.type === "ObjectProperty" ? keyName(prop.key) : void 0;
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 prop of id.properties) {
389
- const key = prop.type === "ObjectProperty" ? keyName(prop.key) : void 0;
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(prop)}: a rest element in parameter ${role.name}`);
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(prop)}: ${key} has no type it can read (string, number, boolean or a union of strings)`);
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 || prop.value.type === "AssignmentPattern" ? {} : { required: true } });
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 prop = keyName(p.key);
478
+ const prop2 = keyName(p.key);
479
479
  const value = p.value.type === "AssignmentPattern" ? p.value.left : p.value;
480
- if (!prop || value.type !== "Identifier") continue;
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 named2 = new Set([...locals.values()].map((l) => l.prop));
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 || !named2.has(k)) out.set(k, v);
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(prop) ? use.get(prop) : fallback;
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 prop = keyName(expr.property);
506
- if (prop && use.has(prop)) out.set(key, use.get(prop));
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
- function actionCandidate(a) {
876
- if (NOT_A_LOGIN.test(a.key) || NOT_A_LOGIN.test(a.action.name)) return null;
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 fields = loginFields(a.action.fields.map((f) => f.name));
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 || NOT_A_LOGIN.test(r.path)) return null;
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 ? loginFields(read2.fields) : null;
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 named = (l) => LOGIN_NAME.test(l.kind === "action" ? l.export : l.path);
898
- function findLogin(d) {
899
- const candidates = [];
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) candidates.push(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) candidates.push(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 (candidates.length === 0) return null;
909
- const score = (l) => (named(l) ? 0 : 4) + (l.kind === "action" && l.host ? 0 : 2) + l.fields.length / 100;
910
- candidates.sort((a, b) => score(a) - score(b));
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 prop of param.properties) {
1094
- if (prop.type !== "ObjectProperty" || keyName(prop.key) !== "searchParams") continue;
1095
- const value = prop.value.type === "AssignmentPattern" ? prop.value.left : prop.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 prop of init.properties) {
1163
- if (prop.type !== "ObjectProperty") continue;
1164
- const key = keyName(prop.key);
1165
- const value = unwrapTs(prop.value);
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 candidates = [path, ...EXTENSIONS.map((e) => path + e), ...EXTENSIONS.map((e) => join5(path, `index${e}`))];
1269
- if (/\.[mc]?jsx?$/.test(path)) candidates.push(...EXTENSIONS.map((e) => path.replace(/\.[mc]?jsx?$/, e)));
1270
- return candidates.find((c) => existsSync(c) && statSync(c).isFile());
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 candidates = (specifier2, fromFile) => {
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 candidates(specifier2, fromFile)) {
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 prop of n.properties) {
1326
- const key = prop.type === "ObjectProperty" ? keyName(prop.key) : void 0;
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(prop.value, (c) => {
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 prop of meta.properties) {
1350
- if (prop.type !== "ObjectProperty") continue;
1351
- const key = keyName(prop.key);
1352
- const value = stringValue(prop.value);
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(prop.value);
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 named2 = /* @__PURE__ */ new Map();
1652
+ const named = /* @__PURE__ */ new Map();
1627
1653
  walk(program, (n) => {
1628
- if (n.type === "VariableDeclarator" && n.id.type === "Identifier" && n.init) named2.set(unwrapTs(n.init), n.id.name);
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 ?? named2.get(n);
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(join6(root, n)));
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(join6(root, rel), { withFileTypes: true });
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 = readFileSync6(join6(root, file), "utf8");
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 = readFileSync6(join6(root, file), "utf8");
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 found = findLogin(out);
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 = readFileSync6(join6(root, file), "utf8");
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(readFileSync6(path, "utf8"));
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 = join6(root, DESCRIPTIONS_FILE);
2054
+ const path = join7(root, DESCRIPTIONS_FILE);
1797
2055
  if (!existsSync2(path)) return {};
1798
- const parsed = parseDescriptions(readFileSync6(path, "utf8"));
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 = join6(root, DESCRIPTIONS_FILE);
2060
+ const path = join7(root, DESCRIPTIONS_FILE);
1803
2061
  if (!existsSync2(path)) return {};
1804
- const parsed = parseDescriptions(readFileSync6(path, "utf8"));
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: readFileSync6(join6(root, path), "utf8") });
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(join6(root, "tsconfig.json")) ? "exposify.generated.ts" : "exposify.generated.js";
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
- 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}` };
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);
@@ -1872,13 +2136,14 @@ function renderManifest(d, opts) {
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(join6(root, "package.json"));
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,6 +2166,12 @@ 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
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
  }
@@ -1909,10 +2180,10 @@ function generate(root, opts = {}, warn = (m) => console.warn(`[exposify] ${m}`)
1909
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 = join6(root, outFile);
2183
+ const file = join7(root, outFile);
1913
2184
  let current;
1914
2185
  try {
1915
- current = readFileSync6(file, "utf8");
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(join7(root, dir))) continue;
2233
+ if (!existsSync3(join8(root, dir))) continue;
1963
2234
  try {
1964
- watch(join7(root, dir), { recursive: true }, schedule).unref();
2235
+ watch(join8(root, dir), { recursive: true }, schedule).unref();
1965
2236
  } catch {
1966
2237
  }
1967
2238
  }