ampless 0.2.0-alpha.7 → 1.0.0-alpha.9
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 +118 -126
- package/dist/index.js +160 -65
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ type SiteSettingsEventType = 'site.settings.updated';
|
|
|
4
4
|
type EventType = ContentEventType | MediaEventType | SiteSettingsEventType;
|
|
5
5
|
/** Minimal projection of a Post item carried in events (no body, to keep payloads small). */
|
|
6
6
|
interface ContentEventPayload {
|
|
7
|
-
siteId: string;
|
|
8
7
|
postId: string;
|
|
9
8
|
slug: string;
|
|
10
9
|
title: string;
|
|
@@ -13,18 +12,16 @@ interface ContentEventPayload {
|
|
|
13
12
|
tags?: string[];
|
|
14
13
|
}
|
|
15
14
|
interface MediaEventPayload {
|
|
16
|
-
siteId: string;
|
|
17
15
|
mediaId: string;
|
|
18
16
|
src: string;
|
|
19
17
|
mimeType: string;
|
|
20
18
|
}
|
|
21
19
|
/**
|
|
22
|
-
* Emitted whenever any setting under `siteconfig
|
|
23
|
-
*
|
|
24
|
-
*
|
|
20
|
+
* Emitted whenever any setting under the `siteconfig:` PK in KvStore is
|
|
21
|
+
* created, updated, or removed. Subscribers (built-in or user plugins)
|
|
22
|
+
* can rebuild caches, theme assets, etc.
|
|
25
23
|
*/
|
|
26
24
|
interface SiteSettingsEventPayload {
|
|
27
|
-
siteId: string;
|
|
28
25
|
}
|
|
29
26
|
type EventPayloadOf<T extends EventType> = T extends ContentEventType ? ContentEventPayload : T extends MediaEventType ? MediaEventPayload : T extends SiteSettingsEventType ? SiteSettingsEventPayload : never;
|
|
30
27
|
interface AmplessEvent<T extends EventType = EventType> {
|
|
@@ -89,13 +86,12 @@ type PluginEventHandler<T extends EventType = EventType> = (event: AmplessEvent<
|
|
|
89
86
|
* (sandbox, tests) without touching plugin code.
|
|
90
87
|
*/
|
|
91
88
|
interface PluginRuntimeContext {
|
|
92
|
-
siteId: string;
|
|
93
89
|
/** Read-only view of the cms.config site block (name, url, description). */
|
|
94
90
|
site: Config['site'];
|
|
95
|
-
/** Read all published posts
|
|
91
|
+
/** Read all published posts (used by sitemap/RSS). */
|
|
96
92
|
listPublishedPosts(): Promise<Post[]>;
|
|
97
93
|
/**
|
|
98
|
-
* Persist a file under `public/plugins/{pluginName}/{key}` in the
|
|
94
|
+
* Persist a file under `public/plugins/{pluginName}/{key}` in the
|
|
99
95
|
* S3 bucket. Returns the public URL.
|
|
100
96
|
*/
|
|
101
97
|
writePublicAsset(key: string, body: string | Uint8Array, contentType: string): Promise<string>;
|
|
@@ -157,9 +153,9 @@ interface AmplessPlugin {
|
|
|
157
153
|
siteMetadata?(site: Config['site']): PluginMetadata;
|
|
158
154
|
/**
|
|
159
155
|
* Dynamic OG image renderer. The dispatcher route (e.g.
|
|
160
|
-
* `app/
|
|
161
|
-
*
|
|
162
|
-
*
|
|
156
|
+
* `app/og/[slug]/route.ts`) reads this and feeds the element into
|
|
157
|
+
* Next.js `ImageResponse`. Only one plugin should set this — the
|
|
158
|
+
* route resolves the first plugin in `cms.config.plugins` that
|
|
163
159
|
* declares `ogImage`.
|
|
164
160
|
*/
|
|
165
161
|
ogImage?: OgImageConfig;
|
|
@@ -169,9 +165,9 @@ declare function definePlugin(p: AmplessPlugin): AmplessPlugin;
|
|
|
169
165
|
type ContentFormat = 'tiptap' | 'markdown' | 'html' | 'static';
|
|
170
166
|
/**
|
|
171
167
|
* Body shape for `format: 'static'` posts. The actual asset bytes
|
|
172
|
-
* live under S3 at `public/static/<
|
|
173
|
-
*
|
|
174
|
-
*
|
|
168
|
+
* live under S3 at `public/static/<slug>/<files...>` — the body here
|
|
169
|
+
* is the manifest describing which entrypoint to serve and which files
|
|
170
|
+
* are part of the bundle.
|
|
175
171
|
*
|
|
176
172
|
* Stored as JSON in the `body` column (same encoding pattern as the
|
|
177
173
|
* tiptap doc / html string / markdown string for the other formats).
|
|
@@ -201,7 +197,8 @@ type PostStatus = 'draft' | 'published';
|
|
|
201
197
|
* Well-known keys:
|
|
202
198
|
* - `no_layout`: when true, the public page is served as bare HTML
|
|
203
199
|
* (no theme chrome). The runtime's post dispatcher checks this
|
|
204
|
-
* before rendering and redirects to the
|
|
200
|
+
* before rendering and redirects to the unified `/_/<slug>` route
|
|
201
|
+
* handler.
|
|
205
202
|
*
|
|
206
203
|
* Additional keys are passed through unchanged — themes and plugins
|
|
207
204
|
* are free to store their own per-post state here (e.g. SEO overrides,
|
|
@@ -213,7 +210,6 @@ interface PostMetadata {
|
|
|
213
210
|
}
|
|
214
211
|
interface Post {
|
|
215
212
|
postId: string;
|
|
216
|
-
siteId: string;
|
|
217
213
|
slug: string;
|
|
218
214
|
title: string;
|
|
219
215
|
excerpt?: string;
|
|
@@ -226,7 +222,6 @@ interface Post {
|
|
|
226
222
|
}
|
|
227
223
|
interface Page {
|
|
228
224
|
pageId: string;
|
|
229
|
-
siteId: string;
|
|
230
225
|
slug: string;
|
|
231
226
|
title: string;
|
|
232
227
|
format: ContentFormat;
|
|
@@ -236,7 +231,6 @@ interface Page {
|
|
|
236
231
|
}
|
|
237
232
|
interface Media {
|
|
238
233
|
mediaId: string;
|
|
239
|
-
siteId: string;
|
|
240
234
|
src: string;
|
|
241
235
|
mimeType: string;
|
|
242
236
|
size: number;
|
|
@@ -261,21 +255,6 @@ interface MediaProcessingDefaults {
|
|
|
261
255
|
/** Use lossless WebP for PNG inputs (default true). */
|
|
262
256
|
losslessForPng?: boolean;
|
|
263
257
|
}
|
|
264
|
-
/**
|
|
265
|
-
* Per-site override. The top-level `Config.site` provides defaults that
|
|
266
|
-
* fall through when these are unset. `domains` is required because it's
|
|
267
|
-
* how the middleware maps incoming requests to a siteId.
|
|
268
|
-
*/
|
|
269
|
-
interface SiteConfig {
|
|
270
|
-
/** Hostnames this site responds to (subdomains, separate apex domains, both fine). */
|
|
271
|
-
domains: string[];
|
|
272
|
-
/** Override `Config.site.name` for this site. */
|
|
273
|
-
name?: string;
|
|
274
|
-
/** Override `Config.site.url` (canonical) for this site. */
|
|
275
|
-
url?: string;
|
|
276
|
-
/** Override `Config.site.description` for this site. */
|
|
277
|
-
description?: string;
|
|
278
|
-
}
|
|
279
258
|
interface Config {
|
|
280
259
|
site: {
|
|
281
260
|
name: string;
|
|
@@ -304,16 +283,9 @@ interface Config {
|
|
|
304
283
|
* UI locale for the admin app. The scaffolded project ships
|
|
305
284
|
* `locales/<code>.json` dictionaries; defaults are `en` and `ja`.
|
|
306
285
|
* Add a new language by dropping `locales/<code>.json` in and
|
|
307
|
-
* updating the dictionary map in `lib/i18n.ts`.
|
|
308
|
-
* possible via the `locale` site setting.
|
|
286
|
+
* updating the dictionary map in `lib/i18n.ts`.
|
|
309
287
|
*/
|
|
310
288
|
locale?: string;
|
|
311
|
-
/**
|
|
312
|
-
* Multi-site configuration. When 2+ entries are declared, the runtime
|
|
313
|
-
* switches to multi-site mode (host-based routing + Cache-Control:
|
|
314
|
-
* private). When 0 or 1 entries, single-site mode is used.
|
|
315
|
-
*/
|
|
316
|
-
sites?: Record<string, SiteConfig>;
|
|
317
289
|
/**
|
|
318
290
|
* Active plugins. Each entry is the result of a plugin factory call
|
|
319
291
|
* (e.g. `seoPlugin({ ... })`) or a raw AmplessPlugin object. Strings are
|
|
@@ -346,7 +318,6 @@ interface ContentTypeDefinition {
|
|
|
346
318
|
declare function defineSchema<T extends Record<string, ContentTypeDefinition>>(schema: T): T;
|
|
347
319
|
|
|
348
320
|
interface ListOptions {
|
|
349
|
-
siteId?: string;
|
|
350
321
|
limit?: number;
|
|
351
322
|
status?: 'draft' | 'published' | 'all';
|
|
352
323
|
}
|
|
@@ -355,85 +326,20 @@ type CreatePostInput = Omit<Post, 'postId'> & {
|
|
|
355
326
|
};
|
|
356
327
|
interface PostsProvider {
|
|
357
328
|
list(opts?: ListOptions): Promise<Post[]>;
|
|
358
|
-
get(slug: string
|
|
359
|
-
|
|
360
|
-
}): Promise<Post | null>;
|
|
361
|
-
getById(postId: string, opts?: {
|
|
362
|
-
siteId?: string;
|
|
363
|
-
}): Promise<Post | null>;
|
|
329
|
+
get(slug: string): Promise<Post | null>;
|
|
330
|
+
getById(postId: string): Promise<Post | null>;
|
|
364
331
|
create(data: CreatePostInput): Promise<Post>;
|
|
365
|
-
update(postId: string, data: Partial<Post
|
|
366
|
-
|
|
367
|
-
}): Promise<Post>;
|
|
368
|
-
remove(postId: string, opts?: {
|
|
369
|
-
siteId?: string;
|
|
370
|
-
}): Promise<void>;
|
|
332
|
+
update(postId: string, data: Partial<Post>): Promise<Post>;
|
|
333
|
+
remove(postId: string): Promise<void>;
|
|
371
334
|
}
|
|
372
335
|
declare function setPostsProvider(p: PostsProvider): void;
|
|
373
336
|
declare function hasPostsProvider(): boolean;
|
|
374
337
|
declare function listPosts(opts?: ListOptions): Promise<Post[]>;
|
|
375
|
-
declare function getPost(slug: string
|
|
376
|
-
|
|
377
|
-
}): Promise<Post | null>;
|
|
378
|
-
declare function getPostById(postId: string, opts?: {
|
|
379
|
-
siteId?: string;
|
|
380
|
-
}): Promise<Post | null>;
|
|
338
|
+
declare function getPost(slug: string): Promise<Post | null>;
|
|
339
|
+
declare function getPostById(postId: string): Promise<Post | null>;
|
|
381
340
|
declare function createPost(data: CreatePostInput): Promise<Post>;
|
|
382
|
-
declare function updatePost(postId: string, data: Partial<Post
|
|
383
|
-
|
|
384
|
-
}): Promise<Post>;
|
|
385
|
-
declare function deletePost(postId: string, opts?: {
|
|
386
|
-
siteId?: string;
|
|
387
|
-
}): Promise<void>;
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* Single-site fallback identifier. Used when `cms.config.sites` is unset
|
|
391
|
-
* or empty — keeps existing single-site code paths working without
|
|
392
|
-
* special-casing.
|
|
393
|
-
*/
|
|
394
|
-
declare const DEFAULT_SITE_ID = "default";
|
|
395
|
-
/**
|
|
396
|
-
* Resolve a hostname to a configured siteId.
|
|
397
|
-
*
|
|
398
|
-
* - Single-site mode (no `sites` defined / empty / exactly one entry):
|
|
399
|
-
* returns that site's id regardless of host. The single site catches
|
|
400
|
-
* every request. Matches `isMultiSite` (≥2 entries = multi).
|
|
401
|
-
* - Multi-site mode (2+ entries): looks up the host in each site's
|
|
402
|
-
* `domains` list. Returns `null` if the host is not registered
|
|
403
|
-
* (caller should 404).
|
|
404
|
-
*
|
|
405
|
-
* The host comparison is case-insensitive; ports are not stripped here
|
|
406
|
-
* — pass the bare hostname (e.g. `'site-a.example.com'`).
|
|
407
|
-
*/
|
|
408
|
-
declare function resolveSiteId(host: string, config: Config): string | null;
|
|
409
|
-
/**
|
|
410
|
-
* Multi-site mode iff two or more sites are declared. A single declared
|
|
411
|
-
* site is treated as single-site mode (the explicit declaration just lets
|
|
412
|
-
* the operator name the site, but no host disambiguation is needed).
|
|
413
|
-
*/
|
|
414
|
-
declare function isMultiSite(config: Config): boolean;
|
|
415
|
-
/**
|
|
416
|
-
* Site-effective name / url / description for a given siteId. Per-site
|
|
417
|
-
* `sites.{id}.{name|url|description}` overrides the top-level `site.*`
|
|
418
|
-
* defaults; otherwise falls through.
|
|
419
|
-
*/
|
|
420
|
-
declare function siteFor(siteId: string, config: Config): {
|
|
421
|
-
name: string;
|
|
422
|
-
url: string;
|
|
423
|
-
description?: string;
|
|
424
|
-
};
|
|
425
|
-
/**
|
|
426
|
-
* Build the denormalized GSI key for `bySiteIdStatus`. All Post writes
|
|
427
|
-
* (admin client, MCP tools) must set this so the public-read resolvers
|
|
428
|
-
* can do a single Query without table-level filtering.
|
|
429
|
-
*/
|
|
430
|
-
declare function composeSiteIdStatus(siteId: string, status: PostStatus): string;
|
|
431
|
-
/**
|
|
432
|
-
* Build the denormalized GSI key for `bySiteIdSlug`. The public
|
|
433
|
-
* `getPublishedPost(slug)` resolver does an O(1) PK lookup against
|
|
434
|
-
* this index, so every Post write must set it alongside the slug.
|
|
435
|
-
*/
|
|
436
|
-
declare function composeSiteIdSlug(siteId: string, slug: string): string;
|
|
341
|
+
declare function updatePost(postId: string, data: Partial<Post>): Promise<Post>;
|
|
342
|
+
declare function deletePost(postId: string): Promise<void>;
|
|
437
343
|
|
|
438
344
|
interface KvItem<T = unknown> {
|
|
439
345
|
pk: string;
|
|
@@ -459,15 +365,15 @@ declare function hasKvStore(): boolean;
|
|
|
459
365
|
* namespace (e.g. `mcp-tokens`).
|
|
460
366
|
*/
|
|
461
367
|
declare function getKvStore(): KvStore;
|
|
462
|
-
declare const SITE_CONFIG_PK
|
|
463
|
-
declare function getSiteSetting<T = unknown>(
|
|
464
|
-
declare function setSiteSetting(
|
|
465
|
-
declare function deleteSiteSetting(
|
|
368
|
+
declare const SITE_CONFIG_PK = "siteconfig";
|
|
369
|
+
declare function getSiteSetting<T = unknown>(key: string): Promise<T | null>;
|
|
370
|
+
declare function setSiteSetting(key: string, value: unknown): Promise<void>;
|
|
371
|
+
declare function deleteSiteSetting(key: string): Promise<void>;
|
|
466
372
|
/**
|
|
467
|
-
* Fetch every setting
|
|
373
|
+
* Fetch every site setting as a flat map (`{ 'site.name': 'My Blog', ... }`).
|
|
468
374
|
* Use `unflattenSettings` to convert to the nested shape if needed.
|
|
469
375
|
*/
|
|
470
|
-
declare function listSiteSettings(
|
|
376
|
+
declare function listSiteSettings(): Promise<Record<string, unknown>>;
|
|
471
377
|
declare function flattenSettings(obj: Record<string, unknown>, prefix?: string): Record<string, unknown>;
|
|
472
378
|
declare function unflattenSettings(flat: Record<string, unknown>): Record<string, unknown>;
|
|
473
379
|
|
|
@@ -551,6 +457,94 @@ declare function formatPublicAssetUrl(bucket: string, region: string, key: strin
|
|
|
551
457
|
*/
|
|
552
458
|
declare function extractFirstImageUrl(post: Post): string | null;
|
|
553
459
|
|
|
460
|
+
/**
|
|
461
|
+
* Pure helpers for `format: 'static'` post bundles. Kept platform-free
|
|
462
|
+
* (no `File`, no Amplify Storage, no AWS SDK) so both the browser admin
|
|
463
|
+
* uploader and the MCP tools (running in stdio CLI + Lambda HTTP
|
|
464
|
+
* transport) can share the validation / picker / mime logic.
|
|
465
|
+
*
|
|
466
|
+
* The `extractZip(File)` / `uploadBundle` / `deleteBundle` browser
|
|
467
|
+
* helpers stay in `@ampless/admin/lib/static-bundle.js` — they pull in
|
|
468
|
+
* JSZip and `aws-amplify/storage`, neither of which we want in Lambda.
|
|
469
|
+
* The MCP Lambda implements its own zip extractor (`fflate`) on top of
|
|
470
|
+
* the helpers here.
|
|
471
|
+
*/
|
|
472
|
+
declare const DEFAULT_ENTRYPOINT = "index.html";
|
|
473
|
+
/**
|
|
474
|
+
* Maximum bundle size (uncompressed) for the browser-side admin
|
|
475
|
+
* pipeline. Above this the in-tab extract / multi-PUT pipeline gets
|
|
476
|
+
* sluggish. The Lambda / MCP side enforces its own limit (Function URL
|
|
477
|
+
* payload cap, around 6 MB base64-inflated per call) — this constant
|
|
478
|
+
* is exported as a reasonable upper bound for any caller that wants
|
|
479
|
+
* a sanity check, not a hard contract for non-browser paths.
|
|
480
|
+
*/
|
|
481
|
+
declare const MAX_BUNDLE_BYTES: number;
|
|
482
|
+
declare const TEXT_EXTENSIONS: ReadonlySet<string>;
|
|
483
|
+
declare function mimeTypeFor(path: string): string;
|
|
484
|
+
interface ValidationIssue {
|
|
485
|
+
path: string;
|
|
486
|
+
line?: number;
|
|
487
|
+
reason: string;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Reject paths that would either escape the bundle root (`..`), confuse
|
|
491
|
+
* S3 (`null bytes`), or carry OS-specific cruft from zip tools
|
|
492
|
+
* (`__MACOSX/`, `.DS_Store`). Returns `null` when the path is OK,
|
|
493
|
+
* otherwise the reason to surface to the user.
|
|
494
|
+
*/
|
|
495
|
+
declare function validateBundlePath(path: string): string | null;
|
|
496
|
+
/**
|
|
497
|
+
* Scan text files for **absolute** path references. The static-bundle
|
|
498
|
+
* contract is that every reference resolves inside the bundle via
|
|
499
|
+
* relative paths, so any `href="/foo"` would either escape to the
|
|
500
|
+
* Next.js root or break under arbitrary URL prefixes. Protocol-prefixed
|
|
501
|
+
* URLs (`https://…`) and `mailto:` / `tel:` are fine.
|
|
502
|
+
*
|
|
503
|
+
* `data:` and `blob:` URIs also pass (they don't traverse the network).
|
|
504
|
+
* Hash-only (`#anchor`) and empty values pass too.
|
|
505
|
+
*/
|
|
506
|
+
declare function findAbsolutePathRefs(path: string, content: string): ValidationIssue[];
|
|
507
|
+
interface ExtractedFile {
|
|
508
|
+
/** Relative path inside the bundle (slash-separated). */
|
|
509
|
+
path: string;
|
|
510
|
+
data: Uint8Array;
|
|
511
|
+
}
|
|
512
|
+
interface BundleExtractResult {
|
|
513
|
+
files: ExtractedFile[];
|
|
514
|
+
issues: ValidationIssue[];
|
|
515
|
+
totalBytes: number;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Scan every text file in the bundle for absolute URL refs. Returns
|
|
519
|
+
* the union of issues across files so the admin UI / MCP tool can
|
|
520
|
+
* render a single list of problems before saving.
|
|
521
|
+
*/
|
|
522
|
+
declare function validateBundle(files: ExtractedFile[]): ValidationIssue[];
|
|
523
|
+
declare function bundlePrefix(slug: string): string;
|
|
524
|
+
/**
|
|
525
|
+
* If every file shares the same top-level directory (e.g. macOS Finder
|
|
526
|
+
* zipping wraps the contents in a `MyBundle/` folder), strip that
|
|
527
|
+
* prefix so the bundle's logical root is the entrypoint's parent.
|
|
528
|
+
* Bundles already at root pass through unchanged.
|
|
529
|
+
*/
|
|
530
|
+
declare function stripCommonPrefix(files: ExtractedFile[]): ExtractedFile[];
|
|
531
|
+
/**
|
|
532
|
+
* Heuristic for the default entrypoint when the caller didn't override:
|
|
533
|
+
* 1. exact `index.html` at root
|
|
534
|
+
* 2. exact `index.htm` at root
|
|
535
|
+
* 3. the first .html / .htm file at root (alphabetical)
|
|
536
|
+
* 4. the first .html / .htm anywhere
|
|
537
|
+
* 5. fall back to the literal 'index.html' so the validator surfaces
|
|
538
|
+
* a meaningful error to the user ("entrypoint not in bundle")
|
|
539
|
+
*
|
|
540
|
+
* Accepts either extracted files (with `data`) or a plain list of
|
|
541
|
+
* relative paths — the MCP `commit_static_post` flow only has the
|
|
542
|
+
* S3 ListObjects result to work with, no bytes.
|
|
543
|
+
*/
|
|
544
|
+
declare function pickDefaultEntrypoint(files: readonly {
|
|
545
|
+
path: string;
|
|
546
|
+
}[]): string;
|
|
547
|
+
|
|
554
548
|
type ThemeFieldType = 'color' | 'text' | 'select' | 'image' | 'length' | 'fontFamily' | 'linkList';
|
|
555
549
|
/**
|
|
556
550
|
* Single entry in a `linkList` field. Stored as part of a JSON array
|
|
@@ -695,11 +689,9 @@ interface ThemeModule {
|
|
|
695
689
|
*/
|
|
696
690
|
routes?: {
|
|
697
691
|
feed?: (ctx: {
|
|
698
|
-
siteId: string;
|
|
699
692
|
request: Request;
|
|
700
693
|
}) => Promise<Response>;
|
|
701
694
|
sitemap?: (ctx: {
|
|
702
|
-
siteId: string;
|
|
703
695
|
request: Request;
|
|
704
696
|
}) => Promise<Response>;
|
|
705
697
|
};
|
|
@@ -757,11 +749,11 @@ declare function isTagListUrl(url: string): {
|
|
|
757
749
|
/**
|
|
758
750
|
* Resolve effective values for every manifest field, merging stored
|
|
759
751
|
* overrides on top of defaults. `stored` is the flat settings map keyed
|
|
760
|
-
* by `theme.{key}` — typically the output of `listSiteSettings(
|
|
752
|
+
* by `theme.{key}` — typically the output of `listSiteSettings()`
|
|
761
753
|
* filtered to theme entries.
|
|
762
754
|
*/
|
|
763
755
|
declare function resolveThemeValues(manifest: ThemeManifest, stored: Record<string, unknown>): Record<string, string>;
|
|
764
756
|
|
|
765
757
|
declare const VERSION = "0.0.1";
|
|
766
758
|
|
|
767
|
-
export { type AmplessEvent, type AmplessPlugin, type AuthContext, type Config, type ContentEventPayload, type ContentEventType, type ContentFormat, type ContentTypeDefinition, type CreatePostInput,
|
|
759
|
+
export { type AmplessEvent, type AmplessPlugin, type AuthContext, type BundleExtractResult, type Config, type ContentEventPayload, type ContentEventType, type ContentFormat, type ContentTypeDefinition, type CreatePostInput, DEFAULT_ENTRYPOINT, 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 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, createPost, decodeAwsJson, defineConfig, definePlugin, defineSchema, defineTheme, defineThemeModule, deletePost, deleteSiteSetting, detectContentEvents, encodeAwsJson, escapeXml, extractFirstImageUrl, findAbsolutePathRefs, flattenSettings, formatColorPair, formatDate, formatPublicAssetUrl, getKvStore, getPost, getPostById, getSiteSetting, hasKvStore, hasPostsProvider, isTagListUrl, listPosts, listSiteSettings, mimeTypeFor, parseColorPair, parseLinkList, pickDefaultEntrypoint, resolveLocalized, resolveThemeValues, setKvStore, setPostsProvider, setSiteSetting, stringifyLinkList, stripCommonPrefix, themeSettingKey, unflattenSettings, updatePost, validateBundle, validateBundlePath, validateThemeValue };
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,6 @@ function defineSchema(schema) {
|
|
|
12
12
|
var DUMMY_POSTS = [
|
|
13
13
|
{
|
|
14
14
|
postId: "post-001",
|
|
15
|
-
siteId: "default",
|
|
16
15
|
slug: "hello-world",
|
|
17
16
|
title: "Hello, ampless",
|
|
18
17
|
excerpt: "Welcome to your new ampless-powered blog. This is the first post.",
|
|
@@ -24,7 +23,6 @@ var DUMMY_POSTS = [
|
|
|
24
23
|
},
|
|
25
24
|
{
|
|
26
25
|
postId: "post-002",
|
|
27
|
-
siteId: "default",
|
|
28
26
|
slug: "about-ampless",
|
|
29
27
|
title: "About ampless",
|
|
30
28
|
excerpt: "Why ampless exists, and what makes it different from other CMS options.",
|
|
@@ -36,7 +34,6 @@ var DUMMY_POSTS = [
|
|
|
36
34
|
},
|
|
37
35
|
{
|
|
38
36
|
postId: "post-003",
|
|
39
|
-
siteId: "default",
|
|
40
37
|
slug: "getting-started",
|
|
41
38
|
title: "Getting started",
|
|
42
39
|
excerpt: "How to set up your ampless site and deploy it to AWS.",
|
|
@@ -57,8 +54,8 @@ function hasPostsProvider() {
|
|
|
57
54
|
return provider !== null;
|
|
58
55
|
}
|
|
59
56
|
function dummyList(opts = {}) {
|
|
60
|
-
const {
|
|
61
|
-
let posts = DUMMY_POSTS
|
|
57
|
+
const { limit, status = "published" } = opts;
|
|
58
|
+
let posts = DUMMY_POSTS;
|
|
62
59
|
if (status !== "all") posts = posts.filter((p) => p.status === status);
|
|
63
60
|
return limit ? posts.slice(0, limit) : posts;
|
|
64
61
|
}
|
|
@@ -66,60 +63,25 @@ async function listPosts(opts = {}) {
|
|
|
66
63
|
if (provider) return provider.list(opts);
|
|
67
64
|
return dummyList(opts);
|
|
68
65
|
}
|
|
69
|
-
async function getPost(slug
|
|
70
|
-
if (provider) return provider.get(slug
|
|
71
|
-
|
|
72
|
-
return DUMMY_POSTS.find((p) => p.siteId === siteId && p.slug === slug) ?? null;
|
|
66
|
+
async function getPost(slug) {
|
|
67
|
+
if (provider) return provider.get(slug);
|
|
68
|
+
return DUMMY_POSTS.find((p) => p.slug === slug) ?? null;
|
|
73
69
|
}
|
|
74
|
-
async function getPostById(postId
|
|
75
|
-
if (provider) return provider.getById(postId
|
|
76
|
-
|
|
77
|
-
return DUMMY_POSTS.find((p) => p.siteId === siteId && p.postId === postId) ?? null;
|
|
70
|
+
async function getPostById(postId) {
|
|
71
|
+
if (provider) return provider.getById(postId);
|
|
72
|
+
return DUMMY_POSTS.find((p) => p.postId === postId) ?? null;
|
|
78
73
|
}
|
|
79
74
|
async function createPost(data) {
|
|
80
75
|
if (!provider) throw new Error("No posts provider configured. Call setPostsProvider() first.");
|
|
81
76
|
return provider.create(data);
|
|
82
77
|
}
|
|
83
|
-
async function updatePost(postId, data
|
|
78
|
+
async function updatePost(postId, data) {
|
|
84
79
|
if (!provider) throw new Error("No posts provider configured. Call setPostsProvider() first.");
|
|
85
|
-
return provider.update(postId, data
|
|
80
|
+
return provider.update(postId, data);
|
|
86
81
|
}
|
|
87
|
-
async function deletePost(postId
|
|
82
|
+
async function deletePost(postId) {
|
|
88
83
|
if (!provider) throw new Error("No posts provider configured. Call setPostsProvider() first.");
|
|
89
|
-
return provider.remove(postId
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// src/sites.ts
|
|
93
|
-
var DEFAULT_SITE_ID = "default";
|
|
94
|
-
function resolveSiteId(host, config) {
|
|
95
|
-
const sites = config.sites;
|
|
96
|
-
if (!sites || Object.keys(sites).length === 0) {
|
|
97
|
-
return DEFAULT_SITE_ID;
|
|
98
|
-
}
|
|
99
|
-
const ids = Object.keys(sites);
|
|
100
|
-
if (ids.length === 1) return ids[0];
|
|
101
|
-
const lower = host.toLowerCase();
|
|
102
|
-
for (const [id, site] of Object.entries(sites)) {
|
|
103
|
-
if (site.domains.some((d) => d.toLowerCase() === lower)) return id;
|
|
104
|
-
}
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
function isMultiSite(config) {
|
|
108
|
-
return !!config.sites && Object.keys(config.sites).length >= 2;
|
|
109
|
-
}
|
|
110
|
-
function siteFor(siteId, config) {
|
|
111
|
-
const override = config.sites?.[siteId];
|
|
112
|
-
return {
|
|
113
|
-
name: override?.name ?? config.site.name,
|
|
114
|
-
url: override?.url ?? config.site.url,
|
|
115
|
-
description: override?.description ?? config.site.description
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
function composeSiteIdStatus(siteId, status) {
|
|
119
|
-
return `${siteId}#${status}`;
|
|
120
|
-
}
|
|
121
|
-
function composeSiteIdSlug(siteId, slug) {
|
|
122
|
-
return `${siteId}#${slug}`;
|
|
84
|
+
return provider.remove(postId);
|
|
123
85
|
}
|
|
124
86
|
|
|
125
87
|
// src/kv.ts
|
|
@@ -139,18 +101,18 @@ function requireStore() {
|
|
|
139
101
|
function getKvStore() {
|
|
140
102
|
return requireStore();
|
|
141
103
|
}
|
|
142
|
-
var SITE_CONFIG_PK =
|
|
143
|
-
async function getSiteSetting(
|
|
144
|
-
return requireStore().get(SITE_CONFIG_PK
|
|
104
|
+
var SITE_CONFIG_PK = "siteconfig";
|
|
105
|
+
async function getSiteSetting(key) {
|
|
106
|
+
return requireStore().get(SITE_CONFIG_PK, key);
|
|
145
107
|
}
|
|
146
|
-
async function setSiteSetting(
|
|
147
|
-
return requireStore().put(SITE_CONFIG_PK
|
|
108
|
+
async function setSiteSetting(key, value) {
|
|
109
|
+
return requireStore().put(SITE_CONFIG_PK, key, value);
|
|
148
110
|
}
|
|
149
|
-
async function deleteSiteSetting(
|
|
150
|
-
return requireStore().remove(SITE_CONFIG_PK
|
|
111
|
+
async function deleteSiteSetting(key) {
|
|
112
|
+
return requireStore().remove(SITE_CONFIG_PK, key);
|
|
151
113
|
}
|
|
152
|
-
async function listSiteSettings(
|
|
153
|
-
const items = await requireStore().query(SITE_CONFIG_PK
|
|
114
|
+
async function listSiteSettings() {
|
|
115
|
+
const items = await requireStore().query(SITE_CONFIG_PK);
|
|
154
116
|
const out = {};
|
|
155
117
|
for (const item of items) {
|
|
156
118
|
out[item.sk] = item.value;
|
|
@@ -296,6 +258,135 @@ function findHtmlImage(body) {
|
|
|
296
258
|
return m ? m[1] : null;
|
|
297
259
|
}
|
|
298
260
|
|
|
261
|
+
// src/static-bundle.ts
|
|
262
|
+
var DEFAULT_ENTRYPOINT = "index.html";
|
|
263
|
+
var MAX_BUNDLE_BYTES = 50 * 1024 * 1024;
|
|
264
|
+
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
265
|
+
".html",
|
|
266
|
+
".htm",
|
|
267
|
+
".css",
|
|
268
|
+
".svg"
|
|
269
|
+
]);
|
|
270
|
+
var MIME_TYPES = {
|
|
271
|
+
".html": "text/html; charset=utf-8",
|
|
272
|
+
".htm": "text/html; charset=utf-8",
|
|
273
|
+
".css": "text/css; charset=utf-8",
|
|
274
|
+
".js": "application/javascript; charset=utf-8",
|
|
275
|
+
".mjs": "application/javascript; charset=utf-8",
|
|
276
|
+
".json": "application/json; charset=utf-8",
|
|
277
|
+
".svg": "image/svg+xml",
|
|
278
|
+
".png": "image/png",
|
|
279
|
+
".jpg": "image/jpeg",
|
|
280
|
+
".jpeg": "image/jpeg",
|
|
281
|
+
".gif": "image/gif",
|
|
282
|
+
".webp": "image/webp",
|
|
283
|
+
".avif": "image/avif",
|
|
284
|
+
".ico": "image/x-icon",
|
|
285
|
+
".woff": "font/woff",
|
|
286
|
+
".woff2": "font/woff2",
|
|
287
|
+
".ttf": "font/ttf",
|
|
288
|
+
".otf": "font/otf",
|
|
289
|
+
".eot": "application/vnd.ms-fontobject",
|
|
290
|
+
".txt": "text/plain; charset=utf-8",
|
|
291
|
+
".xml": "application/xml; charset=utf-8",
|
|
292
|
+
".map": "application/json; charset=utf-8",
|
|
293
|
+
".pdf": "application/pdf"
|
|
294
|
+
};
|
|
295
|
+
function mimeTypeFor(path) {
|
|
296
|
+
const lower = path.toLowerCase();
|
|
297
|
+
const dot = lower.lastIndexOf(".");
|
|
298
|
+
if (dot < 0) return "application/octet-stream";
|
|
299
|
+
return MIME_TYPES[lower.slice(dot)] ?? "application/octet-stream";
|
|
300
|
+
}
|
|
301
|
+
function validateBundlePath(path) {
|
|
302
|
+
if (path === "" || path.endsWith("/")) return "directory entry";
|
|
303
|
+
if (path.includes("\0")) return "contains null byte";
|
|
304
|
+
if (path.startsWith("/") || path.startsWith("\\")) return "absolute path";
|
|
305
|
+
if (path.split(/[/\\]/).some((seg) => seg === "..")) return "parent-directory traversal";
|
|
306
|
+
if (path.startsWith("__MACOSX/") || /(^|\/)\._/.test(path)) return "macOS resource fork";
|
|
307
|
+
if (/(^|\/)\.DS_Store$/.test(path)) return ".DS_Store junk";
|
|
308
|
+
if (/(^|\/)Thumbs\.db$/i.test(path)) return "Thumbs.db junk";
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
var HTML_URL_ATTR_RE = /\b(?:href|src|action|data|poster|cite|formaction|manifest|srcset)\s*=\s*["']([^"']+)["']/gi;
|
|
312
|
+
var CSS_URL_RE = /url\(\s*["']?([^"')\s]+)["']?\s*\)|@import\s+["']([^"']+)["']/gi;
|
|
313
|
+
function findAbsolutePathRefs(path, content) {
|
|
314
|
+
const ext = path.toLowerCase().slice(path.lastIndexOf("."));
|
|
315
|
+
if (!TEXT_EXTENSIONS.has(ext)) return [];
|
|
316
|
+
const issues = [];
|
|
317
|
+
function check(url, lineHint) {
|
|
318
|
+
const trimmed = url.trim();
|
|
319
|
+
if (!trimmed) return;
|
|
320
|
+
if (trimmed.startsWith("#")) return;
|
|
321
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed)) return;
|
|
322
|
+
if (trimmed.startsWith("//")) {
|
|
323
|
+
issues.push({ path, reason: `protocol-relative URL: ${lineHint}` });
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (trimmed.startsWith("/")) {
|
|
327
|
+
issues.push({ path, reason: `absolute path: ${lineHint}` });
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (ext === ".html" || ext === ".htm" || ext === ".svg") {
|
|
332
|
+
HTML_URL_ATTR_RE.lastIndex = 0;
|
|
333
|
+
let m;
|
|
334
|
+
while ((m = HTML_URL_ATTR_RE.exec(content)) !== null) {
|
|
335
|
+
const val = m[1] ?? "";
|
|
336
|
+
if (/\bsrcset\s*=/i.test(m[0])) {
|
|
337
|
+
for (const candidate of val.split(",")) {
|
|
338
|
+
const urlPart = candidate.trim().split(/\s+/)[0];
|
|
339
|
+
if (urlPart) check(urlPart, candidate.trim());
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
check(val, m[0]);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (ext === ".css" || ext === ".svg") {
|
|
347
|
+
CSS_URL_RE.lastIndex = 0;
|
|
348
|
+
let m;
|
|
349
|
+
while ((m = CSS_URL_RE.exec(content)) !== null) {
|
|
350
|
+
const val = (m[1] ?? m[2] ?? "").trim();
|
|
351
|
+
check(val, m[0]);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return issues;
|
|
355
|
+
}
|
|
356
|
+
function validateBundle(files) {
|
|
357
|
+
const issues = [];
|
|
358
|
+
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
359
|
+
for (const f of files) {
|
|
360
|
+
const ext = f.path.toLowerCase().slice(f.path.lastIndexOf("."));
|
|
361
|
+
if (!TEXT_EXTENSIONS.has(ext)) continue;
|
|
362
|
+
const text = decoder.decode(f.data);
|
|
363
|
+
issues.push(...findAbsolutePathRefs(f.path, text));
|
|
364
|
+
}
|
|
365
|
+
return issues;
|
|
366
|
+
}
|
|
367
|
+
function bundlePrefix(slug) {
|
|
368
|
+
return `public/static/${slug}/`;
|
|
369
|
+
}
|
|
370
|
+
function stripCommonPrefix(files) {
|
|
371
|
+
if (files.length === 0) return files;
|
|
372
|
+
const firstSlash = files[0].path.indexOf("/");
|
|
373
|
+
if (firstSlash < 0) return files;
|
|
374
|
+
const prefix = files[0].path.slice(0, firstSlash + 1);
|
|
375
|
+
if (!files.every((f) => f.path.startsWith(prefix))) return files;
|
|
376
|
+
return files.map((f) => ({ ...f, path: f.path.slice(prefix.length) }));
|
|
377
|
+
}
|
|
378
|
+
function pickDefaultEntrypoint(files) {
|
|
379
|
+
const exact = files.find((f) => f.path === DEFAULT_ENTRYPOINT);
|
|
380
|
+
if (exact) return exact.path;
|
|
381
|
+
const altRoot = files.find((f) => f.path === "index.htm");
|
|
382
|
+
if (altRoot) return altRoot.path;
|
|
383
|
+
const htmlRoot = files.filter((f) => /^[^/]+\.html?$/.test(f.path)).sort((a, b) => a.path.localeCompare(b.path));
|
|
384
|
+
if (htmlRoot.length > 0) return htmlRoot[0].path;
|
|
385
|
+
const htmlAny = files.filter((f) => /\.html?$/.test(f.path)).sort((a, b) => a.path.localeCompare(b.path));
|
|
386
|
+
if (htmlAny.length > 0) return htmlAny[0].path;
|
|
387
|
+
return DEFAULT_ENTRYPOINT;
|
|
388
|
+
}
|
|
389
|
+
|
|
299
390
|
// src/events.ts
|
|
300
391
|
function detectContentEvents(input) {
|
|
301
392
|
switch (input.eventName) {
|
|
@@ -471,11 +562,12 @@ function defaultValueAsString(field) {
|
|
|
471
562
|
// src/index.ts
|
|
472
563
|
var VERSION = "0.0.1";
|
|
473
564
|
export {
|
|
474
|
-
|
|
565
|
+
DEFAULT_ENTRYPOINT,
|
|
566
|
+
MAX_BUNDLE_BYTES,
|
|
475
567
|
SITE_CONFIG_PK,
|
|
568
|
+
TEXT_EXTENSIONS,
|
|
476
569
|
VERSION,
|
|
477
|
-
|
|
478
|
-
composeSiteIdStatus,
|
|
570
|
+
bundlePrefix,
|
|
479
571
|
createPost,
|
|
480
572
|
decodeAwsJson,
|
|
481
573
|
defineConfig,
|
|
@@ -489,6 +581,7 @@ export {
|
|
|
489
581
|
encodeAwsJson,
|
|
490
582
|
escapeXml,
|
|
491
583
|
extractFirstImageUrl,
|
|
584
|
+
findAbsolutePathRefs,
|
|
492
585
|
flattenSettings,
|
|
493
586
|
formatColorPair,
|
|
494
587
|
formatDate,
|
|
@@ -499,22 +592,24 @@ export {
|
|
|
499
592
|
getSiteSetting,
|
|
500
593
|
hasKvStore,
|
|
501
594
|
hasPostsProvider,
|
|
502
|
-
isMultiSite,
|
|
503
595
|
isTagListUrl,
|
|
504
596
|
listPosts,
|
|
505
597
|
listSiteSettings,
|
|
598
|
+
mimeTypeFor,
|
|
506
599
|
parseColorPair,
|
|
507
600
|
parseLinkList,
|
|
601
|
+
pickDefaultEntrypoint,
|
|
508
602
|
resolveLocalized,
|
|
509
|
-
resolveSiteId,
|
|
510
603
|
resolveThemeValues,
|
|
511
604
|
setKvStore,
|
|
512
605
|
setPostsProvider,
|
|
513
606
|
setSiteSetting,
|
|
514
|
-
siteFor,
|
|
515
607
|
stringifyLinkList,
|
|
608
|
+
stripCommonPrefix,
|
|
516
609
|
themeSettingKey,
|
|
517
610
|
unflattenSettings,
|
|
518
611
|
updatePost,
|
|
612
|
+
validateBundle,
|
|
613
|
+
validateBundlePath,
|
|
519
614
|
validateThemeValue
|
|
520
615
|
};
|