@exposify/next 0.0.5 → 0.0.6
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 +5 -1
- package/dist/exposify-next.js +144 -17
- package/dist/index.cjs +50 -1
- package/dist/index.js +50 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -184,7 +184,11 @@ route reads `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY
|
|
|
184
184
|
name them, or `supabase: false` to turn it off. The dashboard shows what was
|
|
185
185
|
found and has the switch for passwords. `inspect` says which way it went and
|
|
186
186
|
why; `npx @exposify/next test-sign-in` signs in on your running app and checks
|
|
187
|
-
a protected page accepts the cookie.
|
|
187
|
+
a protected page accepts the cookie. If your `proxy.ts` sends signed-out
|
|
188
|
+
visitors to the login page (Supabase's starter does), it would send AI
|
|
189
|
+
assistants there too, before they can sign in: `inspect` warns, and
|
|
190
|
+
`setup --apply` leaves `/mcp` out of its matcher. Your pages still go through
|
|
191
|
+
it on tool calls. `@supabase/auth-helpers-nextjs` (an
|
|
188
192
|
older cookie format) isn't supported, and apps on `@supabase/supabase-js`
|
|
189
193
|
alone keep their session in the browser, where the server never sees it.
|
|
190
194
|
|
package/dist/exposify-next.js
CHANGED
|
@@ -2296,8 +2296,8 @@ function generate(root, opts = {}, warn = (m) => console.warn(`[exposify] ${m}`)
|
|
|
2296
2296
|
import { createInterface } from "readline";
|
|
2297
2297
|
|
|
2298
2298
|
// bin/adapter.ts
|
|
2299
|
-
import { existsSync as
|
|
2300
|
-
import { dirname as dirname4, join as
|
|
2299
|
+
import { existsSync as existsSync5, readFileSync as readFileSync10 } from "fs";
|
|
2300
|
+
import { dirname as dirname4, join as join10, relative as relative3, resolve as resolve2 } from "path";
|
|
2301
2301
|
import { decideExposure, deriveDescription, resolveOptions, toolNames } from "@exposify/core";
|
|
2302
2302
|
import { renderConfig, replaceAiReadyOptions } from "@exposify/cli";
|
|
2303
2303
|
|
|
@@ -2646,13 +2646,134 @@ function scanActions(d, call, root, notes, suggestions = []) {
|
|
|
2646
2646
|
return rows;
|
|
2647
2647
|
}
|
|
2648
2648
|
|
|
2649
|
+
// bin/proxy.ts
|
|
2650
|
+
import { existsSync as existsSync4, readFileSync as readFileSync9 } from "fs";
|
|
2651
|
+
import { join as join9 } from "path";
|
|
2652
|
+
var FILES = ["", "src/"].flatMap((dir) => ["proxy", "middleware"].flatMap((name) => [".ts", ".js", ".mjs"].map((ext) => `${dir}${name}${ext}`)));
|
|
2653
|
+
var REDIRECT = /\bredirect\s*\(/;
|
|
2654
|
+
function matcherRegExp(pattern) {
|
|
2655
|
+
let re = "";
|
|
2656
|
+
for (let i = 0; i < pattern.length; ) {
|
|
2657
|
+
const c = pattern[i];
|
|
2658
|
+
if (c === "(") {
|
|
2659
|
+
let depth = 0;
|
|
2660
|
+
let j = i;
|
|
2661
|
+
for (; j < pattern.length; j++) {
|
|
2662
|
+
if (pattern[j] === "\\") j++;
|
|
2663
|
+
else if (pattern[j] === "(") depth++;
|
|
2664
|
+
else if (pattern[j] === ")" && --depth === 0) break;
|
|
2665
|
+
}
|
|
2666
|
+
re += pattern.slice(i, j + 1);
|
|
2667
|
+
i = j + 1;
|
|
2668
|
+
} else if (c === "/" && pattern[i + 1] === ":") {
|
|
2669
|
+
const m = /^\/:\w+([*+?]?)/.exec(pattern.slice(i));
|
|
2670
|
+
re += m[1] === "*" ? "(?:/.*)?" : m[1] === "+" ? "/.+" : m[1] === "?" ? "(?:/[^/]+)?" : "/[^/]+";
|
|
2671
|
+
i += m[0].length;
|
|
2672
|
+
} else {
|
|
2673
|
+
re += c.replace(/[.*+?^${}|[\]\\]/g, "\\$&");
|
|
2674
|
+
i++;
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
try {
|
|
2678
|
+
return new RegExp(`^${re}/?$`);
|
|
2679
|
+
} catch {
|
|
2680
|
+
return void 0;
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
function matcherNode(program) {
|
|
2684
|
+
for (const n of program.body) {
|
|
2685
|
+
const decl = n.type === "ExportNamedDeclaration" ? n.declaration : void 0;
|
|
2686
|
+
if (decl?.type !== "VariableDeclaration") continue;
|
|
2687
|
+
for (const d of decl.declarations) {
|
|
2688
|
+
if (d.id?.name !== "config") continue;
|
|
2689
|
+
const obj = unwrapTs(d.init);
|
|
2690
|
+
if (obj?.type !== "ObjectExpression") return "opaque";
|
|
2691
|
+
const prop2 = obj.properties.find((p) => p.type === "ObjectProperty" && (p.key?.name ?? p.key?.value) === "matcher");
|
|
2692
|
+
return prop2 ? unwrapTs(prop2.value) ?? "opaque" : null;
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
return null;
|
|
2696
|
+
}
|
|
2697
|
+
function matcherEntries(node) {
|
|
2698
|
+
const items = node.type === "ArrayExpression" ? node.elements : [node];
|
|
2699
|
+
const out = [];
|
|
2700
|
+
for (const item of items) {
|
|
2701
|
+
const literal = item?.type === "ObjectExpression" ? unwrapTs(item.properties.find((p) => (p.key?.name ?? p.key?.value) === "source")?.value) : item;
|
|
2702
|
+
const pattern = stringValue(literal);
|
|
2703
|
+
if (pattern === void 0 || !literal) return void 0;
|
|
2704
|
+
out.push({ pattern, node: literal });
|
|
2705
|
+
}
|
|
2706
|
+
return out;
|
|
2707
|
+
}
|
|
2708
|
+
function redirects(root, file, source, program) {
|
|
2709
|
+
if (REDIRECT.test(source)) return true;
|
|
2710
|
+
const load = moduleLoader(root);
|
|
2711
|
+
for (const n of program.body) {
|
|
2712
|
+
if (n.type !== "ImportDeclaration") continue;
|
|
2713
|
+
const found = load(n.source.value, file);
|
|
2714
|
+
if (found && REDIRECT.test(found.source)) return true;
|
|
2715
|
+
}
|
|
2716
|
+
return false;
|
|
2717
|
+
}
|
|
2718
|
+
function middlewareFile(root) {
|
|
2719
|
+
return FILES.find((f) => existsSync4(join9(root, f)));
|
|
2720
|
+
}
|
|
2721
|
+
function findProxyGate(root, mcpPath = "/mcp") {
|
|
2722
|
+
const file = middlewareFile(root);
|
|
2723
|
+
if (!file) return void 0;
|
|
2724
|
+
const source = readFileSync9(join9(root, file), "utf8");
|
|
2725
|
+
const program = parseModule(source, file);
|
|
2726
|
+
if (!program || !redirects(root, file, source, program)) return void 0;
|
|
2727
|
+
const matcher = matcherNode(program);
|
|
2728
|
+
if (matcher === "opaque") return void 0;
|
|
2729
|
+
if (matcher === null) return { file, mcpPath, matching: [] };
|
|
2730
|
+
const entries = matcherEntries(matcher);
|
|
2731
|
+
if (!entries) return void 0;
|
|
2732
|
+
const probes = [mcpPath, `${mcpPath.replace(/\/$/, "")}/login`];
|
|
2733
|
+
const matching = entries.filter((e) => probes.some((p) => matcherRegExp(e.pattern)?.test(p))).map((e) => e.pattern);
|
|
2734
|
+
return matching.length > 0 ? { file, mcpPath, matching } : void 0;
|
|
2735
|
+
}
|
|
2736
|
+
function proxyGateHint(gate) {
|
|
2737
|
+
return `${gate.file} sends signed-out requests to another page and runs on ${gate.mcpPath}, so AI assistants get that page instead of a sign-in. Leave ${gate.mcpPath} out of its matcher (the MCP route checks its own callers): ` + (gate.matching.some((p) => p.startsWith("/((?!")) ? `add ${exclusion(gate.mcpPath)}| after "(?!" in config.matcher.` : `matcher: ["/((?!${exclusion(gate.mcpPath)}).*)"].`);
|
|
2738
|
+
}
|
|
2739
|
+
var exclusion = (mcpPath) => `${mcpPath.replace(/^\/+|\/+$/g, "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:/|$)`;
|
|
2740
|
+
var inSource = (mcpPath) => exclusion(mcpPath).replace(/\\/g, "\\\\");
|
|
2741
|
+
function excludeMcpFromProxy(root, gate) {
|
|
2742
|
+
const file = join9(root, gate.file);
|
|
2743
|
+
const before = readFileSync9(file, "utf8");
|
|
2744
|
+
const program = parseModule(before, gate.file);
|
|
2745
|
+
if (!program) return { blocked: `couldn't parse ${gate.file}` };
|
|
2746
|
+
const summary = `Leave ${gate.mcpPath} out of ${gate.file}'s matcher, so AI assistants reach the sign-in instead of your login page`;
|
|
2747
|
+
const matcher = matcherNode(program);
|
|
2748
|
+
if (matcher === null) {
|
|
2749
|
+
const after2 = `${before.replace(/\n*$/, "\n")}
|
|
2750
|
+
export const config = {
|
|
2751
|
+
matcher: ["/((?!${inSource(gate.mcpPath)}).*)"],
|
|
2752
|
+
};
|
|
2753
|
+
`;
|
|
2754
|
+
return { plan: { file, summary, before, after: after2 } };
|
|
2755
|
+
}
|
|
2756
|
+
const entries = matcher === "opaque" ? void 0 : matcherEntries(matcher);
|
|
2757
|
+
if (!entries) return { blocked: `${gate.file}'s matcher isn't a list of strings` };
|
|
2758
|
+
let after = before;
|
|
2759
|
+
for (const e of [...entries].reverse()) {
|
|
2760
|
+
if (!gate.matching.includes(e.pattern)) continue;
|
|
2761
|
+
const raw = before.slice(e.node.start, e.node.end);
|
|
2762
|
+
const at = raw.indexOf("/((?!");
|
|
2763
|
+
if (at < 0) return { blocked: `${gate.file}'s matcher "${e.pattern}" has no "(?!" to add ${gate.mcpPath} to` };
|
|
2764
|
+
const insert = e.node.start + at + "/((?!".length;
|
|
2765
|
+
after = `${after.slice(0, insert)}${inSource(gate.mcpPath)}|${after.slice(insert)}`;
|
|
2766
|
+
}
|
|
2767
|
+
return { plan: { file, summary, before, after } };
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2649
2770
|
// bin/adapter.ts
|
|
2650
2771
|
var MCP_ROUTE_DIR = "mcp/[[...transport]]";
|
|
2651
2772
|
function isNextProject(dir) {
|
|
2652
2773
|
try {
|
|
2653
|
-
const pkg = JSON.parse(
|
|
2774
|
+
const pkg = JSON.parse(readFileSync10(join10(dir, "package.json"), "utf8"));
|
|
2654
2775
|
const hasNext = Boolean(pkg.dependencies?.next ?? pkg.devDependencies?.next);
|
|
2655
|
-
return hasNext && ["app", "src/app", "pages", "src/pages"].some((d) =>
|
|
2776
|
+
return hasNext && ["app", "src/app", "pages", "src/pages"].some((d) => existsSync5(join10(dir, d)));
|
|
2656
2777
|
} catch {
|
|
2657
2778
|
return false;
|
|
2658
2779
|
}
|
|
@@ -2704,7 +2825,11 @@ ${block}
|
|
|
2704
2825
|
const wrap = hasWithExposify(root) ? null : wrapNextConfig(root);
|
|
2705
2826
|
const also = wrap && "plan" in wrap ? [{ plan: wrap.plan, manual: WRAP_HINT }] : [];
|
|
2706
2827
|
if (wrap && !("plan" in wrap)) lines.push(WRAP_HINT);
|
|
2707
|
-
const
|
|
2828
|
+
const gate = findProxyGate(root, d.mcpPath ?? "/mcp");
|
|
2829
|
+
const unblock = gate ? excludeMcpFromProxy(root, gate) : null;
|
|
2830
|
+
if (gate && unblock && "plan" in unblock) also.push({ plan: unblock.plan, manual: proxyGateHint(gate) });
|
|
2831
|
+
else if (gate) lines.push(proxyGateHint(gate));
|
|
2832
|
+
const existing = ["exposify.generated.ts", "exposify.generated.js"].map((f) => join10(root, f)).find((f) => existsSync5(f));
|
|
2708
2833
|
const manifestFile = existing ?? generate(root, {}, () => {
|
|
2709
2834
|
}).file;
|
|
2710
2835
|
const generated = relative3(root, manifestFile);
|
|
@@ -2715,7 +2840,7 @@ ${block}
|
|
|
2715
2840
|
return { code: options, hint: { lines: [`Replace the aiReady(...) call in ${file}:${result.aiReady.site.line} with this.`, ...lines] }, apply };
|
|
2716
2841
|
}
|
|
2717
2842
|
const routeFile = `${d.appDir ?? "app"}/${MCP_ROUTE_DIR}/route.${generated.endsWith(".ts") ? "ts" : "js"}`;
|
|
2718
|
-
const manifestImport = relative3(
|
|
2843
|
+
const manifestImport = relative3(join10(root, dirname4(routeFile)), manifestFile).replace(/\.[jt]s$/, "");
|
|
2719
2844
|
const code = [
|
|
2720
2845
|
'import { aiReady } from "@exposify/next";',
|
|
2721
2846
|
...usesZod ? ['import { z } from "zod";'] : [],
|
|
@@ -2727,7 +2852,7 @@ ${block}
|
|
|
2727
2852
|
].join("\n");
|
|
2728
2853
|
return {
|
|
2729
2854
|
code,
|
|
2730
|
-
apply: { plan: { file:
|
|
2855
|
+
apply: { plan: { file: join10(root, routeFile), summary: `Create ${routeFile}`, before: null, after: `${code}
|
|
2731
2856
|
` }, ...also.length ? { also } : {} },
|
|
2732
2857
|
hint: { lines: [`Create ${routeFile} with this (agents connect to /mcp). It imports ${generated}${existing ? "" : ", written just now"}.`, ...lines] }
|
|
2733
2858
|
};
|
|
@@ -2741,10 +2866,10 @@ function withAlso(outcome, also, lines) {
|
|
|
2741
2866
|
return outcome;
|
|
2742
2867
|
}
|
|
2743
2868
|
function wrapNextConfig(root) {
|
|
2744
|
-
const name = NEXT_CONFIGS.find((n) =>
|
|
2869
|
+
const name = NEXT_CONFIGS.find((n) => existsSync5(join10(root, n)));
|
|
2745
2870
|
if (!name) return { blocked: "no next.config file" };
|
|
2746
|
-
const file =
|
|
2747
|
-
const before =
|
|
2871
|
+
const file = join10(root, name);
|
|
2872
|
+
const before = readFileSync10(file, "utf8");
|
|
2748
2873
|
const program = parseModule(before, name);
|
|
2749
2874
|
if (!program) return { blocked: `couldn't parse ${name}` };
|
|
2750
2875
|
const body = program.body;
|
|
@@ -2770,7 +2895,7 @@ ${wrapped}`;
|
|
|
2770
2895
|
function hasWithExposify(root) {
|
|
2771
2896
|
for (const name of NEXT_CONFIGS) {
|
|
2772
2897
|
try {
|
|
2773
|
-
if (
|
|
2898
|
+
if (readFileSync10(join10(root, name), "utf8").includes("withExposify")) return true;
|
|
2774
2899
|
} catch {
|
|
2775
2900
|
}
|
|
2776
2901
|
}
|
|
@@ -2817,7 +2942,7 @@ function scanProject(root) {
|
|
|
2817
2942
|
for (const p of d.pages) {
|
|
2818
2943
|
let source;
|
|
2819
2944
|
try {
|
|
2820
|
-
source =
|
|
2945
|
+
source = readFileSync10(join10(root, p.file), "utf8");
|
|
2821
2946
|
} catch {
|
|
2822
2947
|
source = void 0;
|
|
2823
2948
|
}
|
|
@@ -2844,6 +2969,8 @@ function scanProject(root) {
|
|
|
2844
2969
|
for (const row of rows) if (row.page && row.describedName) names.set(`GET ${row.info.path}`, row.describedName);
|
|
2845
2970
|
const suggestions = [];
|
|
2846
2971
|
const actions = scanActions(d, call, root, notes, suggestions);
|
|
2972
|
+
const gate = findProxyGate(root, d.mcpPath ?? "/mcp");
|
|
2973
|
+
if (gate) notes.push(`${proxyGateHint(gate)} setup --apply can make this change.`);
|
|
2847
2974
|
const routes = rows.map((row) => {
|
|
2848
2975
|
const key = `${row.info.method} ${row.info.path}`;
|
|
2849
2976
|
const decision = exposure.get(key);
|
|
@@ -2886,7 +3013,7 @@ function scanProject(root) {
|
|
|
2886
3013
|
} : {},
|
|
2887
3014
|
...call && d.mcpRoute ? {
|
|
2888
3015
|
aiReady: {
|
|
2889
|
-
site: { file:
|
|
3016
|
+
site: { file: join10(root, d.mcpRoute.file), line: call.line },
|
|
2890
3017
|
options: {
|
|
2891
3018
|
...call.expose ? { expose: call.expose } : {},
|
|
2892
3019
|
...call.exclude ? { exclude: call.exclude } : {},
|
|
@@ -2900,7 +3027,7 @@ function scanProject(root) {
|
|
|
2900
3027
|
}
|
|
2901
3028
|
function applyReplace(site, config, entries, usesZod, relFile) {
|
|
2902
3029
|
try {
|
|
2903
|
-
const before =
|
|
3030
|
+
const before = readFileSync10(site.file, "utf8");
|
|
2904
3031
|
const edit = replaceAiReadyOptions({
|
|
2905
3032
|
source: before,
|
|
2906
3033
|
file: site.file,
|
|
@@ -2919,8 +3046,8 @@ function applyReplace(site, config, entries, usesZod, relFile) {
|
|
|
2919
3046
|
}
|
|
2920
3047
|
|
|
2921
3048
|
// bin/test-sign-in.ts
|
|
2922
|
-
import { readFileSync as
|
|
2923
|
-
import { join as
|
|
3049
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
3050
|
+
import { join as join11 } from "path";
|
|
2924
3051
|
import { parseEnv } from "util";
|
|
2925
3052
|
import { supabaseCookieName, supabasePasswordLogin, supabaseSessionCookies } from "@exposify/core";
|
|
2926
3053
|
var TEST_SIGN_IN_USAGE = `Usage: @exposify/next test-sign-in [options]
|
|
@@ -2942,7 +3069,7 @@ environment, .env.local or .env. Signs the test session out afterwards.`;
|
|
|
2942
3069
|
function supabaseEnv(root, env) {
|
|
2943
3070
|
const files = [".env.local", ".env"].map((f) => {
|
|
2944
3071
|
try {
|
|
2945
|
-
return parseEnv(
|
|
3072
|
+
return parseEnv(readFileSync11(join11(root, f), "utf8"));
|
|
2946
3073
|
} catch {
|
|
2947
3074
|
return {};
|
|
2948
3075
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -444,9 +444,58 @@ function tokenize(html) {
|
|
|
444
444
|
if (last < html.length) out.push(html.slice(last));
|
|
445
445
|
return out;
|
|
446
446
|
}
|
|
447
|
+
function divEnd(html, from) {
|
|
448
|
+
const re = /<div\b(?:[^>"']|"[^"]*"|'[^']*')*>|<\/div\s*>/gi;
|
|
449
|
+
re.lastIndex = from;
|
|
450
|
+
let depth = 1;
|
|
451
|
+
for (let m = re.exec(html); m; m = re.exec(html)) {
|
|
452
|
+
depth += m[0][1] === "/" ? -1 : 1;
|
|
453
|
+
if (depth === 0) return re.lastIndex;
|
|
454
|
+
}
|
|
455
|
+
return html.length;
|
|
456
|
+
}
|
|
457
|
+
function boundaryEnd(html, from) {
|
|
458
|
+
const re = /<!--\$[?!]?-->|<!--\/\$-->/g;
|
|
459
|
+
re.lastIndex = from;
|
|
460
|
+
let depth = 1;
|
|
461
|
+
for (let m = re.exec(html); m; m = re.exec(html)) {
|
|
462
|
+
depth += m[0][4] === "/" ? -1 : 1;
|
|
463
|
+
if (depth === 0) return re.lastIndex;
|
|
464
|
+
}
|
|
465
|
+
return html.length;
|
|
466
|
+
}
|
|
467
|
+
function resolveStreamed(html) {
|
|
468
|
+
const chunks = [];
|
|
469
|
+
const open = /<div\s+hidden(?:="")?\s+id="S:([0-9a-f]+)"\s*>/g;
|
|
470
|
+
let out = "";
|
|
471
|
+
let last = 0;
|
|
472
|
+
for (let m = open.exec(html); m; m = open.exec(html)) {
|
|
473
|
+
const end = divEnd(html, open.lastIndex);
|
|
474
|
+
chunks.push({ id: m[1], content: html.slice(open.lastIndex, end).replace(/<\/div\s*>$/i, "") });
|
|
475
|
+
out += html.slice(last, m.index);
|
|
476
|
+
last = end;
|
|
477
|
+
open.lastIndex = end;
|
|
478
|
+
}
|
|
479
|
+
if (chunks.length === 0) return html;
|
|
480
|
+
out += html.slice(last);
|
|
481
|
+
for (let moved = true; moved; ) {
|
|
482
|
+
moved = false;
|
|
483
|
+
for (const c of chunks) {
|
|
484
|
+
const marker = `<template id="B:${c.id}"></template>`;
|
|
485
|
+
const at = out.indexOf(marker);
|
|
486
|
+
if (at < 0) continue;
|
|
487
|
+
const inBoundary = out.slice(Math.max(0, at - 9), at) === "<!--$?-->";
|
|
488
|
+
const start = inBoundary ? at - 9 : at;
|
|
489
|
+
const end = inBoundary ? boundaryEnd(out, at) : at + marker.length;
|
|
490
|
+
out = out.slice(0, start) + c.content + out.slice(end);
|
|
491
|
+
moved = true;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return out;
|
|
495
|
+
}
|
|
447
496
|
var hidden = (t) => "hidden" in t.attrs || t.attrs["aria-hidden"] === "true" || /display\s*:\s*none|visibility\s*:\s*hidden/i.test(t.attrs.style ?? "") || t.name === "input" && t.attrs.type === "hidden";
|
|
448
497
|
function htmlToText(html) {
|
|
449
|
-
const tokens = tokenize(html);
|
|
498
|
+
const tokens = tokenize(resolveStreamed(html));
|
|
450
499
|
const hasMain = tokens.some((t) => typeof t !== "string" && t.name === "main" && !t.close);
|
|
451
500
|
const lines = [];
|
|
452
501
|
let line = "";
|
package/dist/index.js
CHANGED
|
@@ -454,9 +454,58 @@ function tokenize(html) {
|
|
|
454
454
|
if (last < html.length) out.push(html.slice(last));
|
|
455
455
|
return out;
|
|
456
456
|
}
|
|
457
|
+
function divEnd(html, from) {
|
|
458
|
+
const re = /<div\b(?:[^>"']|"[^"]*"|'[^']*')*>|<\/div\s*>/gi;
|
|
459
|
+
re.lastIndex = from;
|
|
460
|
+
let depth = 1;
|
|
461
|
+
for (let m = re.exec(html); m; m = re.exec(html)) {
|
|
462
|
+
depth += m[0][1] === "/" ? -1 : 1;
|
|
463
|
+
if (depth === 0) return re.lastIndex;
|
|
464
|
+
}
|
|
465
|
+
return html.length;
|
|
466
|
+
}
|
|
467
|
+
function boundaryEnd(html, from) {
|
|
468
|
+
const re = /<!--\$[?!]?-->|<!--\/\$-->/g;
|
|
469
|
+
re.lastIndex = from;
|
|
470
|
+
let depth = 1;
|
|
471
|
+
for (let m = re.exec(html); m; m = re.exec(html)) {
|
|
472
|
+
depth += m[0][4] === "/" ? -1 : 1;
|
|
473
|
+
if (depth === 0) return re.lastIndex;
|
|
474
|
+
}
|
|
475
|
+
return html.length;
|
|
476
|
+
}
|
|
477
|
+
function resolveStreamed(html) {
|
|
478
|
+
const chunks = [];
|
|
479
|
+
const open = /<div\s+hidden(?:="")?\s+id="S:([0-9a-f]+)"\s*>/g;
|
|
480
|
+
let out = "";
|
|
481
|
+
let last = 0;
|
|
482
|
+
for (let m = open.exec(html); m; m = open.exec(html)) {
|
|
483
|
+
const end = divEnd(html, open.lastIndex);
|
|
484
|
+
chunks.push({ id: m[1], content: html.slice(open.lastIndex, end).replace(/<\/div\s*>$/i, "") });
|
|
485
|
+
out += html.slice(last, m.index);
|
|
486
|
+
last = end;
|
|
487
|
+
open.lastIndex = end;
|
|
488
|
+
}
|
|
489
|
+
if (chunks.length === 0) return html;
|
|
490
|
+
out += html.slice(last);
|
|
491
|
+
for (let moved = true; moved; ) {
|
|
492
|
+
moved = false;
|
|
493
|
+
for (const c of chunks) {
|
|
494
|
+
const marker = `<template id="B:${c.id}"></template>`;
|
|
495
|
+
const at = out.indexOf(marker);
|
|
496
|
+
if (at < 0) continue;
|
|
497
|
+
const inBoundary = out.slice(Math.max(0, at - 9), at) === "<!--$?-->";
|
|
498
|
+
const start = inBoundary ? at - 9 : at;
|
|
499
|
+
const end = inBoundary ? boundaryEnd(out, at) : at + marker.length;
|
|
500
|
+
out = out.slice(0, start) + c.content + out.slice(end);
|
|
501
|
+
moved = true;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return out;
|
|
505
|
+
}
|
|
457
506
|
var hidden = (t) => "hidden" in t.attrs || t.attrs["aria-hidden"] === "true" || /display\s*:\s*none|visibility\s*:\s*hidden/i.test(t.attrs.style ?? "") || t.name === "input" && t.attrs.type === "hidden";
|
|
458
507
|
function htmlToText(html) {
|
|
459
|
-
const tokens = tokenize(html);
|
|
508
|
+
const tokens = tokenize(resolveStreamed(html));
|
|
460
509
|
const hasMain = tokens.some((t) => typeof t !== "string" && t.name === "main" && !t.close);
|
|
461
510
|
const lines = [];
|
|
462
511
|
let line = "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exposify/next",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@babel/parser": "^8.0.0",
|
|
35
|
-
"@exposify/cli": "0.0.
|
|
35
|
+
"@exposify/cli": "0.0.5",
|
|
36
36
|
"@exposify/core": "0.3.16",
|
|
37
37
|
"tinyglobby": "^0.2.10",
|
|
38
38
|
"zod": "^3.24.0"
|