@allegrocdp/preview 0.1.0-dev.11 → 0.1.0-dev.12
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/cli.js +73 -2
- package/dist/server.js +74 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7,6 +7,8 @@ import { Command } from "commander";
|
|
|
7
7
|
import chokidar from "chokidar";
|
|
8
8
|
import express from "express";
|
|
9
9
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
|
|
10
|
+
import { get as httpGet } from "http";
|
|
11
|
+
import { get as httpsGet } from "https";
|
|
10
12
|
import open from "open";
|
|
11
13
|
import { basename, isAbsolute, join, relative, resolve } from "path";
|
|
12
14
|
import { fileURLToPath } from "url";
|
|
@@ -158,6 +160,60 @@ function normalizeTenantSdkUrl(raw) {
|
|
|
158
160
|
const withProtocol = /^https?:\/\//.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
159
161
|
return withProtocol.replace(/\/+$/, "") + "/client.js";
|
|
160
162
|
}
|
|
163
|
+
var previewCssCache = /* @__PURE__ */ new Map();
|
|
164
|
+
var PREVIEW_CSS_TTL_MS = 6e4;
|
|
165
|
+
function escapeStyle(css) {
|
|
166
|
+
return css.replace(/<\/(style)/gi, "<\\/$1");
|
|
167
|
+
}
|
|
168
|
+
function isLocalHost(hostname) {
|
|
169
|
+
const name = hostname.toLowerCase();
|
|
170
|
+
return name === "localhost" || name.endsWith(".localhost") || name.endsWith(".test") || name === "127.0.0.1" || name === "::1";
|
|
171
|
+
}
|
|
172
|
+
function getJson(target) {
|
|
173
|
+
return new Promise((resolve2, reject) => {
|
|
174
|
+
const handleResponse = (res) => {
|
|
175
|
+
const status = res.statusCode ?? 0;
|
|
176
|
+
if (status < 200 || status >= 300) {
|
|
177
|
+
res.resume();
|
|
178
|
+
reject(new Error(`HTTP ${status}`));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
let body = "";
|
|
182
|
+
res.setEncoding("utf-8");
|
|
183
|
+
res.on("data", (chunk) => body += chunk);
|
|
184
|
+
res.on("end", () => {
|
|
185
|
+
try {
|
|
186
|
+
resolve2(JSON.parse(body));
|
|
187
|
+
} catch (err) {
|
|
188
|
+
reject(err);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
const req = target.protocol === "https:" ? httpsGet(target, { rejectUnauthorized: !isLocalHost(target.hostname) }, handleResponse) : httpGet(target, handleResponse);
|
|
193
|
+
req.on("error", reject);
|
|
194
|
+
req.setTimeout(5e3, () => req.destroy(new Error("Request timed out")));
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
async function fetchPreviewCss(sdkUrl) {
|
|
198
|
+
let endpoint;
|
|
199
|
+
try {
|
|
200
|
+
endpoint = new URL("/api/preview-css", sdkUrl);
|
|
201
|
+
} catch {
|
|
202
|
+
return "";
|
|
203
|
+
}
|
|
204
|
+
const cached = previewCssCache.get(endpoint.origin);
|
|
205
|
+
if (cached && Date.now() - cached.fetchedAt < PREVIEW_CSS_TTL_MS) {
|
|
206
|
+
return cached.css;
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
const data = await getJson(endpoint);
|
|
210
|
+
const css = typeof data.css === "string" ? data.css : "";
|
|
211
|
+
previewCssCache.set(endpoint.origin, { css, fetchedAt: Date.now() });
|
|
212
|
+
return css;
|
|
213
|
+
} catch {
|
|
214
|
+
return cached?.css ?? "";
|
|
215
|
+
}
|
|
216
|
+
}
|
|
161
217
|
var COOKIE_NOOP_SCRIPT = `(function(){var _jar={};Object.defineProperty(document,'cookie',{configurable:true,get:function(){return Object.keys(_jar).map(function(k){return k+'='+_jar[k];}).join('; ');},set:function(str){var parts=str.split(';');var kv=parts[0].trim();var eq=kv.indexOf('=');var name=kv.slice(0,eq).trim();var val=kv.slice(eq+1).trim();var maxAge=null;for(var i=1;i<parts.length;i++){var p=parts[i].trim().toLowerCase();if(p.indexOf('max-age=')===0){maxAge=parseInt(p.split('=')[1],10);}}if(maxAge!==null&&maxAge<=0){delete _jar[name];}else{_jar[name]=val;}}});})();`;
|
|
162
218
|
function buildPreviewData(options) {
|
|
163
219
|
const json = JSON.stringify({
|
|
@@ -170,6 +226,9 @@ function buildPreviewData(options) {
|
|
|
170
226
|
});
|
|
171
227
|
return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
172
228
|
}
|
|
229
|
+
function previewCssTag(css) {
|
|
230
|
+
return css ? `<style data-allegro-preview-css>${escapeStyle(css)}</style>` : "";
|
|
231
|
+
}
|
|
173
232
|
function standaloneShell(options) {
|
|
174
233
|
const cookieScript = options.blockCookies ?? true ? `<script>${COOKIE_NOOP_SCRIPT}</script>` : "";
|
|
175
234
|
return `<!DOCTYPE html>
|
|
@@ -184,6 +243,7 @@ function standaloneShell(options) {
|
|
|
184
243
|
body { margin: 0; }
|
|
185
244
|
[data-allegro-interaction] { display: block; }
|
|
186
245
|
</style>
|
|
246
|
+
${previewCssTag(options.previewCss)}
|
|
187
247
|
</head>
|
|
188
248
|
<body>
|
|
189
249
|
<script id="allegro-preview-data" type="application/json">${buildPreviewData(options)}</script>
|
|
@@ -212,6 +272,7 @@ function articleShell(options) {
|
|
|
212
272
|
p { margin: 0 0 1.1em; font-size: 1rem; overflow-wrap: break-word; }
|
|
213
273
|
[data-allegro-interaction] { display: block; margin: 24px 0; }
|
|
214
274
|
</style>
|
|
275
|
+
${previewCssTag(options.previewCss)}
|
|
215
276
|
</head>
|
|
216
277
|
<body>
|
|
217
278
|
<script id="allegro-preview-data" type="application/json">${buildPreviewData(options)}</script>
|
|
@@ -389,7 +450,7 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
389
450
|
sseClients.delete(res);
|
|
390
451
|
});
|
|
391
452
|
});
|
|
392
|
-
app.get("/preview", (req, res) => {
|
|
453
|
+
app.get("/preview", async (req, res) => {
|
|
393
454
|
const slug = typeof req.query.slug === "string" ? req.query.slug : "";
|
|
394
455
|
const tab = typeof req.query.tab === "string" ? req.query.tab : "standalone";
|
|
395
456
|
const sdkParam = typeof req.query.sdk === "string" ? req.query.sdk : void 0;
|
|
@@ -397,6 +458,14 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
397
458
|
res.status(400).send("Missing slug");
|
|
398
459
|
return;
|
|
399
460
|
}
|
|
461
|
+
if (sdkParam !== void 0) {
|
|
462
|
+
try {
|
|
463
|
+
new URL(sdkParam);
|
|
464
|
+
} catch {
|
|
465
|
+
res.status(400).send("Invalid sdk URL");
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
400
469
|
if (slug !== "." && !templates.some((t) => t.slug === slug)) {
|
|
401
470
|
res.status(404).send("Template not found");
|
|
402
471
|
return;
|
|
@@ -422,6 +491,7 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
422
491
|
}
|
|
423
492
|
const effectiveSdkUrl = sdkParam ?? (tenant ? normalizeTenantSdkUrl(tenant) : void 0);
|
|
424
493
|
const blockCookies = req.query.blockCookies !== "false";
|
|
494
|
+
const previewCss = effectiveSdkUrl ? await fetchPreviewCss(effectiveSdkUrl) : "";
|
|
425
495
|
const shellOptions = {
|
|
426
496
|
slug,
|
|
427
497
|
html: templateFiles.html,
|
|
@@ -430,7 +500,8 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
430
500
|
fieldValues,
|
|
431
501
|
tab,
|
|
432
502
|
sdkUrl: effectiveSdkUrl,
|
|
433
|
-
blockCookies
|
|
503
|
+
blockCookies,
|
|
504
|
+
previewCss
|
|
434
505
|
};
|
|
435
506
|
const rendered = tab === "standalone" ? standaloneShell(shellOptions) : articleShell(shellOptions);
|
|
436
507
|
res.setHeader("Cache-Control", "no-store");
|
package/dist/server.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import chokidar from "chokidar";
|
|
5
5
|
import express from "express";
|
|
6
6
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
|
|
7
|
+
import { get as httpGet } from "http";
|
|
8
|
+
import { get as httpsGet } from "https";
|
|
7
9
|
import open from "open";
|
|
8
10
|
import { basename, isAbsolute, join, relative, resolve } from "path";
|
|
9
11
|
import { fileURLToPath } from "url";
|
|
@@ -155,6 +157,60 @@ function normalizeTenantSdkUrl(raw) {
|
|
|
155
157
|
const withProtocol = /^https?:\/\//.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
156
158
|
return withProtocol.replace(/\/+$/, "") + "/client.js";
|
|
157
159
|
}
|
|
160
|
+
var previewCssCache = /* @__PURE__ */ new Map();
|
|
161
|
+
var PREVIEW_CSS_TTL_MS = 6e4;
|
|
162
|
+
function escapeStyle(css) {
|
|
163
|
+
return css.replace(/<\/(style)/gi, "<\\/$1");
|
|
164
|
+
}
|
|
165
|
+
function isLocalHost(hostname) {
|
|
166
|
+
const name = hostname.toLowerCase();
|
|
167
|
+
return name === "localhost" || name.endsWith(".localhost") || name.endsWith(".test") || name === "127.0.0.1" || name === "::1";
|
|
168
|
+
}
|
|
169
|
+
function getJson(target) {
|
|
170
|
+
return new Promise((resolve2, reject) => {
|
|
171
|
+
const handleResponse = (res) => {
|
|
172
|
+
const status = res.statusCode ?? 0;
|
|
173
|
+
if (status < 200 || status >= 300) {
|
|
174
|
+
res.resume();
|
|
175
|
+
reject(new Error(`HTTP ${status}`));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
let body = "";
|
|
179
|
+
res.setEncoding("utf-8");
|
|
180
|
+
res.on("data", (chunk) => body += chunk);
|
|
181
|
+
res.on("end", () => {
|
|
182
|
+
try {
|
|
183
|
+
resolve2(JSON.parse(body));
|
|
184
|
+
} catch (err) {
|
|
185
|
+
reject(err);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
const req = target.protocol === "https:" ? httpsGet(target, { rejectUnauthorized: !isLocalHost(target.hostname) }, handleResponse) : httpGet(target, handleResponse);
|
|
190
|
+
req.on("error", reject);
|
|
191
|
+
req.setTimeout(5e3, () => req.destroy(new Error("Request timed out")));
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
async function fetchPreviewCss(sdkUrl) {
|
|
195
|
+
let endpoint;
|
|
196
|
+
try {
|
|
197
|
+
endpoint = new URL("/api/preview-css", sdkUrl);
|
|
198
|
+
} catch {
|
|
199
|
+
return "";
|
|
200
|
+
}
|
|
201
|
+
const cached = previewCssCache.get(endpoint.origin);
|
|
202
|
+
if (cached && Date.now() - cached.fetchedAt < PREVIEW_CSS_TTL_MS) {
|
|
203
|
+
return cached.css;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
const data = await getJson(endpoint);
|
|
207
|
+
const css = typeof data.css === "string" ? data.css : "";
|
|
208
|
+
previewCssCache.set(endpoint.origin, { css, fetchedAt: Date.now() });
|
|
209
|
+
return css;
|
|
210
|
+
} catch {
|
|
211
|
+
return cached?.css ?? "";
|
|
212
|
+
}
|
|
213
|
+
}
|
|
158
214
|
var COOKIE_NOOP_SCRIPT = `(function(){var _jar={};Object.defineProperty(document,'cookie',{configurable:true,get:function(){return Object.keys(_jar).map(function(k){return k+'='+_jar[k];}).join('; ');},set:function(str){var parts=str.split(';');var kv=parts[0].trim();var eq=kv.indexOf('=');var name=kv.slice(0,eq).trim();var val=kv.slice(eq+1).trim();var maxAge=null;for(var i=1;i<parts.length;i++){var p=parts[i].trim().toLowerCase();if(p.indexOf('max-age=')===0){maxAge=parseInt(p.split('=')[1],10);}}if(maxAge!==null&&maxAge<=0){delete _jar[name];}else{_jar[name]=val;}}});})();`;
|
|
159
215
|
function buildPreviewData(options) {
|
|
160
216
|
const json = JSON.stringify({
|
|
@@ -167,6 +223,9 @@ function buildPreviewData(options) {
|
|
|
167
223
|
});
|
|
168
224
|
return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
169
225
|
}
|
|
226
|
+
function previewCssTag(css) {
|
|
227
|
+
return css ? `<style data-allegro-preview-css>${escapeStyle(css)}</style>` : "";
|
|
228
|
+
}
|
|
170
229
|
function standaloneShell(options) {
|
|
171
230
|
const cookieScript = options.blockCookies ?? true ? `<script>${COOKIE_NOOP_SCRIPT}</script>` : "";
|
|
172
231
|
return `<!DOCTYPE html>
|
|
@@ -181,6 +240,7 @@ function standaloneShell(options) {
|
|
|
181
240
|
body { margin: 0; }
|
|
182
241
|
[data-allegro-interaction] { display: block; }
|
|
183
242
|
</style>
|
|
243
|
+
${previewCssTag(options.previewCss)}
|
|
184
244
|
</head>
|
|
185
245
|
<body>
|
|
186
246
|
<script id="allegro-preview-data" type="application/json">${buildPreviewData(options)}</script>
|
|
@@ -209,6 +269,7 @@ function articleShell(options) {
|
|
|
209
269
|
p { margin: 0 0 1.1em; font-size: 1rem; overflow-wrap: break-word; }
|
|
210
270
|
[data-allegro-interaction] { display: block; margin: 24px 0; }
|
|
211
271
|
</style>
|
|
272
|
+
${previewCssTag(options.previewCss)}
|
|
212
273
|
</head>
|
|
213
274
|
<body>
|
|
214
275
|
<script id="allegro-preview-data" type="application/json">${buildPreviewData(options)}</script>
|
|
@@ -386,7 +447,7 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
386
447
|
sseClients.delete(res);
|
|
387
448
|
});
|
|
388
449
|
});
|
|
389
|
-
app.get("/preview", (req, res) => {
|
|
450
|
+
app.get("/preview", async (req, res) => {
|
|
390
451
|
const slug = typeof req.query.slug === "string" ? req.query.slug : "";
|
|
391
452
|
const tab = typeof req.query.tab === "string" ? req.query.tab : "standalone";
|
|
392
453
|
const sdkParam = typeof req.query.sdk === "string" ? req.query.sdk : void 0;
|
|
@@ -394,6 +455,14 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
394
455
|
res.status(400).send("Missing slug");
|
|
395
456
|
return;
|
|
396
457
|
}
|
|
458
|
+
if (sdkParam !== void 0) {
|
|
459
|
+
try {
|
|
460
|
+
new URL(sdkParam);
|
|
461
|
+
} catch {
|
|
462
|
+
res.status(400).send("Invalid sdk URL");
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
397
466
|
if (slug !== "." && !templates.some((t) => t.slug === slug)) {
|
|
398
467
|
res.status(404).send("Template not found");
|
|
399
468
|
return;
|
|
@@ -419,6 +488,7 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
419
488
|
}
|
|
420
489
|
const effectiveSdkUrl = sdkParam ?? (tenant ? normalizeTenantSdkUrl(tenant) : void 0);
|
|
421
490
|
const blockCookies = req.query.blockCookies !== "false";
|
|
491
|
+
const previewCss = effectiveSdkUrl ? await fetchPreviewCss(effectiveSdkUrl) : "";
|
|
422
492
|
const shellOptions = {
|
|
423
493
|
slug,
|
|
424
494
|
html: templateFiles.html,
|
|
@@ -427,7 +497,8 @@ async function startServer(templatePath, port = 0, tenant = "") {
|
|
|
427
497
|
fieldValues,
|
|
428
498
|
tab,
|
|
429
499
|
sdkUrl: effectiveSdkUrl,
|
|
430
|
-
blockCookies
|
|
500
|
+
blockCookies,
|
|
501
|
+
previewCss
|
|
431
502
|
};
|
|
432
503
|
const rendered = tab === "standalone" ? standaloneShell(shellOptions) : articleShell(shellOptions);
|
|
433
504
|
res.setHeader("Cache-Control", "no-store");
|
|
@@ -522,5 +593,6 @@ allegro-preview \u2192 ${url}
|
|
|
522
593
|
void open(url);
|
|
523
594
|
}
|
|
524
595
|
export {
|
|
596
|
+
escapeStyle,
|
|
525
597
|
startServer
|
|
526
598
|
};
|
package/package.json
CHANGED