dh-remixer-sdk 0.0.25 → 0.0.26-1a0cd0b
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/package.json +1 -1
- package/scripts/ssg-helmet/build-helmet-html.mjs +6 -1
- package/scripts/ssg-helmet/index.mjs +12 -1
- package/scripts/ssg-helmet/prerenderer.mjs +57 -4
- package/templates/base/filemap.json +2 -1
- package/templates/base/supabase.ts +73 -0
- package/templates/ecommerce/stripe-checkout.ts +1 -1
package/package.json
CHANGED
|
@@ -289,6 +289,9 @@ export function buildRedirectHelmetHtml(
|
|
|
289
289
|
);
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
const bodyTag = pick(/(<body\b[^>]*>)/i, prerenderedHtml) || "<body>";
|
|
293
|
+
const bodyInner = pick(/<body\b[^>]*>([\s\S]*?)<\/body>/i, prerenderedHtml);
|
|
294
|
+
|
|
292
295
|
// --- redirect script ------------------------------------------------------
|
|
293
296
|
out.push(
|
|
294
297
|
`<script type="text/javascript">
|
|
@@ -320,6 +323,8 @@ export function buildRedirectHelmetHtml(
|
|
|
320
323
|
<head>
|
|
321
324
|
${out.filter(Boolean).join("\n")}
|
|
322
325
|
</head>
|
|
323
|
-
|
|
326
|
+
${bodyTag}
|
|
327
|
+
${bodyInner || ""}
|
|
328
|
+
</body>
|
|
324
329
|
</html>`;
|
|
325
330
|
}
|
|
@@ -53,7 +53,7 @@ async function main() {
|
|
|
53
53
|
}
|
|
54
54
|
prerenderer = null;
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
if (attempt === maxRetries) {
|
|
58
58
|
throw new Error(`Failed to create Prerenderer after ${maxRetries} attempts: ${err.message}`);
|
|
59
59
|
}
|
|
@@ -100,6 +100,17 @@ async function main() {
|
|
|
100
100
|
);
|
|
101
101
|
console.log("[created] sitemap.xml");
|
|
102
102
|
|
|
103
|
+
await fs.writeFile(
|
|
104
|
+
path.join(REMIXER_BUILD_DIR, "robots.txt"),
|
|
105
|
+
`User-agent: *
|
|
106
|
+
Allow: /
|
|
107
|
+
|
|
108
|
+
Sitemap: https://${TARGET_DOMAIN}/sitemap.xml
|
|
109
|
+
`,
|
|
110
|
+
"utf8",
|
|
111
|
+
);
|
|
112
|
+
console.log("[created] robots.txt");
|
|
113
|
+
|
|
103
114
|
await prerenderer.close();
|
|
104
115
|
}
|
|
105
116
|
|
|
@@ -26,21 +26,73 @@ export class Prerenderer {
|
|
|
26
26
|
|
|
27
27
|
async #renderPage(route) {
|
|
28
28
|
const r = this.#normalize(route);
|
|
29
|
-
const { Page, Runtime } = this.#client;
|
|
29
|
+
const { Page, Runtime, Network } = this.#client;
|
|
30
|
+
|
|
31
|
+
await Network.enable();
|
|
32
|
+
|
|
33
|
+
let inflight = 0;
|
|
34
|
+
|
|
35
|
+
// Track network activity
|
|
36
|
+
Network.requestWillBeSent(() => {
|
|
37
|
+
inflight++;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
Network.loadingFinished(() => {
|
|
41
|
+
inflight = Math.max(0, inflight - 1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
Network.loadingFailed(() => {
|
|
45
|
+
inflight = Math.max(0, inflight - 1);
|
|
46
|
+
});
|
|
30
47
|
|
|
31
48
|
// Navigate
|
|
32
49
|
await Page.navigate({ url: `${this.#origin}${r}` });
|
|
33
50
|
await Page.loadEventFired();
|
|
34
51
|
|
|
35
|
-
// Wait for
|
|
52
|
+
// Wait for network idle (no requests for 500ms)
|
|
53
|
+
await new Promise((resolve) => {
|
|
54
|
+
let idleTimer;
|
|
55
|
+
|
|
56
|
+
const checkIdle = () => {
|
|
57
|
+
if (inflight === 0) {
|
|
58
|
+
idleTimer = globalThis.setTimeout(() => resolve(), 500);
|
|
59
|
+
} else {
|
|
60
|
+
clearTimeout(idleTimer);
|
|
61
|
+
globalThis.setTimeout(checkIdle, 100);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
checkIdle();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Wait for real DOM content (NOT just Helmet)
|
|
36
69
|
await Runtime.evaluate({
|
|
37
70
|
expression: `
|
|
38
71
|
(async function () {
|
|
39
72
|
const deadline = Date.now() + 15000;
|
|
73
|
+
|
|
74
|
+
function hasContent() {
|
|
75
|
+
const root = document.querySelector('#root') || document.body;
|
|
76
|
+
|
|
77
|
+
if (!root) return false;
|
|
78
|
+
|
|
79
|
+
// Must have meaningful text
|
|
80
|
+
const text = root.innerText || '';
|
|
81
|
+
if (text.trim().length < 100) return false;
|
|
82
|
+
|
|
83
|
+
// Must have some actual elements
|
|
84
|
+
const meaningful = root.querySelector(
|
|
85
|
+
'h1, h2, h3, p, img, ul, ol, li, section, article'
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return !!meaningful;
|
|
89
|
+
}
|
|
90
|
+
|
|
40
91
|
while (Date.now() < deadline) {
|
|
41
|
-
if (
|
|
92
|
+
if (hasContent()) return true;
|
|
42
93
|
await new Promise(r => setTimeout(r, 50));
|
|
43
94
|
}
|
|
95
|
+
|
|
44
96
|
return false;
|
|
45
97
|
})()
|
|
46
98
|
`,
|
|
@@ -48,13 +100,14 @@ export class Prerenderer {
|
|
|
48
100
|
returnByValue: true,
|
|
49
101
|
});
|
|
50
102
|
|
|
103
|
+
// Small buffer to let layout settle
|
|
51
104
|
await Runtime.evaluate({
|
|
52
105
|
expression: `new Promise(r => setTimeout(r, 100))`,
|
|
53
106
|
awaitPromise: true,
|
|
54
107
|
returnByValue: true,
|
|
55
108
|
});
|
|
56
109
|
|
|
57
|
-
//
|
|
110
|
+
// Extract FULL HTML (including body)
|
|
58
111
|
const { result } = await Runtime.evaluate({
|
|
59
112
|
expression: "document.documentElement.outerHTML",
|
|
60
113
|
returnByValue: true,
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
|
|
2
|
+
import { createClient } from '@supabase/supabase-js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 1) supabase — user-scoped client
|
|
6
|
+
* • Before login: uses the project access token
|
|
7
|
+
* • After login: automatically swaps to the user's JWT
|
|
8
|
+
*/
|
|
9
|
+
export const supabase = createClient(
|
|
10
|
+
import.meta.env.VITE_SUPABASE_URL!,
|
|
11
|
+
import.meta.env.VITE_SUPABASE_ANON_KEY!,
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 2) supabaseProject — tenant-wide client
|
|
16
|
+
* • Always uses the project access token
|
|
17
|
+
* • For public or calendar-style views where you want every project's data
|
|
18
|
+
*/
|
|
19
|
+
export const supabaseProject = createClient(
|
|
20
|
+
import.meta.env.VITE_SUPABASE_URL!,
|
|
21
|
+
import.meta.env.VITE_SUPABASE_ANON_KEY!,
|
|
22
|
+
{
|
|
23
|
+
global: {
|
|
24
|
+
headers: {
|
|
25
|
+
Authorization: `Bearer ${import.meta.env.VITE_PROJECT_ACCESS_TOKEN}`,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
auth: {
|
|
29
|
+
// disable session persistence so it never picks up a user JWT
|
|
30
|
+
detectSessionInUrl: false,
|
|
31
|
+
persistSession: false,
|
|
32
|
+
autoRefreshToken: false,
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* authHeader() — helper for manual fetch() calls (Edge Functions/REST)
|
|
39
|
+
* • Ensures project_id is set in user_metadata and session is refreshed
|
|
40
|
+
* • Returns `{ Authorization: 'Bearer <token>' }` where `<token>` is:
|
|
41
|
+
* - the logged-in user's JWT (if session exists), or
|
|
42
|
+
* - the project access token (anonymous)
|
|
43
|
+
*/
|
|
44
|
+
export async function authHeader() {
|
|
45
|
+
let {
|
|
46
|
+
data: { session },
|
|
47
|
+
} = await supabase.auth.getSession();
|
|
48
|
+
|
|
49
|
+
if (session?.user) {
|
|
50
|
+
const currentProjectId = import.meta.env.VITE_PROJECT_ID;
|
|
51
|
+
const userProjectId = session.user.user_metadata?.project_id;
|
|
52
|
+
|
|
53
|
+
if (userProjectId !== currentProjectId) {
|
|
54
|
+
const { error } = await supabase.auth.updateUser({
|
|
55
|
+
data: {
|
|
56
|
+
...session.user.user_metadata,
|
|
57
|
+
project_id: currentProjectId,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
if (error) throw error;
|
|
61
|
+
|
|
62
|
+
// Force session refresh to get new JWT with updated project_id
|
|
63
|
+
const { data: refreshData, error: refreshError } = await supabase.auth.refreshSession();
|
|
64
|
+
if (refreshError) throw refreshError;
|
|
65
|
+
session = refreshData.session;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const token =
|
|
70
|
+
session?.access_token ?? import.meta.env.VITE_PROJECT_ACCESS_TOKEN;
|
|
71
|
+
|
|
72
|
+
return { Authorization: `Bearer ${token}` };
|
|
73
|
+
}
|
|
@@ -49,7 +49,7 @@ export const createCheckoutSession = async (
|
|
|
49
49
|
payload.guest_email = params.guestEmail;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
if (params.shippingRateId) {
|
|
52
|
+
if (params.shippingRateId && params.mode !== 'subscription') {
|
|
53
53
|
payload.shipping_rate_id = params.shippingRateId;
|
|
54
54
|
payload.shipping_cost = params.shippingCost;
|
|
55
55
|
if (params.shippingLabel) {
|