@extenshi/cli 0.20.0 → 0.21.0
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 +55 -0
- package/dist/cli.js.map +1 -1
- package/dist/expected-error.d.ts +9 -0
- package/dist/expected-error.d.ts.map +1 -1
- package/dist/expected-error.js +16 -1
- package/dist/expected-error.js.map +1 -1
- package/dist/json-ld.d.ts +89 -0
- package/dist/json-ld.d.ts.map +1 -0
- package/dist/json-ld.js +118 -0
- package/dist/json-ld.js.map +1 -0
- package/dist/landing-page.d.ts +123 -0
- package/dist/landing-page.d.ts.map +1 -0
- package/dist/landing-page.js +405 -0
- package/dist/landing-page.js.map +1 -0
- package/dist/page.d.ts +131 -0
- package/dist/page.d.ts.map +1 -0
- package/dist/page.js +197 -0
- package/dist/page.js.map +1 -0
- package/dist/pay-contract.d.ts +11 -11
- package/dist/pay-contract.d.ts.map +1 -1
- package/dist/pay-contract.js +11 -11
- package/dist/pay-contract.js.map +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +3 -0
- package/dist/telemetry.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Generator: an extension's landing / homepage as ONE static HTML file.
|
|
3
|
+
*
|
|
4
|
+
* No JavaScript in the output — pure HTML/CSS, so it deploys anywhere
|
|
5
|
+
* (GitHub Pages, Netlify, S3) and trivially passes CSP. Store CTAs render
|
|
6
|
+
* only for the stores the developer actually filled in.
|
|
7
|
+
*
|
|
8
|
+
* Every user-supplied string is HTML-escaped, every link is scheme-clamped
|
|
9
|
+
* (safeHref: http(s) only; the support link may also be a single mailto:
|
|
10
|
+
* address) and every image source is clamped to https, a
|
|
11
|
+
* relative path or a raster `data:image/…;base64` URL (safeImageSrc). An
|
|
12
|
+
* inline SVG logo is validated (landingLogoSvgProblem) and then embedded
|
|
13
|
+
* through `<img src="data:image/svg+xml,…">`, where browsers never run
|
|
14
|
+
* scripts or fetch external resources — so even a validator miss cannot
|
|
15
|
+
* script the page.
|
|
16
|
+
*
|
|
17
|
+
* SINGLE SOURCE, THREE COPIES:
|
|
18
|
+
* shared-types/landing-page.ts (canonical — edit here; dojo re-exports it)
|
|
19
|
+
* tools/extenshi-mcp/src/landing-page.ts (copy for the published @extenshi/mcp)
|
|
20
|
+
* tools/extenshi-cli/src/landing-page.ts (copy for the published @extenshi/cli)
|
|
21
|
+
* The published packages cannot import workspace shared-types at runtime.
|
|
22
|
+
* The copies differ from this file ONLY in the json-ld import specifier
|
|
23
|
+
* (`./json-ld.js` there, the package specifier here — see
|
|
24
|
+
* module-specifiers.test.ts for why shared-types may not use relative ones).
|
|
25
|
+
* `landing-page-sync.test.ts` in both packages fails when a copy drifts.
|
|
26
|
+
* After editing, run:
|
|
27
|
+
* sed '/^import/s#shared-types/#./#' shared-types/landing-page.ts > tools/extenshi-mcp/src/landing-page.ts
|
|
28
|
+
* sed '/^import/s#shared-types/#./#' shared-types/landing-page.ts > tools/extenshi-cli/src/landing-page.ts
|
|
29
|
+
*/
|
|
30
|
+
import { jsonLdFromLanding, jsonLdScriptTag } from './json-ld.js';
|
|
31
|
+
/** Input limits shared by the MCP tool and CLI schemas. */
|
|
32
|
+
export const LANDING_LIMITS = {
|
|
33
|
+
extensionName: 120,
|
|
34
|
+
tagline: 200,
|
|
35
|
+
description: 2000,
|
|
36
|
+
url: 2000,
|
|
37
|
+
features: 12,
|
|
38
|
+
featureTitle: 120,
|
|
39
|
+
featureDescription: 500,
|
|
40
|
+
screenshots: 8,
|
|
41
|
+
screenshotAlt: 200,
|
|
42
|
+
logoSvgBytes: 100_000,
|
|
43
|
+
logoUrl: 400_000,
|
|
44
|
+
};
|
|
45
|
+
export const DEFAULT_LANDING_FORM = {
|
|
46
|
+
extensionName: '',
|
|
47
|
+
tagline: '',
|
|
48
|
+
description: '',
|
|
49
|
+
accentColor: '#5e5ce6',
|
|
50
|
+
theme: 'dark',
|
|
51
|
+
chromeUrl: '',
|
|
52
|
+
firefoxUrl: '',
|
|
53
|
+
edgeUrl: '',
|
|
54
|
+
privacyPolicyUrl: '',
|
|
55
|
+
supportUrl: '',
|
|
56
|
+
features: [
|
|
57
|
+
{ title: '', description: '' },
|
|
58
|
+
{ title: '', description: '' },
|
|
59
|
+
{ title: '', description: '' },
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Rebuild the editor form from a saved `page-generator` row (or the project
|
|
64
|
+
* name when nothing is saved). Called during render — not in an effect —
|
|
65
|
+
* so the live preview iframe mounts with this payload, not the empty default.
|
|
66
|
+
* An older row that predates `features` (or the optional fields) still yields
|
|
67
|
+
* a full form.
|
|
68
|
+
*/
|
|
69
|
+
export function hydrateLandingForm(saved, projectName) {
|
|
70
|
+
if (saved?.form) {
|
|
71
|
+
const features = Array.isArray(saved.form.features) ? saved.form.features : DEFAULT_LANDING_FORM.features;
|
|
72
|
+
const { screenshots, ...rest } = saved.form;
|
|
73
|
+
const form = { ...DEFAULT_LANDING_FORM, ...rest, features };
|
|
74
|
+
if (Array.isArray(screenshots))
|
|
75
|
+
form.screenshots = screenshots;
|
|
76
|
+
return form;
|
|
77
|
+
}
|
|
78
|
+
const name = projectName?.trim();
|
|
79
|
+
if (name)
|
|
80
|
+
return { ...DEFAULT_LANDING_FORM, extensionName: name };
|
|
81
|
+
return { ...DEFAULT_LANDING_FORM, features: [...DEFAULT_LANDING_FORM.features] };
|
|
82
|
+
}
|
|
83
|
+
export function landingFormToInput(form, logoSvg) {
|
|
84
|
+
return {
|
|
85
|
+
extensionName: form.extensionName,
|
|
86
|
+
tagline: form.tagline,
|
|
87
|
+
description: form.description,
|
|
88
|
+
accentColor: form.accentColor,
|
|
89
|
+
theme: form.theme,
|
|
90
|
+
storeUrls: { chrome: form.chromeUrl, firefox: form.firefoxUrl, edge: form.edgeUrl },
|
|
91
|
+
features: form.features,
|
|
92
|
+
privacyPolicyUrl: form.privacyPolicyUrl,
|
|
93
|
+
supportUrl: form.supportUrl,
|
|
94
|
+
logoSvg: logoSvg ?? form.logoSvg ?? '',
|
|
95
|
+
logoUrl: form.logoUrl ?? '',
|
|
96
|
+
homepageUrl: form.homepageUrl ?? '',
|
|
97
|
+
screenshots: form.screenshots ?? [],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/** Tolerates non-strings from hand-edited JSON or an old saved row. */
|
|
101
|
+
function str(v) {
|
|
102
|
+
return typeof v === 'string' ? v.trim() : '';
|
|
103
|
+
}
|
|
104
|
+
function esc(s) {
|
|
105
|
+
return s
|
|
106
|
+
.replaceAll('&', '&')
|
|
107
|
+
.replaceAll('<', '<')
|
|
108
|
+
.replaceAll('>', '>')
|
|
109
|
+
.replaceAll('"', '"')
|
|
110
|
+
.replaceAll("'", ''');
|
|
111
|
+
}
|
|
112
|
+
// HTML-escape AND scheme-clamp a user-supplied URL before it lands in an
|
|
113
|
+
// href="". esc() alone stops attribute-breakout but not a `javascript:`/`data:`
|
|
114
|
+
// scheme, so a value like `javascript:alert(1)` would survive into the
|
|
115
|
+
// downloaded page the developer publishes for their own users. Only http(s)
|
|
116
|
+
// URLs are emitted verbatim; anything else collapses to "#".
|
|
117
|
+
function safeHref(raw) {
|
|
118
|
+
const u = raw.trim();
|
|
119
|
+
return /^https?:\/\//i.test(u) ? esc(u) : '#';
|
|
120
|
+
}
|
|
121
|
+
// One plain address, optionally `?subject=` with a percent-encoded value.
|
|
122
|
+
// No `&` (a second header such as bcc/body), no raw or encoded CR/LF, no
|
|
123
|
+
// whitespace, and the address itself admits no `%` — so
|
|
124
|
+
// `mailto:a@b.c%0d%0aBcc:x` and `mailto:javascript:` are both refused.
|
|
125
|
+
const MAILTO = /^mailto:[A-Za-z0-9._+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+(?:\?subject=(?:[A-Za-z0-9._~-]|%(?![01][0-9a-f]|7f)[0-9a-f]{2})+)?$/i;
|
|
126
|
+
/** safeHref() for the support link only: http(s), or a validated mailto: address. */
|
|
127
|
+
function safeSupportHref(raw) {
|
|
128
|
+
const u = raw.trim();
|
|
129
|
+
return MAILTO.test(u) ? esc(u) : safeHref(u);
|
|
130
|
+
}
|
|
131
|
+
const HTTPS_URL = /^https:\/\/[^\s"'<>\\]+$/i;
|
|
132
|
+
const DATA_IMAGE_URL = /^data:image\/(png|jpeg|gif|webp);base64,[A-Za-z0-9+/]+={0,2}$/;
|
|
133
|
+
// A path relative to the page: no scheme (`javascript:`, `data:`, `http:`), not
|
|
134
|
+
// protocol-relative (`//host`, `\\host` — browsers treat `\` as `/`), and no
|
|
135
|
+
// whitespace or quotes that could smuggle a second attribute.
|
|
136
|
+
const RELATIVE_PATH = /^(?![a-z][a-z0-9+.-]*:)(?![/\\]{2})[^\s"'<>\\]+$/i;
|
|
137
|
+
/** Image src clamp: https, relative path, or raster data URI; '' when unsafe. */
|
|
138
|
+
function safeImageSrc(raw) {
|
|
139
|
+
const u = str(raw);
|
|
140
|
+
if (!u)
|
|
141
|
+
return '';
|
|
142
|
+
if (HTTPS_URL.test(u) || RELATIVE_PATH.test(u))
|
|
143
|
+
return esc(u);
|
|
144
|
+
if (DATA_IMAGE_URL.test(u))
|
|
145
|
+
return u;
|
|
146
|
+
return '';
|
|
147
|
+
}
|
|
148
|
+
function canonicalUrl(raw) {
|
|
149
|
+
const u = str(raw);
|
|
150
|
+
return HTTPS_URL.test(u) ? u : '';
|
|
151
|
+
}
|
|
152
|
+
/** Absolute https form of an image URL (for og:image / JSON-LD), or ''. */
|
|
153
|
+
function absoluteImageUrl(raw, base) {
|
|
154
|
+
const u = str(raw);
|
|
155
|
+
if (HTTPS_URL.test(u))
|
|
156
|
+
return u;
|
|
157
|
+
if (!base || !RELATIVE_PATH.test(u))
|
|
158
|
+
return '';
|
|
159
|
+
try {
|
|
160
|
+
const resolved = new URL(u, base).href;
|
|
161
|
+
return HTTPS_URL.test(resolved) ? resolved : '';
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return '';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const SVG_FORBIDDEN = [
|
|
168
|
+
[/<!DOCTYPE|<!ENTITY/i, 'DOCTYPE/ENTITY declarations are not allowed'],
|
|
169
|
+
[
|
|
170
|
+
/<\s*(script|foreignObject|iframe|object|embed|applet|form|input|textarea|select|button|animate|animateTransform|animateMotion|set|handler|listener)\b/i,
|
|
171
|
+
'script, foreignObject, embedded documents, form controls and animation elements are not allowed',
|
|
172
|
+
],
|
|
173
|
+
[/\son[a-z]+\s*=/i, 'event-handler attributes (on*) are not allowed'],
|
|
174
|
+
[/(javascript|vbscript)\s*:/i, 'script URLs are not allowed'],
|
|
175
|
+
[/&#/, 'numeric character references are not allowed'],
|
|
176
|
+
[/@import|expression\s*\(/i, 'CSS @import / expression() is not allowed'],
|
|
177
|
+
[
|
|
178
|
+
// Quote handled by alternation, not `["']?`, so backtracking cannot skip
|
|
179
|
+
// the quote and let `(?!#)` look at it instead of the value.
|
|
180
|
+
/\s(?:xlink:)?href\s*=\s*(?:"\s*|'\s*|)(?!["'#\s]|data:image\/(?:png|jpeg|gif|webp);)/i,
|
|
181
|
+
'href values other than #fragments or raster data: images are not allowed',
|
|
182
|
+
],
|
|
183
|
+
];
|
|
184
|
+
/**
|
|
185
|
+
* Why an inline SVG logo is refused, or null when it is acceptable. A refusal
|
|
186
|
+
* never throws: buildLandingPage falls back to logoUrl / the monogram, and the
|
|
187
|
+
* MCP tool and CLI surface the reason instead of silently dropping the logo.
|
|
188
|
+
*/
|
|
189
|
+
export function landingLogoSvgProblem(svg) {
|
|
190
|
+
const s = str(svg);
|
|
191
|
+
if (!s)
|
|
192
|
+
return 'the SVG is empty';
|
|
193
|
+
if (new TextEncoder().encode(s).length > LANDING_LIMITS.logoSvgBytes) {
|
|
194
|
+
return `the SVG exceeds ${LANDING_LIMITS.logoSvgBytes} bytes`;
|
|
195
|
+
}
|
|
196
|
+
if (!/^(?:<\?xml[^>]*\?>\s*)?(?:<!--[\s\S]*?-->\s*)*<svg[\s>]/i.test(s) || !/<\/svg>$/i.test(s)) {
|
|
197
|
+
return 'the logo is not a single <svg>…</svg> document';
|
|
198
|
+
}
|
|
199
|
+
for (const [pattern, reason] of SVG_FORBIDDEN)
|
|
200
|
+
if (pattern.test(s))
|
|
201
|
+
return reason;
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
const BROWSER_LABELS = {
|
|
205
|
+
chrome: 'Google Chrome',
|
|
206
|
+
firefox: 'Mozilla Firefox',
|
|
207
|
+
edge: 'Microsoft Edge',
|
|
208
|
+
};
|
|
209
|
+
const STORE_CTA_LABELS = {
|
|
210
|
+
chrome: 'Add to Chrome',
|
|
211
|
+
firefox: 'Get for Firefox',
|
|
212
|
+
edge: 'Get for Edge',
|
|
213
|
+
};
|
|
214
|
+
const DEFAULT_ACCENT = '#5e5ce6';
|
|
215
|
+
const HEX_COLOR = /^#[0-9a-fA-F]{3,8}$/;
|
|
216
|
+
/**
|
|
217
|
+
* Human-readable notes about input the page silently corrected (a link clamped
|
|
218
|
+
* to "#", an unsafe image dropped, a refused logo). Same rules as the builder,
|
|
219
|
+
* so agents and the CLI can report exactly what did not make it into the page.
|
|
220
|
+
*/
|
|
221
|
+
export function landingPageWarnings(input) {
|
|
222
|
+
const out = [];
|
|
223
|
+
const links = [
|
|
224
|
+
['chromeUrl', input.storeUrls.chrome],
|
|
225
|
+
['firefoxUrl', input.storeUrls.firefox],
|
|
226
|
+
['edgeUrl', input.storeUrls.edge],
|
|
227
|
+
['privacyPolicyUrl', input.privacyPolicyUrl],
|
|
228
|
+
];
|
|
229
|
+
for (const [field, value] of links) {
|
|
230
|
+
const v = str(value);
|
|
231
|
+
if (v && safeHref(v) === '#')
|
|
232
|
+
out.push(`${field} is not an http(s) URL; its link points to "#".`);
|
|
233
|
+
}
|
|
234
|
+
const support = str(input.supportUrl);
|
|
235
|
+
if (support && safeSupportHref(support) === '#') {
|
|
236
|
+
out.push('supportUrl is not an http(s) URL or a single mailto: address; its link points to "#".');
|
|
237
|
+
}
|
|
238
|
+
if (!Object.keys(STORE_CTA_LABELS).some((b) => str(input.storeUrls[b]))) {
|
|
239
|
+
out.push('No store URL is set, so the page has no install button.');
|
|
240
|
+
}
|
|
241
|
+
if (str(input.accentColor) && !HEX_COLOR.test(str(input.accentColor))) {
|
|
242
|
+
out.push(`accentColor is not a hex colour; ${DEFAULT_ACCENT} is used.`);
|
|
243
|
+
}
|
|
244
|
+
const homepage = canonicalUrl(input.homepageUrl);
|
|
245
|
+
if (str(input.homepageUrl) && !homepage) {
|
|
246
|
+
out.push('homepageUrl is not an https URL; canonical, og:url and JSON-LD url are omitted.');
|
|
247
|
+
}
|
|
248
|
+
const svg = str(input.logoSvg);
|
|
249
|
+
const svgProblem = svg ? landingLogoSvgProblem(svg) : null;
|
|
250
|
+
if (svgProblem)
|
|
251
|
+
out.push(`logoSvg was refused (${svgProblem}); the fallback logo is shown.`);
|
|
252
|
+
if (str(input.logoUrl) && !safeImageSrc(input.logoUrl)) {
|
|
253
|
+
out.push('logoUrl is not an https URL, relative path or raster data: image; it is omitted.');
|
|
254
|
+
}
|
|
255
|
+
const shots = Array.isArray(input.screenshots) ? input.screenshots : [];
|
|
256
|
+
let relativeShots = 0;
|
|
257
|
+
for (const [i, shot] of shots.slice(0, LANDING_LIMITS.screenshots).entries()) {
|
|
258
|
+
const url = str(shot?.url);
|
|
259
|
+
if (!safeImageSrc(url))
|
|
260
|
+
out.push(`screenshots[${i}].url is not an https URL or relative path; it is omitted.`);
|
|
261
|
+
else if (!HTTPS_URL.test(url) && !DATA_IMAGE_URL.test(url))
|
|
262
|
+
relativeShots++;
|
|
263
|
+
}
|
|
264
|
+
if (shots.length > LANDING_LIMITS.screenshots) {
|
|
265
|
+
out.push(`Only the first ${LANDING_LIMITS.screenshots} screenshots are used.`);
|
|
266
|
+
}
|
|
267
|
+
if (relativeShots > 0 && !homepage) {
|
|
268
|
+
out.push('Relative screenshot paths need an https homepageUrl to appear in og:image and JSON-LD.');
|
|
269
|
+
}
|
|
270
|
+
return out;
|
|
271
|
+
}
|
|
272
|
+
export function buildLandingPage(input) {
|
|
273
|
+
const rawName = str(input.extensionName) || 'My Extension';
|
|
274
|
+
const name = esc(rawName);
|
|
275
|
+
const tagline = esc(str(input.tagline));
|
|
276
|
+
const description = str(input.description);
|
|
277
|
+
const accent = HEX_COLOR.test(str(input.accentColor)) ? str(input.accentColor) : DEFAULT_ACCENT;
|
|
278
|
+
const dark = input.theme === 'dark';
|
|
279
|
+
const bg = dark ? '#0f1115' : '#ffffff';
|
|
280
|
+
const fg = dark ? '#e8eaf0' : '#1f2430';
|
|
281
|
+
const muted = dark ? '#9aa1b2' : '#5b6372';
|
|
282
|
+
const cardBg = dark ? '#171a21' : '#f6f7fa';
|
|
283
|
+
const border = dark ? '#262b36' : '#e5e8ef';
|
|
284
|
+
const homepage = canonicalUrl(input.homepageUrl);
|
|
285
|
+
const monogram = rawName.charAt(0).toUpperCase() || 'E';
|
|
286
|
+
const svg = str(input.logoSvg);
|
|
287
|
+
const logoSrc = svg && !landingLogoSvgProblem(svg)
|
|
288
|
+
? `data:image/svg+xml,${encodeURIComponent(svg)}`
|
|
289
|
+
: safeImageSrc(input.logoUrl);
|
|
290
|
+
const logo = logoSrc
|
|
291
|
+
? `<div class="logo"><img src="${logoSrc}" alt="${name} logo" width="64" height="64" /></div>`
|
|
292
|
+
: `<div class="logo monogram" aria-hidden="true">${esc(monogram)}</div>`;
|
|
293
|
+
const ctas = Object.keys(STORE_CTA_LABELS)
|
|
294
|
+
.filter((b) => str(input.storeUrls[b]))
|
|
295
|
+
.map((b) => `<a class="cta${b === 'chrome' ? ' primary' : ''}" href="${safeHref(str(input.storeUrls[b]))}" rel="noopener">${STORE_CTA_LABELS[b]}<span class="cta-sub">${esc(BROWSER_LABELS[b])}</span></a>`)
|
|
296
|
+
.join('\n\t\t\t');
|
|
297
|
+
const featureList = Array.isArray(input.features) ? input.features : [];
|
|
298
|
+
const features = featureList
|
|
299
|
+
.filter((f) => str(f?.title))
|
|
300
|
+
.map((f) => `<div class="feature">
|
|
301
|
+
<h3>${esc(str(f.title))}</h3>
|
|
302
|
+
<p>${esc(str(f.description))}</p>
|
|
303
|
+
</div>`)
|
|
304
|
+
.join('\n\t\t\t');
|
|
305
|
+
const shots = (Array.isArray(input.screenshots) ? input.screenshots : [])
|
|
306
|
+
.slice(0, LANDING_LIMITS.screenshots)
|
|
307
|
+
.map((shot, i) => ({
|
|
308
|
+
url: str(shot?.url),
|
|
309
|
+
src: safeImageSrc(shot?.url),
|
|
310
|
+
alt: str(shot?.alt).slice(0, LANDING_LIMITS.screenshotAlt) || `${rawName} screenshot ${i + 1}`,
|
|
311
|
+
}))
|
|
312
|
+
.filter((shot) => shot.src);
|
|
313
|
+
const screenshots = shots
|
|
314
|
+
.map((shot) => `<figure class="shot"><img src="${shot.src}" alt="${esc(shot.alt)}" loading="lazy" /></figure>`)
|
|
315
|
+
.join('\n\t\t\t');
|
|
316
|
+
const absoluteShots = shots.map((shot) => absoluteImageUrl(shot.url, homepage)).filter(Boolean);
|
|
317
|
+
const footerLinks = [];
|
|
318
|
+
if (str(input.privacyPolicyUrl)) {
|
|
319
|
+
footerLinks.push(`<a href="${safeHref(str(input.privacyPolicyUrl))}">Privacy policy</a>`);
|
|
320
|
+
}
|
|
321
|
+
if (str(input.supportUrl)) {
|
|
322
|
+
footerLinks.push(`<a href="${safeSupportHref(str(input.supportUrl))}">Support</a>`);
|
|
323
|
+
}
|
|
324
|
+
const jsonLd = jsonLdScriptTag(jsonLdFromLanding({
|
|
325
|
+
extensionName: rawName,
|
|
326
|
+
tagline: str(input.tagline),
|
|
327
|
+
description,
|
|
328
|
+
storeUrls: input.storeUrls,
|
|
329
|
+
features: featureList
|
|
330
|
+
.filter((f) => str(f?.title))
|
|
331
|
+
.map((f) => ({ title: str(f.title), description: str(f.description) })),
|
|
332
|
+
privacyPolicyUrl: str(input.privacyPolicyUrl),
|
|
333
|
+
homepageUrl: homepage || undefined,
|
|
334
|
+
screenshots: absoluteShots,
|
|
335
|
+
}));
|
|
336
|
+
const metaDescription = esc(description || str(input.tagline));
|
|
337
|
+
const ogImage = absoluteShots[0] ?? '';
|
|
338
|
+
const head = [
|
|
339
|
+
`<title>${name}${tagline ? ` — ${tagline}` : ''}</title>`,
|
|
340
|
+
`<meta name="description" content="${metaDescription}" />`,
|
|
341
|
+
homepage ? `<link rel="canonical" href="${esc(homepage)}" />` : '',
|
|
342
|
+
'<meta property="og:type" content="website" />',
|
|
343
|
+
`<meta property="og:title" content="${name}" />`,
|
|
344
|
+
metaDescription ? `<meta property="og:description" content="${metaDescription}" />` : '',
|
|
345
|
+
homepage ? `<meta property="og:url" content="${esc(homepage)}" />` : '',
|
|
346
|
+
ogImage ? `<meta property="og:image" content="${esc(ogImage)}" />` : '',
|
|
347
|
+
`<meta name="twitter:card" content="${ogImage ? 'summary_large_image' : 'summary'}" />`,
|
|
348
|
+
jsonLd,
|
|
349
|
+
]
|
|
350
|
+
.filter(Boolean)
|
|
351
|
+
.join('\n');
|
|
352
|
+
return `<!doctype html>
|
|
353
|
+
<html lang="en">
|
|
354
|
+
<head>
|
|
355
|
+
<meta charset="utf-8" />
|
|
356
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
357
|
+
${head}
|
|
358
|
+
<style>
|
|
359
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
360
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: ${bg}; color: ${fg}; line-height: 1.6; }
|
|
361
|
+
.wrap { max-width: 880px; margin: 0 auto; padding: 0 24px; }
|
|
362
|
+
header { padding: 88px 0 56px; text-align: center; }
|
|
363
|
+
.logo { width: 88px; height: 88px; margin: 0 auto 28px; border-radius: 22px; display: flex; align-items: center; justify-content: center; background: ${cardBg}; border: 1px solid ${border}; overflow: hidden; }
|
|
364
|
+
.logo img { width: 64px; height: 64px; object-fit: contain; }
|
|
365
|
+
.logo.monogram { font-size: 2.4rem; font-weight: 700; color: ${accent}; }
|
|
366
|
+
h1 { font-size: 2.6rem; letter-spacing: -0.02em; line-height: 1.15; }
|
|
367
|
+
.tagline { margin-top: 14px; font-size: 1.25rem; color: ${muted}; }
|
|
368
|
+
.ctas { margin-top: 36px; display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
|
|
369
|
+
.cta { display: inline-flex; flex-direction: column; align-items: center; gap: 2px; padding: 12px 26px; border-radius: 12px; border: 1px solid ${border}; background: ${cardBg}; color: ${fg}; text-decoration: none; font-weight: 600; transition: transform 0.12s ease; }
|
|
370
|
+
.cta:hover { transform: translateY(-2px); }
|
|
371
|
+
.cta.primary { background: ${accent}; border-color: ${accent}; color: #fff; }
|
|
372
|
+
.cta-sub { font-size: 0.72rem; font-weight: 400; opacity: 0.75; }
|
|
373
|
+
.about { padding: 8px 0 24px; text-align: center; }
|
|
374
|
+
.about p { max-width: 640px; margin: 0 auto; color: ${muted}; }
|
|
375
|
+
.shots { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 18px; padding: 24px 0 8px; }
|
|
376
|
+
.shot img { display: block; width: 100%; height: auto; border-radius: 12px; border: 1px solid ${border}; }
|
|
377
|
+
.features { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 18px; padding: 40px 0 64px; }
|
|
378
|
+
.feature { background: ${cardBg}; border: 1px solid ${border}; border-radius: 14px; padding: 22px; }
|
|
379
|
+
.feature h3 { font-size: 1.02rem; margin-bottom: 8px; }
|
|
380
|
+
.feature p { font-size: 0.92rem; color: ${muted}; }
|
|
381
|
+
footer { border-top: 1px solid ${border}; padding: 28px 0 48px; text-align: center; font-size: 0.85rem; color: ${muted}; }
|
|
382
|
+
footer a { color: ${muted}; margin: 0 10px; }
|
|
383
|
+
</style>
|
|
384
|
+
</head>
|
|
385
|
+
<body>
|
|
386
|
+
<div class="wrap">
|
|
387
|
+
<header>
|
|
388
|
+
${logo}
|
|
389
|
+
<h1>${name}</h1>
|
|
390
|
+
${tagline ? `<p class="tagline">${tagline}</p>` : ''}
|
|
391
|
+
${ctas ? `<div class="ctas">\n\t\t\t${ctas}\n\t\t\t</div>` : ''}
|
|
392
|
+
</header>
|
|
393
|
+
${description ? `<section class="about"><p>${esc(description)}</p></section>` : ''}
|
|
394
|
+
${screenshots ? `<section class="shots">\n\t\t\t${screenshots}\n\t\t</section>` : ''}
|
|
395
|
+
${features ? `<section class="features">\n\t\t\t${features}\n\t\t</section>` : ''}
|
|
396
|
+
<footer>
|
|
397
|
+
${footerLinks.join('\n\t\t\t')}
|
|
398
|
+
<div>© ${name}</div>
|
|
399
|
+
</footer>
|
|
400
|
+
</div>
|
|
401
|
+
</body>
|
|
402
|
+
</html>
|
|
403
|
+
`;
|
|
404
|
+
}
|
|
405
|
+
//# sourceMappingURL=landing-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"landing-page.js","sourceRoot":"","sources":["../src/landing-page.ts"],"names":[],"mappings":"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"}
|
package/dist/page.d.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `extenshi page generate` and `extenshi page register`.
|
|
3
|
+
*
|
|
4
|
+
* generate — free, fully offline, no API key: turns a landing-page JSON config
|
|
5
|
+
* (the same fields as the cabinet Page generator, so a saved `page-generator`
|
|
6
|
+
* `{ form }` works as-is) into one static HTML file with no JavaScript. The
|
|
7
|
+
* builder is ./landing-page.ts, a synced copy of shared-types/landing-page.ts.
|
|
8
|
+
*
|
|
9
|
+
* register — records the URL where the developer hosted that page as the
|
|
10
|
+
* project's homepage (or support page) through the same BFF procedure as the
|
|
11
|
+
* MCP `upsert_hosted_page` tool. The server fetches the public page and hashes
|
|
12
|
+
* it; a registered homepage feeds HOMEPAGE_URL in the integration config.
|
|
13
|
+
* Extenshi does not host the page itself.
|
|
14
|
+
*/
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
import { type LandingForm } from './landing-page.js';
|
|
17
|
+
/** Landing config file schema: LandingForm fields, same limits as the MCP tool. */
|
|
18
|
+
export declare const landingConfigSchema: z.ZodObject<{
|
|
19
|
+
extensionName: z.ZodString;
|
|
20
|
+
tagline: z.ZodDefault<z.ZodString>;
|
|
21
|
+
description: z.ZodDefault<z.ZodString>;
|
|
22
|
+
accentColor: z.ZodDefault<z.ZodString>;
|
|
23
|
+
theme: z.ZodDefault<z.ZodEnum<["light", "dark"]>>;
|
|
24
|
+
chromeUrl: z.ZodDefault<z.ZodString>;
|
|
25
|
+
firefoxUrl: z.ZodDefault<z.ZodString>;
|
|
26
|
+
edgeUrl: z.ZodDefault<z.ZodString>;
|
|
27
|
+
privacyPolicyUrl: z.ZodDefault<z.ZodString>;
|
|
28
|
+
supportUrl: z.ZodDefault<z.ZodString>;
|
|
29
|
+
homepageUrl: z.ZodOptional<z.ZodString>;
|
|
30
|
+
features: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
31
|
+
title: z.ZodString;
|
|
32
|
+
description: z.ZodDefault<z.ZodString>;
|
|
33
|
+
}, "strip", z.ZodTypeAny, {
|
|
34
|
+
description: string;
|
|
35
|
+
title: string;
|
|
36
|
+
}, {
|
|
37
|
+
title: string;
|
|
38
|
+
description?: string | undefined;
|
|
39
|
+
}>, "many">>;
|
|
40
|
+
screenshots: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
41
|
+
url: z.ZodString;
|
|
42
|
+
alt: z.ZodDefault<z.ZodString>;
|
|
43
|
+
}, "strip", z.ZodTypeAny, {
|
|
44
|
+
url: string;
|
|
45
|
+
alt: string;
|
|
46
|
+
}, {
|
|
47
|
+
url: string;
|
|
48
|
+
alt?: string | undefined;
|
|
49
|
+
}>, "many">>;
|
|
50
|
+
logoSvg: z.ZodOptional<z.ZodString>;
|
|
51
|
+
logoUrl: z.ZodOptional<z.ZodString>;
|
|
52
|
+
}, "strip", z.ZodTypeAny, {
|
|
53
|
+
description: string;
|
|
54
|
+
extensionName: string;
|
|
55
|
+
tagline: string;
|
|
56
|
+
accentColor: string;
|
|
57
|
+
theme: "light" | "dark";
|
|
58
|
+
chromeUrl: string;
|
|
59
|
+
firefoxUrl: string;
|
|
60
|
+
edgeUrl: string;
|
|
61
|
+
privacyPolicyUrl: string;
|
|
62
|
+
supportUrl: string;
|
|
63
|
+
features: {
|
|
64
|
+
description: string;
|
|
65
|
+
title: string;
|
|
66
|
+
}[];
|
|
67
|
+
homepageUrl?: string | undefined;
|
|
68
|
+
logoUrl?: string | undefined;
|
|
69
|
+
logoSvg?: string | undefined;
|
|
70
|
+
screenshots?: {
|
|
71
|
+
url: string;
|
|
72
|
+
alt: string;
|
|
73
|
+
}[] | undefined;
|
|
74
|
+
}, {
|
|
75
|
+
extensionName: string;
|
|
76
|
+
description?: string | undefined;
|
|
77
|
+
tagline?: string | undefined;
|
|
78
|
+
accentColor?: string | undefined;
|
|
79
|
+
theme?: "light" | "dark" | undefined;
|
|
80
|
+
chromeUrl?: string | undefined;
|
|
81
|
+
firefoxUrl?: string | undefined;
|
|
82
|
+
edgeUrl?: string | undefined;
|
|
83
|
+
privacyPolicyUrl?: string | undefined;
|
|
84
|
+
supportUrl?: string | undefined;
|
|
85
|
+
features?: {
|
|
86
|
+
title: string;
|
|
87
|
+
description?: string | undefined;
|
|
88
|
+
}[] | undefined;
|
|
89
|
+
homepageUrl?: string | undefined;
|
|
90
|
+
logoUrl?: string | undefined;
|
|
91
|
+
logoSvg?: string | undefined;
|
|
92
|
+
screenshots?: {
|
|
93
|
+
url: string;
|
|
94
|
+
alt?: string | undefined;
|
|
95
|
+
}[] | undefined;
|
|
96
|
+
}>;
|
|
97
|
+
export interface PageGenerateOptions {
|
|
98
|
+
config: string;
|
|
99
|
+
out?: string;
|
|
100
|
+
logo?: string;
|
|
101
|
+
}
|
|
102
|
+
export interface PageGenerateResult {
|
|
103
|
+
outPath: string;
|
|
104
|
+
bytes: number;
|
|
105
|
+
warnings: string[];
|
|
106
|
+
}
|
|
107
|
+
/** Parse a config file body: a bare form, or a saved `{ form }` tool-state row. */
|
|
108
|
+
export declare function parseLandingConfig(raw: string): LandingForm;
|
|
109
|
+
export declare function runPageGenerate(opts: PageGenerateOptions): Promise<PageGenerateResult>;
|
|
110
|
+
export interface PageRegisterOptions {
|
|
111
|
+
project: string;
|
|
112
|
+
url: string;
|
|
113
|
+
kind?: string;
|
|
114
|
+
}
|
|
115
|
+
export declare function runPageRegister(opts: PageRegisterOptions): Promise<Record<string, unknown>>;
|
|
116
|
+
export interface PagePublishOptions {
|
|
117
|
+
project: string;
|
|
118
|
+
config: string;
|
|
119
|
+
logo?: string;
|
|
120
|
+
/** commander's --no-homepage sets this to false. */
|
|
121
|
+
homepage?: boolean;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* `extenshi page publish`: host the page on page.extenshi.io. Sends the form —
|
|
125
|
+
* the BFF renders it with the same builder — never the HTML file.
|
|
126
|
+
*/
|
|
127
|
+
export declare function runPagePublish(opts: PagePublishOptions): Promise<Record<string, unknown>>;
|
|
128
|
+
export declare function runPageUnpublish(opts: {
|
|
129
|
+
project: string;
|
|
130
|
+
}): Promise<Record<string, unknown>>;
|
|
131
|
+
//# sourceMappingURL=page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../src/page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,OAAO,EAIN,KAAK,WAAW,EAIhB,MAAM,mBAAmB,CAAA;AAe1B,mFAAmF;AACnF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2B9B,CAAA;AAEF,MAAM,WAAW,mBAAmB;IACnC,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,EAAE,CAAA;CAClB;AAgBD,mFAAmF;AACnF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAoB3D;AA2BD,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAc5F;AAED,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,CAAC,EAAE,MAAM,CAAA;CACb;AAYD,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAejG;AAED,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,oDAAoD;IACpD,QAAQ,CAAC,EAAE,OAAO,CAAA;CAClB;AAID;;;GAGG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAgC/F;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAYlG"}
|