create-qwik 1.1.4 → 1.1.5
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/index.cjs +36 -36
- package/package.json +1 -1
- package/starters/apps/base/.vscode/launch.json +17 -0
- package/starters/apps/base/package.json +9 -9
- package/starters/apps/basic/package.json +1 -1
- package/starters/apps/basic/src/components/starter/next-steps/next-steps.tsx +1 -1
- package/starters/apps/basic/src/routes/layout.tsx +12 -0
- package/starters/apps/empty/package.json +1 -1
- package/starters/apps/empty/src/global.css +0 -7
- package/starters/apps/empty/src/routes/layout.tsx +12 -0
- package/starters/apps/library/package.json +7 -7
- package/starters/apps/site-with-visual-cms/.env +2 -0
- package/starters/apps/site-with-visual-cms/builder-integration.ts +853 -0
- package/starters/apps/site-with-visual-cms/package.json +17 -0
- package/starters/apps/site-with-visual-cms/public/favicon.svg +1 -0
- package/starters/apps/site-with-visual-cms/public/manifest.json +9 -0
- package/starters/apps/site-with-visual-cms/public/robots.txt +0 -0
- package/starters/apps/site-with-visual-cms/src/components/builder-registry.ts +15 -0
- package/starters/apps/site-with-visual-cms/src/components/counter/counter.module.css +24 -0
- package/starters/apps/site-with-visual-cms/src/components/counter/counter.tsx +81 -0
- package/starters/apps/site-with-visual-cms/src/components/footer/footer.module.css +17 -0
- package/starters/apps/site-with-visual-cms/src/components/footer/footer.tsx +14 -0
- package/starters/apps/site-with-visual-cms/src/components/gauge/gauge.module.css +22 -0
- package/starters/apps/site-with-visual-cms/src/components/gauge/index.tsx +30 -0
- package/starters/apps/site-with-visual-cms/src/components/header/header.module.css +50 -0
- package/starters/apps/site-with-visual-cms/src/components/header/header.tsx +34 -0
- package/starters/apps/site-with-visual-cms/src/components/icons/qwik.tsx +38 -0
- package/starters/apps/{documentation-site → site-with-visual-cms}/src/components/router-head/router-head.tsx +3 -3
- package/starters/apps/site-with-visual-cms/src/entry.dev.tsx +17 -0
- package/starters/apps/site-with-visual-cms/src/entry.preview.tsx +20 -0
- package/starters/apps/site-with-visual-cms/src/entry.ssr.tsx +27 -0
- package/starters/apps/site-with-visual-cms/src/global.css +116 -0
- package/starters/apps/{documentation-site → site-with-visual-cms}/src/root.tsx +3 -1
- package/starters/apps/site-with-visual-cms/src/routes/[...index]/index.tsx +57 -0
- package/starters/apps/{documentation-site → site-with-visual-cms}/src/routes/layout.tsx +2 -2
- package/starters/apps/site-with-visual-cms/src/routes/service-worker.ts +18 -0
- package/starters/apps/site-with-visual-cms/vite.config.ts +10 -0
- package/starters/apps/documentation-site/package.json +0 -13
- package/starters/apps/documentation-site/src/components/breadcrumbs/breadcrumbs.css +0 -25
- package/starters/apps/documentation-site/src/components/breadcrumbs/breadcrumbs.tsx +0 -74
- package/starters/apps/documentation-site/src/components/footer/footer.css +0 -22
- package/starters/apps/documentation-site/src/components/footer/footer.tsx +0 -36
- package/starters/apps/documentation-site/src/components/header/header.css +0 -34
- package/starters/apps/documentation-site/src/components/header/header.tsx +0 -26
- package/starters/apps/documentation-site/src/components/icons/qwik.tsx +0 -20
- package/starters/apps/documentation-site/src/components/menu/menu.css +0 -13
- package/starters/apps/documentation-site/src/components/menu/menu.tsx +0 -36
- package/starters/apps/documentation-site/src/components/on-this-page/on-this-page.css +0 -33
- package/starters/apps/documentation-site/src/components/on-this-page/on-this-page.tsx +0 -62
- package/starters/apps/documentation-site/src/global.css +0 -66
- package/starters/apps/documentation-site/src/routes/about-us/index.md +0 -15
- package/starters/apps/documentation-site/src/routes/docs/advanced/index.md +0 -11
- package/starters/apps/documentation-site/src/routes/docs/docs.css +0 -22
- package/starters/apps/documentation-site/src/routes/docs/getting-started/index.md +0 -13
- package/starters/apps/documentation-site/src/routes/docs/index.md +0 -22
- package/starters/apps/documentation-site/src/routes/docs/layout.tsx +0 -25
- package/starters/apps/documentation-site/src/routes/docs/menu.md +0 -21
- package/starters/apps/documentation-site/src/routes/index.tsx +0 -167
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
import type { Logger, Plugin } from 'vite';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { homedir, hostname } from 'node:os';
|
|
5
|
+
import { request } from 'node:https';
|
|
6
|
+
import { IncomingMessage } from 'node:http';
|
|
7
|
+
|
|
8
|
+
function html(content: string) {
|
|
9
|
+
return `
|
|
10
|
+
<!DOCTYPE html>
|
|
11
|
+
<html>
|
|
12
|
+
<head>
|
|
13
|
+
<meta charset="utf-8">
|
|
14
|
+
<title>Visual CMS Site Integration With Builder.io</title>
|
|
15
|
+
<style>
|
|
16
|
+
html {
|
|
17
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
18
|
+
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
|
19
|
+
'Segoe UI Symbol', 'Noto Color Emoji';
|
|
20
|
+
}
|
|
21
|
+
body {
|
|
22
|
+
padding: 80px 0;
|
|
23
|
+
line-height: 1.8;
|
|
24
|
+
}
|
|
25
|
+
main {
|
|
26
|
+
display: grid;
|
|
27
|
+
grid-template-columns: 200px 1fr;
|
|
28
|
+
gap: 40px;
|
|
29
|
+
width: 100%;
|
|
30
|
+
max-width: 800px;
|
|
31
|
+
margin: 0 auto;
|
|
32
|
+
}
|
|
33
|
+
h1 {
|
|
34
|
+
margin-top: 0;
|
|
35
|
+
}
|
|
36
|
+
button {
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
}
|
|
39
|
+
aside ul {
|
|
40
|
+
margin: 0;
|
|
41
|
+
padding: 0;
|
|
42
|
+
list-style: none;
|
|
43
|
+
}
|
|
44
|
+
aside li {
|
|
45
|
+
margin: 0;
|
|
46
|
+
padding: 20px 10px;
|
|
47
|
+
}
|
|
48
|
+
aside li a {
|
|
49
|
+
text-decoration: none;
|
|
50
|
+
color: inherit;
|
|
51
|
+
}
|
|
52
|
+
aside li.active {
|
|
53
|
+
font-weight: bold;
|
|
54
|
+
}
|
|
55
|
+
aside li.completed {
|
|
56
|
+
color: gray;
|
|
57
|
+
}
|
|
58
|
+
</style>
|
|
59
|
+
<link rel="icon shortcut" href="https://cdn.builder.io/favicon.ico">
|
|
60
|
+
</head>
|
|
61
|
+
<body>
|
|
62
|
+
<main>${content}</main>
|
|
63
|
+
</body>
|
|
64
|
+
</html>
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* First step in the setup process. Show all the steps and explain what's going on.
|
|
70
|
+
*/
|
|
71
|
+
function setupOverviewStep(ctx: BuilderSetupContext, url: URL) {
|
|
72
|
+
debug(`show overview step`);
|
|
73
|
+
const nextStepUrl = getAuthConnectUrl(ctx, url);
|
|
74
|
+
|
|
75
|
+
return html(`
|
|
76
|
+
<aside>
|
|
77
|
+
<ul>
|
|
78
|
+
<li class="active">
|
|
79
|
+
Overview
|
|
80
|
+
</li>
|
|
81
|
+
<li>
|
|
82
|
+
Connect Builder.io
|
|
83
|
+
</li>
|
|
84
|
+
<li>
|
|
85
|
+
Setup Content Page
|
|
86
|
+
</li>
|
|
87
|
+
</ul>
|
|
88
|
+
</aside>
|
|
89
|
+
<section>
|
|
90
|
+
<h1>
|
|
91
|
+
Integrate Builder.io Visual CMS with ${ctx.framework}
|
|
92
|
+
</h1>
|
|
93
|
+
<p>
|
|
94
|
+
Success! Your ${ctx.framework} app has been created!
|
|
95
|
+
</p>
|
|
96
|
+
<p>
|
|
97
|
+
Next let's connect Builder.io so you can start editing and publishing content.
|
|
98
|
+
</p>
|
|
99
|
+
<nav>
|
|
100
|
+
<p>
|
|
101
|
+
<a href="${nextStepUrl}">Next</a>
|
|
102
|
+
</p>
|
|
103
|
+
</nav>
|
|
104
|
+
</section>
|
|
105
|
+
`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Returning from the builder.io auth flow, show the next step in the setup process.
|
|
110
|
+
*/
|
|
111
|
+
function connectedToBuilderStep(ctx: BuilderSetupContext, url: URL, backgroundUpdate: boolean) {
|
|
112
|
+
debug(`show connected to builder step (background update: ${backgroundUpdate})`);
|
|
113
|
+
const appBaseUrl = getAppBaseUrl(ctx, url).pathname;
|
|
114
|
+
const connectedStepUrl = getConnectedStepUrl(ctx, url);
|
|
115
|
+
const backgroundUpdateUrl = getBackgroundUpdateUrl(ctx, url);
|
|
116
|
+
|
|
117
|
+
return html(`
|
|
118
|
+
<aside>
|
|
119
|
+
<ul>
|
|
120
|
+
<li class="completed">
|
|
121
|
+
Overview
|
|
122
|
+
</li>
|
|
123
|
+
<li class="active">
|
|
124
|
+
Connect Builder.io
|
|
125
|
+
</li>
|
|
126
|
+
<li>
|
|
127
|
+
Setup Content Page
|
|
128
|
+
</li>
|
|
129
|
+
</ul>
|
|
130
|
+
</aside>
|
|
131
|
+
<section>
|
|
132
|
+
<h1>
|
|
133
|
+
Visual CMS Connected
|
|
134
|
+
</h1>
|
|
135
|
+
<p>
|
|
136
|
+
Great! Your ${ctx.framework} app has been connected to the Builder.io Visual CMS.
|
|
137
|
+
</p>
|
|
138
|
+
<p>
|
|
139
|
+
Next let's connect Builder.io so you can start editing and publishing content.
|
|
140
|
+
</p>
|
|
141
|
+
<nav>
|
|
142
|
+
<p>
|
|
143
|
+
<a id="next" disabled href="${appBaseUrl}">Next</a>
|
|
144
|
+
</p>
|
|
145
|
+
</nav>
|
|
146
|
+
</section>
|
|
147
|
+
${
|
|
148
|
+
backgroundUpdate
|
|
149
|
+
? `
|
|
150
|
+
<script>
|
|
151
|
+
history.replaceState({}, "", "${connectedStepUrl}");
|
|
152
|
+
fetch("${backgroundUpdateUrl}").then((rsp) => {
|
|
153
|
+
if (rsp.ok) {
|
|
154
|
+
document.getElementById("next").removeAttribute("disabled");
|
|
155
|
+
} else {
|
|
156
|
+
console.error("Failed to update Builder.io page", rsp.status);
|
|
157
|
+
rsp.text().then((text) => {
|
|
158
|
+
console.error(text);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}).catch((err) => {
|
|
162
|
+
console.error(err);
|
|
163
|
+
});
|
|
164
|
+
</script>
|
|
165
|
+
`
|
|
166
|
+
: ``
|
|
167
|
+
}
|
|
168
|
+
`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getDefaultHomepage(ctx: BuilderSetupContext) {
|
|
172
|
+
return JSON.stringify({
|
|
173
|
+
'@version': 4,
|
|
174
|
+
name: DEFAULT_HOMEPAGE_PAGE_NAME,
|
|
175
|
+
ownerId: ctx.credentials.publicApiKey,
|
|
176
|
+
published: 'published',
|
|
177
|
+
query: [
|
|
178
|
+
{
|
|
179
|
+
'@type': '@builder.io/core:Query',
|
|
180
|
+
property: 'urlPath',
|
|
181
|
+
value: ctx.appBasePathname,
|
|
182
|
+
operator: 'is',
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
data: {
|
|
186
|
+
blocksString: DEFAULT_HOMEPAGE_BLOCK,
|
|
187
|
+
title: DEFAULT_HOMEPAGE_PAGE_NAME,
|
|
188
|
+
url: ctx.appBasePathname,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const DEFAULT_HOMEPAGE_PAGE_NAME = `Homepage`;
|
|
194
|
+
const DEFAULT_HOMEPAGE_BLOCK = `[{"@type":"@builder.io/sdk:Element","@version":2,"id":"builder-b3e7bacb8fc740109a8154507ad3f39b","component":{"name":"Text","options":{"text":"<h1>Hello Qwik!</h1>"}},"responsiveStyles":{"large":{"display":"flex","flexDirection":"column","position":"relative","flexShrink":"0","boxSizing":"border-box","marginTop":"20px","lineHeight":"normal","height":"auto","marginLeft":"auto","marginRight":"auto"}}},{"@type":"@builder.io/sdk:Element","@version":2,"id":"builder-15627179727f4fcba1246a6da5e8ae67","component":{"name":"Image","options":{"image":"https://cdn.builder.io/api/v1/image/assets%2F4025e37ed968472fac153d65c579ca46%2Fb2a221368b714e8991e94790bb3a0068","backgroundSize":"cover","backgroundPosition":"center","lazy":false,"fitContent":true,"aspectRatio":1.333,"lockAspectRatio":false,"height":1300,"width":975}},"responsiveStyles":{"large":{"display":"flex","flexDirection":"column","position":"relative","flexShrink":"0","boxSizing":"border-box","marginTop":"20px","width":"100%","minHeight":"20px","minWidth":"20px","overflow":"hidden"}}}]`;
|
|
195
|
+
|
|
196
|
+
async function validateBuilderIntegration(
|
|
197
|
+
ctx: BuilderSetupContext,
|
|
198
|
+
url: URL
|
|
199
|
+
): Promise<BuilderIntegrationResult> {
|
|
200
|
+
const result: BuilderIntegrationResult = {};
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
if (ctx.isValid || !isPageRequest(url)) {
|
|
204
|
+
// all good, already validated
|
|
205
|
+
// or this is not a page request so don't bother
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// get the keys from the querystring (if they exist)
|
|
210
|
+
const qsPublicApiKey = url.searchParams.get(BUILDER_PUBLIC_API_KEY_QS);
|
|
211
|
+
const qsPrivateAuthKey = url.searchParams.get(BUILDER_PRIVATE_AUTH_KEY_QS);
|
|
212
|
+
|
|
213
|
+
// check if we're returning from the builder.io auth flow
|
|
214
|
+
if (qsPublicApiKey && qsPrivateAuthKey) {
|
|
215
|
+
debug(`url has auth keys`);
|
|
216
|
+
// we've returned from the builder.io auth flow
|
|
217
|
+
// and have the auth keys in the querystring
|
|
218
|
+
ctx.credentials = {
|
|
219
|
+
publicApiKey: qsPublicApiKey,
|
|
220
|
+
privateAuthKey: qsPrivateAuthKey,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
if (url.searchParams.get(BUILDER_SETUP_STEP_QS) === BUILDER_UPDATE_STEP) {
|
|
224
|
+
debug(`step: ${BUILDER_UPDATE_STEP}`);
|
|
225
|
+
// handle the background fetch() request that should:
|
|
226
|
+
// - create the homepage
|
|
227
|
+
// - write the private auth key to the user's home directory
|
|
228
|
+
// - update the .env file with the public api key
|
|
229
|
+
|
|
230
|
+
// see if this builder account already has a homepage created
|
|
231
|
+
const hasHomepage = await hasBuilderHomepage(ctx);
|
|
232
|
+
if (!hasHomepage) {
|
|
233
|
+
// there is no homepage content created yet
|
|
234
|
+
// create the default homepage for them
|
|
235
|
+
await createBuilderHomepage(ctx);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// write the app credientials to the user's home directory builder config
|
|
239
|
+
setBuilderAppCredentials(ctx);
|
|
240
|
+
|
|
241
|
+
// write the api key it to the app's .env file
|
|
242
|
+
// writing to the .env file will trigger a server restart
|
|
243
|
+
setBuilderPublicApiKey(ctx);
|
|
244
|
+
|
|
245
|
+
// set the result to show the connected to builder step
|
|
246
|
+
result.html = `set public api key: ${ctx.credentials.publicApiKey}`;
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// returning from auth flow then redirected to this page
|
|
251
|
+
// show that we're connected to builder
|
|
252
|
+
// In the background we'll fire off a fetch() that will:
|
|
253
|
+
// - create the homepage
|
|
254
|
+
// - write the private auth key to the user's home directory
|
|
255
|
+
// - update the .env file with the public api key
|
|
256
|
+
// - updating the .env file will trigger a server restart
|
|
257
|
+
result.html = connectedToBuilderStep(ctx, url, true);
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// get the builder public api key from the .env file
|
|
262
|
+
const envApiKey = getBuilderApiKey(ctx);
|
|
263
|
+
if (!envApiKey) {
|
|
264
|
+
debug(`invalid api key in .env file`);
|
|
265
|
+
// we don't have a valid api key saved in the .env file
|
|
266
|
+
// respond with the first step of setup UI
|
|
267
|
+
result.html = setupOverviewStep(ctx, url);
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
debug(`public api key from the .env file: ${envApiKey}`);
|
|
272
|
+
|
|
273
|
+
// set the public api key in the process.env
|
|
274
|
+
// dotenv will normally do this on a fresh server start
|
|
275
|
+
process.env[BUILDER_API_KEY_ENV] = envApiKey;
|
|
276
|
+
|
|
277
|
+
// get the private api key from the user home dir builder config file
|
|
278
|
+
const appCredentials = getBuilderAppCredentials(ctx, envApiKey);
|
|
279
|
+
if (!appCredentials) {
|
|
280
|
+
debug(`invalid app credentials in user home dir`);
|
|
281
|
+
// we don't have a valid private key saved in the user home dir config
|
|
282
|
+
// respond with the first step of setup UI
|
|
283
|
+
result.html = setupOverviewStep(ctx, url);
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// remember the valid app credentials
|
|
288
|
+
ctx.credentials = appCredentials;
|
|
289
|
+
|
|
290
|
+
if (url.searchParams.get(BUILDER_SETUP_STEP_QS) === BUILDER_CONNECTED_STEP) {
|
|
291
|
+
debug(`step: ${BUILDER_CONNECTED_STEP}`);
|
|
292
|
+
// continue showing the connected step
|
|
293
|
+
// we may have reloaded the page and forgetten the context
|
|
294
|
+
// at this point don't do a background fetch() update
|
|
295
|
+
result.html = connectedToBuilderStep(ctx, url, false);
|
|
296
|
+
return result;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// it's possible that the builder auth is setup, but they still don't have a homepage somehow
|
|
300
|
+
// double check if this builder account already has a homepage created
|
|
301
|
+
const hasHomepage = await hasBuilderHomepage(ctx);
|
|
302
|
+
if (!hasHomepage) {
|
|
303
|
+
debug(`no homepage created yet`);
|
|
304
|
+
// there is no homepage content created yet for the valid public api key
|
|
305
|
+
// we don't have their private key, so let's redirect to the auth flow
|
|
306
|
+
// so we can get their private key and create the homepage for them
|
|
307
|
+
// respond with the first step of setup UI
|
|
308
|
+
result.html = setupOverviewStep(ctx, url);
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// awesome, we're all set
|
|
313
|
+
// the public key is saved correctly in the .env file
|
|
314
|
+
// and they have a homepage created
|
|
315
|
+
// no need to respond with the setup UI
|
|
316
|
+
// set isValid to true so we don't have to validate again
|
|
317
|
+
debug(`set is valid`);
|
|
318
|
+
ctx.isValid = true;
|
|
319
|
+
} catch (e: any) {
|
|
320
|
+
// collect the error and let the build decide how to handle it
|
|
321
|
+
result.errors = [e.message];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function hasBuilderHomepage(ctx: BuilderSetupContext) {
|
|
328
|
+
try {
|
|
329
|
+
const url = new URL(`https://cdn.builder.io/api/v3/content/page`);
|
|
330
|
+
url.searchParams.set(`apiKey`, ctx.credentials.publicApiKey);
|
|
331
|
+
url.searchParams.set(`url`, ctx.appBasePathname);
|
|
332
|
+
url.searchParams.set(`cachebust`, Math.random().toString());
|
|
333
|
+
|
|
334
|
+
const res = await requestJSON<{ results: any[] }>({
|
|
335
|
+
url,
|
|
336
|
+
method: 'GET',
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const hasHomepage = res.results.length > 0;
|
|
340
|
+
debug(`has homepage: ${hasHomepage}`);
|
|
341
|
+
return hasHomepage;
|
|
342
|
+
} catch (e) {
|
|
343
|
+
console.error(e);
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function createBuilderHomepage(ctx: BuilderSetupContext) {
|
|
349
|
+
debug(`create homepage`);
|
|
350
|
+
const url = new URL(`https://cdn.builder.io/api/v1/write/page`);
|
|
351
|
+
|
|
352
|
+
const body = getDefaultHomepage(ctx);
|
|
353
|
+
|
|
354
|
+
await requestJSON({
|
|
355
|
+
url,
|
|
356
|
+
method: 'POST',
|
|
357
|
+
headers: {
|
|
358
|
+
Authorization: `Bearer ${ctx.credentials.privateAuthKey}`,
|
|
359
|
+
},
|
|
360
|
+
body,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function setBuilderPublicApiKey(ctx: BuilderSetupContext) {
|
|
365
|
+
const comment = `# https://www.builder.io/c/docs/using-your-api-key`;
|
|
366
|
+
|
|
367
|
+
// check if we already have an .env file
|
|
368
|
+
if (existsSync(ctx.envFilePath)) {
|
|
369
|
+
// read the existing .env file
|
|
370
|
+
let envContent = readFileSync(ctx.envFilePath, 'utf-8');
|
|
371
|
+
if (envContent.includes(BUILDER_API_KEY_ENV)) {
|
|
372
|
+
// existing .env has a builder api key already, update its value
|
|
373
|
+
if (!envContent.includes(ctx.credentials.publicApiKey)) {
|
|
374
|
+
// existing .env has a builder api key, but it's not the same as the one we have
|
|
375
|
+
debug(`update public api key in existing .env file`);
|
|
376
|
+
envContent = envContent.replace(
|
|
377
|
+
new RegExp(`${BUILDER_API_KEY_ENV}=.*`),
|
|
378
|
+
`${BUILDER_API_KEY_ENV}=${ctx.credentials.publicApiKey}`
|
|
379
|
+
);
|
|
380
|
+
writeFileSync(ctx.envFilePath, envContent);
|
|
381
|
+
} else {
|
|
382
|
+
debug(`public api key already in existing .env file`);
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
// existing .env does not have a builder api key, append the key/value
|
|
386
|
+
debug(`append public api key to existing .env file`);
|
|
387
|
+
envContent += `\n\n${comment}\n${BUILDER_API_KEY_ENV}=${ctx.credentials.publicApiKey}\n\n`;
|
|
388
|
+
writeFileSync(ctx.envFilePath, envContent);
|
|
389
|
+
}
|
|
390
|
+
} else {
|
|
391
|
+
// create a new .env file since it doesn't exist yet
|
|
392
|
+
debug(`create .env file with public api key`);
|
|
393
|
+
const newEnv = [comment, `${BUILDER_API_KEY_ENV}=${ctx.credentials.publicApiKey}`, ``];
|
|
394
|
+
writeFileSync(ctx.envFilePath, newEnv.join('\n'));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function getBuilderApiKey(ctx: BuilderSetupContext) {
|
|
399
|
+
if (existsSync(ctx.envFilePath)) {
|
|
400
|
+
const envContent = readFileSync(ctx.envFilePath, 'utf-8');
|
|
401
|
+
|
|
402
|
+
const envs = envContent
|
|
403
|
+
.split('\n')
|
|
404
|
+
.map((l) => l.trim())
|
|
405
|
+
.filter((l) => l.length > 0)
|
|
406
|
+
.filter((l) => !l.startsWith('#'))
|
|
407
|
+
.filter((l) => l.includes('='))
|
|
408
|
+
.map((l) => {
|
|
409
|
+
const [key, value] = l.split('=');
|
|
410
|
+
return { key, value };
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
const builderApiKey = envs.find((e) => e.key === BUILDER_API_KEY_ENV);
|
|
414
|
+
if (typeof builderApiKey?.value === 'string' && builderApiKey.value.length > 0) {
|
|
415
|
+
if (builderApiKey.value !== 'YOUR_API_KEY') {
|
|
416
|
+
return builderApiKey.value;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function getBuilderAppCredentials(ctx: BuilderSetupContext, publicApiKey: string) {
|
|
424
|
+
try {
|
|
425
|
+
const credintialsFilePath = getCredentialsFilePath(ctx, publicApiKey);
|
|
426
|
+
const config = readFileSync(credintialsFilePath, 'utf-8');
|
|
427
|
+
return JSON.parse(config) as BuilderAppCredentials;
|
|
428
|
+
} catch (e: any) {
|
|
429
|
+
if (e.code === 'ENOENT') {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
throw e;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function setBuilderAppCredentials(ctx: BuilderSetupContext) {
|
|
437
|
+
debug(`set credentials`);
|
|
438
|
+
const credintialsFilePath = getCredentialsFilePath(ctx, ctx.credentials.publicApiKey);
|
|
439
|
+
mkdirSync(dirname(credintialsFilePath), { recursive: true });
|
|
440
|
+
writeFileSync(credintialsFilePath, JSON.stringify(ctx.credentials, null, 2));
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function getCredentialsFilePath(ctx: BuilderSetupContext, publicApiKey: string) {
|
|
444
|
+
return join(ctx.credentialsDirPath, `${publicApiKey}.json`);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function isPageRequest(url: URL) {
|
|
448
|
+
if (url.pathname.endsWith('/')) {
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const filename = url.pathname.split('/').pop();
|
|
453
|
+
if (filename) {
|
|
454
|
+
if (!filename.includes('.')) {
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const ext = filename.split('.').pop();
|
|
459
|
+
if (ext === 'html') {
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function interceptPageRequest(ctx: BuilderSetupContext) {
|
|
467
|
+
const result: BuilderIntegrationResult = {};
|
|
468
|
+
|
|
469
|
+
if (ctx.isValid) {
|
|
470
|
+
try {
|
|
471
|
+
result.html = `<script id="builder-dev-tools">(function(){\n${getBuilderDevToolsRuntime()}\n})();</script>
|
|
472
|
+
`;
|
|
473
|
+
} catch (e: any) {
|
|
474
|
+
result.errors = [e.message];
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return result;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function getBuilderDevToolsRuntime() {
|
|
482
|
+
return `
|
|
483
|
+
try {
|
|
484
|
+
const editButton = document.createElement('builder-dev-tools-edit-button');
|
|
485
|
+
editButton.style.display = 'none';
|
|
486
|
+
editButton.setAttribute('aria-hidden', 'true');
|
|
487
|
+
|
|
488
|
+
function onPointerOver(ev) {
|
|
489
|
+
const hoverElm = ev.target;
|
|
490
|
+
if (!hoverElm) {
|
|
491
|
+
hideEditButton();
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (hoverElm.closest('builder-dev-tools-edit-button')) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const contentElm = hoverElm.closest('[builder-content-id]');
|
|
500
|
+
const builderElm = hoverElm.closest('[builder-id]');
|
|
501
|
+
if (!contentElm || !builderElm) {
|
|
502
|
+
hideEditButton();
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const contentId = contentElm.getAttribute('builder-content-id');
|
|
507
|
+
const builderId = builderElm.getAttribute('builder-id');
|
|
508
|
+
if (!contentId || !builderId) {
|
|
509
|
+
hideEditButton();
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const rect = builderElm.getBoundingClientRect();
|
|
514
|
+
editButton.style.display = 'block';
|
|
515
|
+
editButton.style.top = (builderElm.offsetTop - 1) + 'px';
|
|
516
|
+
editButton.style.left = (builderElm.offsetLeft) + 'px';
|
|
517
|
+
editButton.style.width = (rect.width - 2) + 'px';
|
|
518
|
+
editButton.style.height = (rect.height - 2) + 'px';
|
|
519
|
+
editButton.setEditUrl(contentId, builderId);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function hideEditButton() {
|
|
523
|
+
editButton.style.display = 'none';
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
class BuilderDevToolsEditButton extends HTMLElement {
|
|
527
|
+
constructor() {
|
|
528
|
+
super();
|
|
529
|
+
this.shadow = this.attachShadow({ mode: 'open' });
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
connectedCallback() {
|
|
533
|
+
this.shadow.innerHTML = \`
|
|
534
|
+
<style>
|
|
535
|
+
:host {
|
|
536
|
+
--builder-blue: rgb(26, 115, 232);
|
|
537
|
+
box-sizing: border-box;
|
|
538
|
+
position: absolute;
|
|
539
|
+
z-index: 100;
|
|
540
|
+
user-select: none;
|
|
541
|
+
}
|
|
542
|
+
a {
|
|
543
|
+
display: inline-block;
|
|
544
|
+
box-sizing: border-box;
|
|
545
|
+
padding: 4px;
|
|
546
|
+
}
|
|
547
|
+
a span {
|
|
548
|
+
display: inline-block;
|
|
549
|
+
box-sizing: border-box;
|
|
550
|
+
padding: 4px 8px;
|
|
551
|
+
font-size: 12px;
|
|
552
|
+
font-weight: 500;
|
|
553
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
554
|
+
color: white;
|
|
555
|
+
background-color: var(--builder-blue);
|
|
556
|
+
border: 1px solid transparent;
|
|
557
|
+
border-radius: 3px;
|
|
558
|
+
text-align: center;
|
|
559
|
+
box-shadow: rgba(0, 0, 0, 0.2) 0px 1px 3px 0px, rgba(0, 0, 0, 0.14) 0px 1px 1px 0px, rgba(0, 0, 0, 0.12) 0px 2px 1px -1px;
|
|
560
|
+
text-decoration: none;
|
|
561
|
+
pointer-events: none;
|
|
562
|
+
}
|
|
563
|
+
a:hover span {
|
|
564
|
+
border-color: var(--builder-blue);
|
|
565
|
+
color: var(--builder-blue);
|
|
566
|
+
background: white;
|
|
567
|
+
}
|
|
568
|
+
.outline {
|
|
569
|
+
position: absolute;
|
|
570
|
+
top: 0;
|
|
571
|
+
left: 0;
|
|
572
|
+
width: 100%;
|
|
573
|
+
height: 100%;
|
|
574
|
+
pointer-events: none;
|
|
575
|
+
border: 1px solid var(--builder-blue);
|
|
576
|
+
}
|
|
577
|
+
</style>
|
|
578
|
+
<a id="edit" target="_blank"><span>Edit</span></a>
|
|
579
|
+
<div class="outline"></div>
|
|
580
|
+
\`;
|
|
581
|
+
|
|
582
|
+
this.editButton = this.shadow.getElementById('edit');
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
setEditUrl(contentId, builderId) {
|
|
586
|
+
const pathname = '/content/' + contentId + '/edit';
|
|
587
|
+
const url = new URL(pathname, 'https://builder.io');
|
|
588
|
+
url.searchParams.set('selectedBlock', builderId);
|
|
589
|
+
this.editButton.href = url.href;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
customElements.define('builder-dev-tools-edit-button', BuilderDevToolsEditButton);
|
|
594
|
+
document.body.appendChild(editButton);
|
|
595
|
+
|
|
596
|
+
document.addEventListener('pointerover', onPointerOver, { passive: true });
|
|
597
|
+
document.addEventListener('pointerleave', hideEditButton, { passive: true });
|
|
598
|
+
document.addEventListener('pointercancel', hideEditButton, { passive: true });
|
|
599
|
+
document.addEventListener('visibilitychange', hideEditButton, { passive: true });
|
|
600
|
+
|
|
601
|
+
window.addEventListener('popstate', hideEditButton, { passive: true });
|
|
602
|
+
|
|
603
|
+
const originalPushState = history.pushState;
|
|
604
|
+
history.pushState = function () {
|
|
605
|
+
hideEditButton();
|
|
606
|
+
originalPushState.apply(this, arguments);
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
const originalReplaceState = history.replaceState;
|
|
610
|
+
history.replaceState = function () {
|
|
611
|
+
hideEditButton();
|
|
612
|
+
originalReplaceState.apply(this, arguments);
|
|
613
|
+
};
|
|
614
|
+
} catch (e) {
|
|
615
|
+
console.error(e);
|
|
616
|
+
}
|
|
617
|
+
`;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function createBuilderSetup(opts: CreateSetupOptions) {
|
|
621
|
+
const ctx: BuilderSetupContext = {
|
|
622
|
+
...opts,
|
|
623
|
+
credentials: {
|
|
624
|
+
publicApiKey: '',
|
|
625
|
+
privateAuthKey: '',
|
|
626
|
+
},
|
|
627
|
+
isValid: false,
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
const builder: BuilderIntegration = {
|
|
631
|
+
intercept: () => interceptPageRequest(ctx),
|
|
632
|
+
validate: (url) => validateBuilderIntegration(ctx, url),
|
|
633
|
+
};
|
|
634
|
+
return builder;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Get the auth url to connect to builder, and the url to redirect to after connecting
|
|
639
|
+
*/
|
|
640
|
+
function getAuthConnectUrl(ctx: BuilderSetupContext, url: URL) {
|
|
641
|
+
const authUrl = new URL(`/cli-auth`, `https://builder.io`);
|
|
642
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
643
|
+
authUrl.searchParams.set('cli', 'true');
|
|
644
|
+
authUrl.searchParams.set('client_id', ctx.clientId);
|
|
645
|
+
authUrl.searchParams.set('host', ctx.clientHostname);
|
|
646
|
+
|
|
647
|
+
const returnUrl = getAppBaseUrl(ctx, url).href;
|
|
648
|
+
authUrl.searchParams.set('redirect_url', returnUrl);
|
|
649
|
+
|
|
650
|
+
return authUrl.href;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function getAppBaseUrl(ctx: BuilderSetupContext, url: URL) {
|
|
654
|
+
return new URL(ctx.appBasePathname, url.origin);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function getConnectedStepUrl(ctx: BuilderSetupContext, url: URL) {
|
|
658
|
+
const appBaseUrl = getAppBaseUrl(ctx, url);
|
|
659
|
+
appBaseUrl.searchParams.set(BUILDER_SETUP_STEP_QS, BUILDER_CONNECTED_STEP);
|
|
660
|
+
return appBaseUrl.pathname + appBaseUrl.search;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function getBackgroundUpdateUrl(ctx: BuilderSetupContext, url: URL) {
|
|
664
|
+
const appBaseUrl = getAppBaseUrl(ctx, url);
|
|
665
|
+
appBaseUrl.searchParams.set(BUILDER_SETUP_STEP_QS, BUILDER_UPDATE_STEP);
|
|
666
|
+
appBaseUrl.searchParams.set(BUILDER_PUBLIC_API_KEY_QS, ctx.credentials.publicApiKey);
|
|
667
|
+
appBaseUrl.searchParams.set(BUILDER_PRIVATE_AUTH_KEY_QS, ctx.credentials.privateAuthKey);
|
|
668
|
+
return appBaseUrl.pathname + appBaseUrl.search;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function requestJSON<T>(opts: RequestOptions) {
|
|
672
|
+
return new Promise<T>((resolve, reject) => {
|
|
673
|
+
const req = request(
|
|
674
|
+
{
|
|
675
|
+
protocol: opts.url.protocol,
|
|
676
|
+
host: opts.url.host,
|
|
677
|
+
port: opts.url.port,
|
|
678
|
+
path: opts.url.pathname + opts.url.search,
|
|
679
|
+
method: opts.method,
|
|
680
|
+
headers: opts.headers,
|
|
681
|
+
},
|
|
682
|
+
(res) => {
|
|
683
|
+
let data = '';
|
|
684
|
+
res.on('data', (chunk) => {
|
|
685
|
+
data += chunk;
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
res.on('end', () => {
|
|
689
|
+
if (!res.statusCode || res.statusCode > 299) {
|
|
690
|
+
reject(`Request to ${res.url} failed with status ${res.statusCode}`);
|
|
691
|
+
} else {
|
|
692
|
+
if (
|
|
693
|
+
typeof res.headers['content-type'] !== 'string' ||
|
|
694
|
+
!res.headers['content-type'].includes('application/json')
|
|
695
|
+
) {
|
|
696
|
+
reject(`Response from ${res.url} content-type is ${res.headers['content-type']}`);
|
|
697
|
+
} else {
|
|
698
|
+
try {
|
|
699
|
+
resolve(JSON.parse(data));
|
|
700
|
+
} catch (err) {
|
|
701
|
+
reject(`Response from ${res.url} is not valid JSON: ${data}\n${err}`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
).on('error', reject);
|
|
708
|
+
|
|
709
|
+
if (opts.body) {
|
|
710
|
+
req.setHeader('Content-Type', 'application/json');
|
|
711
|
+
req.write(opts.body);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
req.end();
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const BUILDER_API_KEY_ENV = `PUBLIC_BUILDER_API_KEY`;
|
|
719
|
+
const BUILDER_PUBLIC_API_KEY_QS = `api-key`;
|
|
720
|
+
const BUILDER_PRIVATE_AUTH_KEY_QS = `p-key`;
|
|
721
|
+
const BUILDER_SETUP_STEP_QS = `builder-connect`;
|
|
722
|
+
const BUILDER_UPDATE_STEP = `update`;
|
|
723
|
+
const BUILDER_CONNECTED_STEP = `connected`;
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Vite plugin that adds builder.io setup UI
|
|
727
|
+
*/
|
|
728
|
+
export function builderDevTools(opts: BuilderioOptions = {}): Plugin {
|
|
729
|
+
let builder: BuilderIntegration | undefined;
|
|
730
|
+
let logger: Logger | undefined;
|
|
731
|
+
let port: number | undefined;
|
|
732
|
+
|
|
733
|
+
return {
|
|
734
|
+
name: 'builderDevTools',
|
|
735
|
+
|
|
736
|
+
configResolved(config) {
|
|
737
|
+
logger = config.logger;
|
|
738
|
+
port = config.server.port;
|
|
739
|
+
builder = createBuilderSetup({
|
|
740
|
+
appBasePathname: config.base,
|
|
741
|
+
clientHostname: hostname(),
|
|
742
|
+
clientId: 'create-qwik-app',
|
|
743
|
+
credentialsDirPath: join(homedir(), `.config`, `builder`),
|
|
744
|
+
envFilePath: opts.envFilePath || join(config.root, `.env`),
|
|
745
|
+
framework: 'Qwik',
|
|
746
|
+
});
|
|
747
|
+
},
|
|
748
|
+
|
|
749
|
+
configureServer(server) {
|
|
750
|
+
server.middlewares.use(async (req, res, next) => {
|
|
751
|
+
req.socket.address();
|
|
752
|
+
|
|
753
|
+
const orgResponseEnd = res.end;
|
|
754
|
+
res.end = function (...args: any[]) {
|
|
755
|
+
if (builder) {
|
|
756
|
+
const contentType = (res.getHeader('Content-Type') || '').toString();
|
|
757
|
+
|
|
758
|
+
if (contentType.includes('text/html')) {
|
|
759
|
+
const result = builder.intercept();
|
|
760
|
+
if (result.errors && logger) {
|
|
761
|
+
result.errors.map((e) => logger!.error(e));
|
|
762
|
+
}
|
|
763
|
+
if (result.html) {
|
|
764
|
+
res.write(result.html);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
return orgResponseEnd.apply(this, args);
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
// add Vite dev server middleware that
|
|
773
|
+
// shows builder setup UI if needed
|
|
774
|
+
if (builder) {
|
|
775
|
+
const url = getNodeHttpUrl(port, req);
|
|
776
|
+
|
|
777
|
+
const result = await builder.validate(url);
|
|
778
|
+
|
|
779
|
+
if (result.errors && logger) {
|
|
780
|
+
result.errors.map((e) => logger!.error(e));
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (result.html) {
|
|
784
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
785
|
+
res.setHeader('Cache-Control', 'max-age=0, no-cache, no-store');
|
|
786
|
+
res.setHeader('X-Builderio-Vite-Dev-Server', 'true');
|
|
787
|
+
res.end(result.html);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
next();
|
|
792
|
+
});
|
|
793
|
+
},
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function getNodeHttpUrl(port: number | undefined, req: IncomingMessage) {
|
|
798
|
+
const a = req.socket.address();
|
|
799
|
+
const address = 'address' in a && typeof a.address === 'string' ? a.address : 'localhost';
|
|
800
|
+
port = 'port' in a && typeof a.port === 'number' ? a.port : port;
|
|
801
|
+
|
|
802
|
+
return new URL(req.url || '/', `http://${address}:${port}`);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function debug(...args: any[]) {
|
|
806
|
+
if (process.env.DEBUG) {
|
|
807
|
+
// eslint-disable-next-line no-console
|
|
808
|
+
console.debug('[builder.io]', ...args);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
export interface BuilderioOptions {
|
|
813
|
+
/**
|
|
814
|
+
* Absolute path to the project's `.env` file.
|
|
815
|
+
*/
|
|
816
|
+
envFilePath?: string;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
interface CreateSetupOptions {
|
|
820
|
+
appBasePathname: string;
|
|
821
|
+
clientHostname: string;
|
|
822
|
+
clientId: string;
|
|
823
|
+
credentialsDirPath: string;
|
|
824
|
+
envFilePath: string;
|
|
825
|
+
framework: string;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
interface BuilderSetupContext extends CreateSetupOptions {
|
|
829
|
+
isValid: boolean;
|
|
830
|
+
credentials: BuilderAppCredentials;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
interface BuilderIntegration {
|
|
834
|
+
intercept: () => BuilderIntegrationResult;
|
|
835
|
+
validate: (url: URL) => Promise<BuilderIntegrationResult>;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
interface BuilderIntegrationResult {
|
|
839
|
+
html?: string;
|
|
840
|
+
errors?: string[];
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
interface BuilderAppCredentials {
|
|
844
|
+
publicApiKey: string;
|
|
845
|
+
privateAuthKey: string;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
interface RequestOptions {
|
|
849
|
+
url: URL;
|
|
850
|
+
headers?: Record<string, string>;
|
|
851
|
+
method?: string;
|
|
852
|
+
body?: any;
|
|
853
|
+
}
|