ampless 0.2.0-alpha.7 → 0.2.0-alpha.8
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/index.d.ts +89 -1
- package/dist/index.js +139 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -551,6 +551,94 @@ declare function formatPublicAssetUrl(bucket: string, region: string, key: strin
|
|
|
551
551
|
*/
|
|
552
552
|
declare function extractFirstImageUrl(post: Post): string | null;
|
|
553
553
|
|
|
554
|
+
/**
|
|
555
|
+
* Pure helpers for `format: 'static'` post bundles. Kept platform-free
|
|
556
|
+
* (no `File`, no Amplify Storage, no AWS SDK) so both the browser admin
|
|
557
|
+
* uploader and the MCP tools (running in stdio CLI + Lambda HTTP
|
|
558
|
+
* transport) can share the validation / picker / mime logic.
|
|
559
|
+
*
|
|
560
|
+
* The `extractZip(File)` / `uploadBundle` / `deleteBundle` browser
|
|
561
|
+
* helpers stay in `@ampless/admin/lib/static-bundle.js` — they pull in
|
|
562
|
+
* JSZip and `aws-amplify/storage`, neither of which we want in Lambda.
|
|
563
|
+
* The MCP Lambda implements its own zip extractor (`fflate`) on top of
|
|
564
|
+
* the helpers here.
|
|
565
|
+
*/
|
|
566
|
+
declare const DEFAULT_ENTRYPOINT = "index.html";
|
|
567
|
+
/**
|
|
568
|
+
* Maximum bundle size (uncompressed) for the browser-side admin
|
|
569
|
+
* pipeline. Above this the in-tab extract / multi-PUT pipeline gets
|
|
570
|
+
* sluggish. The Lambda / MCP side enforces its own limit (Function URL
|
|
571
|
+
* payload cap, around 6 MB base64-inflated per call) — this constant
|
|
572
|
+
* is exported as a reasonable upper bound for any caller that wants
|
|
573
|
+
* a sanity check, not a hard contract for non-browser paths.
|
|
574
|
+
*/
|
|
575
|
+
declare const MAX_BUNDLE_BYTES: number;
|
|
576
|
+
declare const TEXT_EXTENSIONS: ReadonlySet<string>;
|
|
577
|
+
declare function mimeTypeFor(path: string): string;
|
|
578
|
+
interface ValidationIssue {
|
|
579
|
+
path: string;
|
|
580
|
+
line?: number;
|
|
581
|
+
reason: string;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Reject paths that would either escape the bundle root (`..`), confuse
|
|
585
|
+
* S3 (`null bytes`), or carry OS-specific cruft from zip tools
|
|
586
|
+
* (`__MACOSX/`, `.DS_Store`). Returns `null` when the path is OK,
|
|
587
|
+
* otherwise the reason to surface to the user.
|
|
588
|
+
*/
|
|
589
|
+
declare function validateBundlePath(path: string): string | null;
|
|
590
|
+
/**
|
|
591
|
+
* Scan text files for **absolute** path references. The static-bundle
|
|
592
|
+
* contract is that every reference resolves inside the bundle via
|
|
593
|
+
* relative paths, so any `href="/foo"` would either escape to the
|
|
594
|
+
* Next.js root or break under arbitrary URL prefixes. Protocol-prefixed
|
|
595
|
+
* URLs (`https://…`) and `mailto:` / `tel:` are fine.
|
|
596
|
+
*
|
|
597
|
+
* `data:` and `blob:` URIs also pass (they don't traverse the network).
|
|
598
|
+
* Hash-only (`#anchor`) and empty values pass too.
|
|
599
|
+
*/
|
|
600
|
+
declare function findAbsolutePathRefs(path: string, content: string): ValidationIssue[];
|
|
601
|
+
interface ExtractedFile {
|
|
602
|
+
/** Relative path inside the bundle (slash-separated). */
|
|
603
|
+
path: string;
|
|
604
|
+
data: Uint8Array;
|
|
605
|
+
}
|
|
606
|
+
interface BundleExtractResult {
|
|
607
|
+
files: ExtractedFile[];
|
|
608
|
+
issues: ValidationIssue[];
|
|
609
|
+
totalBytes: number;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Scan every text file in the bundle for absolute URL refs. Returns
|
|
613
|
+
* the union of issues across files so the admin UI / MCP tool can
|
|
614
|
+
* render a single list of problems before saving.
|
|
615
|
+
*/
|
|
616
|
+
declare function validateBundle(files: ExtractedFile[]): ValidationIssue[];
|
|
617
|
+
declare function bundlePrefix(siteId: string, slug: string): string;
|
|
618
|
+
/**
|
|
619
|
+
* If every file shares the same top-level directory (e.g. macOS Finder
|
|
620
|
+
* zipping wraps the contents in a `MyBundle/` folder), strip that
|
|
621
|
+
* prefix so the bundle's logical root is the entrypoint's parent.
|
|
622
|
+
* Bundles already at root pass through unchanged.
|
|
623
|
+
*/
|
|
624
|
+
declare function stripCommonPrefix(files: ExtractedFile[]): ExtractedFile[];
|
|
625
|
+
/**
|
|
626
|
+
* Heuristic for the default entrypoint when the caller didn't override:
|
|
627
|
+
* 1. exact `index.html` at root
|
|
628
|
+
* 2. exact `index.htm` at root
|
|
629
|
+
* 3. the first .html / .htm file at root (alphabetical)
|
|
630
|
+
* 4. the first .html / .htm anywhere
|
|
631
|
+
* 5. fall back to the literal 'index.html' so the validator surfaces
|
|
632
|
+
* a meaningful error to the user ("entrypoint not in bundle")
|
|
633
|
+
*
|
|
634
|
+
* Accepts either extracted files (with `data`) or a plain list of
|
|
635
|
+
* relative paths — the MCP `commit_static_post` flow only has the
|
|
636
|
+
* S3 ListObjects result to work with, no bytes.
|
|
637
|
+
*/
|
|
638
|
+
declare function pickDefaultEntrypoint(files: readonly {
|
|
639
|
+
path: string;
|
|
640
|
+
}[]): string;
|
|
641
|
+
|
|
554
642
|
type ThemeFieldType = 'color' | 'text' | 'select' | 'image' | 'length' | 'fontFamily' | 'linkList';
|
|
555
643
|
/**
|
|
556
644
|
* Single entry in a `linkList` field. Stored as part of a JSON array
|
|
@@ -764,4 +852,4 @@ declare function resolveThemeValues(manifest: ThemeManifest, stored: Record<stri
|
|
|
764
852
|
|
|
765
853
|
declare const VERSION = "0.0.1";
|
|
766
854
|
|
|
767
|
-
export { type AmplessEvent, type AmplessPlugin, type AuthContext, type Config, type ContentEventPayload, type ContentEventType, type ContentFormat, type ContentTypeDefinition, type CreatePostInput, DEFAULT_SITE_ID, type DateFormat, type EventPayloadOf, type EventType, type FieldDefinition, type FieldType, type ImageDisplay, type KvItem, type KvStore, type LinkListItem, type ListOptions, type LocalizedString, type Media, type MediaEventPayload, type MediaEventType, type MediaProcessingDefaults, type OgImageConfig, type OgImageFont, type OgImageRenderContext, type Page, type PluginEventHandler, type PluginMetadata, type PluginRuntimeContext, type Post, type PostMetadata, type PostStatus, type PostsProvider, type Role, SITE_CONFIG_PK, type SiteConfig, type SiteSettingsEventPayload, type SiteSettingsEventType, type StaticPostBody, type StreamEventName, type ThemeColorField, type ThemeField, type ThemeFieldType, type ThemeFontFamilyField, type ThemeImageField, type ThemeLengthField, type ThemeLinkListField, type ThemeManifest, type ThemeModule, type ThemeRouteContext, type ThemeSelectField, type ThemeTextField, type TrustLevel, VERSION, composeSiteIdSlug, composeSiteIdStatus, createPost, decodeAwsJson, defineConfig, definePlugin, defineSchema, defineTheme, defineThemeModule, deletePost, deleteSiteSetting, detectContentEvents, encodeAwsJson, escapeXml, extractFirstImageUrl, flattenSettings, formatColorPair, formatDate, formatPublicAssetUrl, getKvStore, getPost, getPostById, getSiteSetting, hasKvStore, hasPostsProvider, isMultiSite, isTagListUrl, listPosts, listSiteSettings, parseColorPair, parseLinkList, resolveLocalized, resolveSiteId, resolveThemeValues, setKvStore, setPostsProvider, setSiteSetting, siteFor, stringifyLinkList, themeSettingKey, unflattenSettings, updatePost, validateThemeValue };
|
|
855
|
+
export { type AmplessEvent, type AmplessPlugin, type AuthContext, type BundleExtractResult, type Config, type ContentEventPayload, type ContentEventType, type ContentFormat, type ContentTypeDefinition, type CreatePostInput, DEFAULT_ENTRYPOINT, DEFAULT_SITE_ID, type DateFormat, type EventPayloadOf, type EventType, type ExtractedFile, type FieldDefinition, type FieldType, type ImageDisplay, type KvItem, type KvStore, type LinkListItem, type ListOptions, type LocalizedString, MAX_BUNDLE_BYTES, type Media, type MediaEventPayload, type MediaEventType, type MediaProcessingDefaults, type OgImageConfig, type OgImageFont, type OgImageRenderContext, type Page, type PluginEventHandler, type PluginMetadata, type PluginRuntimeContext, type Post, type PostMetadata, type PostStatus, type PostsProvider, type Role, SITE_CONFIG_PK, type SiteConfig, type SiteSettingsEventPayload, type SiteSettingsEventType, type StaticPostBody, type StreamEventName, TEXT_EXTENSIONS, type ThemeColorField, type ThemeField, type ThemeFieldType, type ThemeFontFamilyField, type ThemeImageField, type ThemeLengthField, type ThemeLinkListField, type ThemeManifest, type ThemeModule, type ThemeRouteContext, type ThemeSelectField, type ThemeTextField, type TrustLevel, VERSION, type ValidationIssue, bundlePrefix, composeSiteIdSlug, composeSiteIdStatus, createPost, decodeAwsJson, defineConfig, definePlugin, defineSchema, defineTheme, defineThemeModule, deletePost, deleteSiteSetting, detectContentEvents, encodeAwsJson, escapeXml, extractFirstImageUrl, findAbsolutePathRefs, flattenSettings, formatColorPair, formatDate, formatPublicAssetUrl, getKvStore, getPost, getPostById, getSiteSetting, hasKvStore, hasPostsProvider, isMultiSite, isTagListUrl, listPosts, listSiteSettings, mimeTypeFor, parseColorPair, parseLinkList, pickDefaultEntrypoint, resolveLocalized, resolveSiteId, resolveThemeValues, setKvStore, setPostsProvider, setSiteSetting, siteFor, stringifyLinkList, stripCommonPrefix, themeSettingKey, unflattenSettings, updatePost, validateBundle, validateBundlePath, validateThemeValue };
|
package/dist/index.js
CHANGED
|
@@ -296,6 +296,135 @@ function findHtmlImage(body) {
|
|
|
296
296
|
return m ? m[1] : null;
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
+
// src/static-bundle.ts
|
|
300
|
+
var DEFAULT_ENTRYPOINT = "index.html";
|
|
301
|
+
var MAX_BUNDLE_BYTES = 50 * 1024 * 1024;
|
|
302
|
+
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
303
|
+
".html",
|
|
304
|
+
".htm",
|
|
305
|
+
".css",
|
|
306
|
+
".svg"
|
|
307
|
+
]);
|
|
308
|
+
var MIME_TYPES = {
|
|
309
|
+
".html": "text/html; charset=utf-8",
|
|
310
|
+
".htm": "text/html; charset=utf-8",
|
|
311
|
+
".css": "text/css; charset=utf-8",
|
|
312
|
+
".js": "application/javascript; charset=utf-8",
|
|
313
|
+
".mjs": "application/javascript; charset=utf-8",
|
|
314
|
+
".json": "application/json; charset=utf-8",
|
|
315
|
+
".svg": "image/svg+xml",
|
|
316
|
+
".png": "image/png",
|
|
317
|
+
".jpg": "image/jpeg",
|
|
318
|
+
".jpeg": "image/jpeg",
|
|
319
|
+
".gif": "image/gif",
|
|
320
|
+
".webp": "image/webp",
|
|
321
|
+
".avif": "image/avif",
|
|
322
|
+
".ico": "image/x-icon",
|
|
323
|
+
".woff": "font/woff",
|
|
324
|
+
".woff2": "font/woff2",
|
|
325
|
+
".ttf": "font/ttf",
|
|
326
|
+
".otf": "font/otf",
|
|
327
|
+
".eot": "application/vnd.ms-fontobject",
|
|
328
|
+
".txt": "text/plain; charset=utf-8",
|
|
329
|
+
".xml": "application/xml; charset=utf-8",
|
|
330
|
+
".map": "application/json; charset=utf-8",
|
|
331
|
+
".pdf": "application/pdf"
|
|
332
|
+
};
|
|
333
|
+
function mimeTypeFor(path) {
|
|
334
|
+
const lower = path.toLowerCase();
|
|
335
|
+
const dot = lower.lastIndexOf(".");
|
|
336
|
+
if (dot < 0) return "application/octet-stream";
|
|
337
|
+
return MIME_TYPES[lower.slice(dot)] ?? "application/octet-stream";
|
|
338
|
+
}
|
|
339
|
+
function validateBundlePath(path) {
|
|
340
|
+
if (path === "" || path.endsWith("/")) return "directory entry";
|
|
341
|
+
if (path.includes("\0")) return "contains null byte";
|
|
342
|
+
if (path.startsWith("/") || path.startsWith("\\")) return "absolute path";
|
|
343
|
+
if (path.split(/[/\\]/).some((seg) => seg === "..")) return "parent-directory traversal";
|
|
344
|
+
if (path.startsWith("__MACOSX/") || /(^|\/)\._/.test(path)) return "macOS resource fork";
|
|
345
|
+
if (/(^|\/)\.DS_Store$/.test(path)) return ".DS_Store junk";
|
|
346
|
+
if (/(^|\/)Thumbs\.db$/i.test(path)) return "Thumbs.db junk";
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
var HTML_URL_ATTR_RE = /\b(?:href|src|action|data|poster|cite|formaction|manifest|srcset)\s*=\s*["']([^"']+)["']/gi;
|
|
350
|
+
var CSS_URL_RE = /url\(\s*["']?([^"')\s]+)["']?\s*\)|@import\s+["']([^"']+)["']/gi;
|
|
351
|
+
function findAbsolutePathRefs(path, content) {
|
|
352
|
+
const ext = path.toLowerCase().slice(path.lastIndexOf("."));
|
|
353
|
+
if (!TEXT_EXTENSIONS.has(ext)) return [];
|
|
354
|
+
const issues = [];
|
|
355
|
+
function check(url, lineHint) {
|
|
356
|
+
const trimmed = url.trim();
|
|
357
|
+
if (!trimmed) return;
|
|
358
|
+
if (trimmed.startsWith("#")) return;
|
|
359
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed)) return;
|
|
360
|
+
if (trimmed.startsWith("//")) {
|
|
361
|
+
issues.push({ path, reason: `protocol-relative URL: ${lineHint}` });
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (trimmed.startsWith("/")) {
|
|
365
|
+
issues.push({ path, reason: `absolute path: ${lineHint}` });
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (ext === ".html" || ext === ".htm" || ext === ".svg") {
|
|
370
|
+
HTML_URL_ATTR_RE.lastIndex = 0;
|
|
371
|
+
let m;
|
|
372
|
+
while ((m = HTML_URL_ATTR_RE.exec(content)) !== null) {
|
|
373
|
+
const val = m[1] ?? "";
|
|
374
|
+
if (/\bsrcset\s*=/i.test(m[0])) {
|
|
375
|
+
for (const candidate of val.split(",")) {
|
|
376
|
+
const urlPart = candidate.trim().split(/\s+/)[0];
|
|
377
|
+
if (urlPart) check(urlPart, candidate.trim());
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
check(val, m[0]);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (ext === ".css" || ext === ".svg") {
|
|
385
|
+
CSS_URL_RE.lastIndex = 0;
|
|
386
|
+
let m;
|
|
387
|
+
while ((m = CSS_URL_RE.exec(content)) !== null) {
|
|
388
|
+
const val = (m[1] ?? m[2] ?? "").trim();
|
|
389
|
+
check(val, m[0]);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return issues;
|
|
393
|
+
}
|
|
394
|
+
function validateBundle(files) {
|
|
395
|
+
const issues = [];
|
|
396
|
+
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
397
|
+
for (const f of files) {
|
|
398
|
+
const ext = f.path.toLowerCase().slice(f.path.lastIndexOf("."));
|
|
399
|
+
if (!TEXT_EXTENSIONS.has(ext)) continue;
|
|
400
|
+
const text = decoder.decode(f.data);
|
|
401
|
+
issues.push(...findAbsolutePathRefs(f.path, text));
|
|
402
|
+
}
|
|
403
|
+
return issues;
|
|
404
|
+
}
|
|
405
|
+
function bundlePrefix(siteId, slug) {
|
|
406
|
+
return `public/static/${siteId}/${slug}/`;
|
|
407
|
+
}
|
|
408
|
+
function stripCommonPrefix(files) {
|
|
409
|
+
if (files.length === 0) return files;
|
|
410
|
+
const firstSlash = files[0].path.indexOf("/");
|
|
411
|
+
if (firstSlash < 0) return files;
|
|
412
|
+
const prefix = files[0].path.slice(0, firstSlash + 1);
|
|
413
|
+
if (!files.every((f) => f.path.startsWith(prefix))) return files;
|
|
414
|
+
return files.map((f) => ({ ...f, path: f.path.slice(prefix.length) }));
|
|
415
|
+
}
|
|
416
|
+
function pickDefaultEntrypoint(files) {
|
|
417
|
+
const exact = files.find((f) => f.path === DEFAULT_ENTRYPOINT);
|
|
418
|
+
if (exact) return exact.path;
|
|
419
|
+
const altRoot = files.find((f) => f.path === "index.htm");
|
|
420
|
+
if (altRoot) return altRoot.path;
|
|
421
|
+
const htmlRoot = files.filter((f) => /^[^/]+\.html?$/.test(f.path)).sort((a, b) => a.path.localeCompare(b.path));
|
|
422
|
+
if (htmlRoot.length > 0) return htmlRoot[0].path;
|
|
423
|
+
const htmlAny = files.filter((f) => /\.html?$/.test(f.path)).sort((a, b) => a.path.localeCompare(b.path));
|
|
424
|
+
if (htmlAny.length > 0) return htmlAny[0].path;
|
|
425
|
+
return DEFAULT_ENTRYPOINT;
|
|
426
|
+
}
|
|
427
|
+
|
|
299
428
|
// src/events.ts
|
|
300
429
|
function detectContentEvents(input) {
|
|
301
430
|
switch (input.eventName) {
|
|
@@ -471,9 +600,13 @@ function defaultValueAsString(field) {
|
|
|
471
600
|
// src/index.ts
|
|
472
601
|
var VERSION = "0.0.1";
|
|
473
602
|
export {
|
|
603
|
+
DEFAULT_ENTRYPOINT,
|
|
474
604
|
DEFAULT_SITE_ID,
|
|
605
|
+
MAX_BUNDLE_BYTES,
|
|
475
606
|
SITE_CONFIG_PK,
|
|
607
|
+
TEXT_EXTENSIONS,
|
|
476
608
|
VERSION,
|
|
609
|
+
bundlePrefix,
|
|
477
610
|
composeSiteIdSlug,
|
|
478
611
|
composeSiteIdStatus,
|
|
479
612
|
createPost,
|
|
@@ -489,6 +622,7 @@ export {
|
|
|
489
622
|
encodeAwsJson,
|
|
490
623
|
escapeXml,
|
|
491
624
|
extractFirstImageUrl,
|
|
625
|
+
findAbsolutePathRefs,
|
|
492
626
|
flattenSettings,
|
|
493
627
|
formatColorPair,
|
|
494
628
|
formatDate,
|
|
@@ -503,8 +637,10 @@ export {
|
|
|
503
637
|
isTagListUrl,
|
|
504
638
|
listPosts,
|
|
505
639
|
listSiteSettings,
|
|
640
|
+
mimeTypeFor,
|
|
506
641
|
parseColorPair,
|
|
507
642
|
parseLinkList,
|
|
643
|
+
pickDefaultEntrypoint,
|
|
508
644
|
resolveLocalized,
|
|
509
645
|
resolveSiteId,
|
|
510
646
|
resolveThemeValues,
|
|
@@ -513,8 +649,11 @@ export {
|
|
|
513
649
|
setSiteSetting,
|
|
514
650
|
siteFor,
|
|
515
651
|
stringifyLinkList,
|
|
652
|
+
stripCommonPrefix,
|
|
516
653
|
themeSettingKey,
|
|
517
654
|
unflattenSettings,
|
|
518
655
|
updatePost,
|
|
656
|
+
validateBundle,
|
|
657
|
+
validateBundlePath,
|
|
519
658
|
validateThemeValue
|
|
520
659
|
};
|