ampless 1.0.0-alpha.17 → 1.0.0-alpha.19
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 +177 -7
- package/dist/index.js +131 -0
- package/docs/plugin-author-guide.ja.md +522 -0
- package/docs/plugin-author-guide.md +662 -0
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -283,9 +283,11 @@ type TrustLevel = 'untrusted' | 'trusted' | 'privileged';
|
|
|
283
283
|
* declaration-vs-implementation reconciliation warnings and (in later
|
|
284
284
|
* phases) for `allowCapabilities` gating in `cms.config.ts`.
|
|
285
285
|
*
|
|
286
|
-
*
|
|
286
|
+
* Active capabilities:
|
|
287
287
|
* - `publicHead` / `publicBody`: descriptor-based head/body injection.
|
|
288
288
|
* - `metadata` / `eventHooks`: name-only declaration for existing surfaces.
|
|
289
|
+
* - `adminSettings`: admin-managed public settings manifest.
|
|
290
|
+
* - `writePublicAsset`: trusted hook context can write namespaced public assets.
|
|
289
291
|
*
|
|
290
292
|
* Reserved capabilities are accepted by the type so that plugins can
|
|
291
293
|
* declare future intent, but the runtime does nothing with them yet —
|
|
@@ -293,7 +295,7 @@ type TrustLevel = 'untrusted' | 'trusted' | 'privileged';
|
|
|
293
295
|
* capability today is harmless, but the runtime won't expose any new
|
|
294
296
|
* surface for it until the matching phase ships.
|
|
295
297
|
*/
|
|
296
|
-
type PluginCapability = 'publicHead' | 'publicBody' | 'metadata' | 'eventHooks' | 'adminSettings' | '
|
|
298
|
+
type PluginCapability = 'publicHead' | 'publicBody' | 'metadata' | 'eventHooks' | 'adminSettings' | 'writePublicAsset' | 'schema' | 'contentFields' | 'adminPage' | 'serverRoute' | 'secretSettings' | 'network' | 'scheduler' | 'storageWrite' | 'privilegedSystem';
|
|
297
299
|
/**
|
|
298
300
|
* Loading strategy for `script` / `inlineScript` descriptors.
|
|
299
301
|
*
|
|
@@ -314,13 +316,27 @@ type PluginCapability = 'publicHead' | 'publicBody' | 'metadata' | 'eventHooks'
|
|
|
314
316
|
type ScriptStrategy = 'afterInteractive' | 'lazyOnload';
|
|
315
317
|
/**
|
|
316
318
|
* Context passed to `publicHead` / `publicBodyEnd`. Phase 1 carries
|
|
317
|
-
* only the site-wide config block
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
* an admin-managed accessor here.
|
|
319
|
+
* only the site-wide config block. Phase 2 adds the `setting()`
|
|
320
|
+
* accessor for admin-managed `settings.public` values; per-route /
|
|
321
|
+
* per-post context lands in Phase 4 (`plugin-per-post-rfp.md`).
|
|
321
322
|
*/
|
|
322
323
|
interface PluginPublicRenderContext {
|
|
323
324
|
site: Config['site'];
|
|
325
|
+
/**
|
|
326
|
+
* Resolve a public setting value for the active plugin instance.
|
|
327
|
+
* Returns `stored ?? manifest.default ?? undefined`. Stored values
|
|
328
|
+
* are read at request time from the DDB → S3 site-settings cache
|
|
329
|
+
* pipeline. Bound by the runtime when the plugin declares
|
|
330
|
+
* `settings.public` — plugins without a manifest can still call
|
|
331
|
+
* this, in which case it always returns `undefined`.
|
|
332
|
+
*
|
|
333
|
+
* The generic type is a convenience cast: the runtime does not
|
|
334
|
+
* coerce values, so callers should pick `T` to match the field's
|
|
335
|
+
* declared type (`text` → `string`, `number` → `number`, etc.).
|
|
336
|
+
* Validation at admin save time guarantees stored values match the
|
|
337
|
+
* field's declared shape, so the cast is safe in practice.
|
|
338
|
+
*/
|
|
339
|
+
setting<T = unknown>(key: string): T | undefined;
|
|
324
340
|
}
|
|
325
341
|
/**
|
|
326
342
|
* Descriptor returned by `publicHead()`. The runtime validates each
|
|
@@ -474,6 +490,95 @@ interface OgImageConfig {
|
|
|
474
490
|
*/
|
|
475
491
|
render(ctx: OgImageRenderContext): Promise<unknown> | unknown;
|
|
476
492
|
}
|
|
493
|
+
/**
|
|
494
|
+
* Shared shape for every `PluginSettingField` variant. `T` is the
|
|
495
|
+
* decoded value type (string for text-like fields, number for number,
|
|
496
|
+
* boolean for boolean, etc.). All variants share `key` / `label` /
|
|
497
|
+
* `default` / `required`; type-specific constraints (e.g. `pattern`,
|
|
498
|
+
* `min`, `options`) live on the discriminated branches.
|
|
499
|
+
*/
|
|
500
|
+
interface PluginFieldBase<T> {
|
|
501
|
+
/**
|
|
502
|
+
* Storage key. Stored as `plugins.<instanceId>.<key>`. Must match
|
|
503
|
+
* `PLUGIN_KEY_PATTERN` (`/^[a-zA-Z0-9_-]+$/`) so the `pk.sk` dotted
|
|
504
|
+
* separator survives. Violations are skipped + warned by the
|
|
505
|
+
* runtime/admin normalization pass.
|
|
506
|
+
*/
|
|
507
|
+
key: string;
|
|
508
|
+
label: LocalizedString;
|
|
509
|
+
description?: LocalizedString;
|
|
510
|
+
/** Used by the runtime when no admin-stored value is present. */
|
|
511
|
+
default?: T;
|
|
512
|
+
/**
|
|
513
|
+
* When true, an empty / undefined value is rejected at save time
|
|
514
|
+
* and the resolver returns `undefined`. When false (default),
|
|
515
|
+
* string-like fields accept empty string as a valid "disabled"
|
|
516
|
+
* value while non-string fields still reject empty strings.
|
|
517
|
+
*/
|
|
518
|
+
required?: boolean;
|
|
519
|
+
/** Optional UI grouping (mirrors `ThemeField.group`). */
|
|
520
|
+
group?: LocalizedString;
|
|
521
|
+
}
|
|
522
|
+
interface PluginTextField extends PluginFieldBase<string> {
|
|
523
|
+
type: 'text';
|
|
524
|
+
maxLength?: number;
|
|
525
|
+
/** RegExp source (e.g. `'^G-[A-Z0-9]+$'`). Validated against
|
|
526
|
+
* non-empty values; empty string skips the check. */
|
|
527
|
+
pattern?: string;
|
|
528
|
+
/** Placeholder for the admin input. */
|
|
529
|
+
placeholder?: string;
|
|
530
|
+
}
|
|
531
|
+
interface PluginTextareaField extends PluginFieldBase<string> {
|
|
532
|
+
type: 'textarea';
|
|
533
|
+
maxLength?: number;
|
|
534
|
+
placeholder?: string;
|
|
535
|
+
/** Rendered rows hint for the admin textarea. */
|
|
536
|
+
rows?: number;
|
|
537
|
+
}
|
|
538
|
+
interface PluginBooleanField extends PluginFieldBase<boolean> {
|
|
539
|
+
type: 'boolean';
|
|
540
|
+
}
|
|
541
|
+
interface PluginNumberField extends PluginFieldBase<number> {
|
|
542
|
+
type: 'number';
|
|
543
|
+
min?: number;
|
|
544
|
+
max?: number;
|
|
545
|
+
step?: number;
|
|
546
|
+
}
|
|
547
|
+
interface PluginSelectField extends PluginFieldBase<string> {
|
|
548
|
+
type: 'select';
|
|
549
|
+
options: ReadonlyArray<{
|
|
550
|
+
value: string;
|
|
551
|
+
label: LocalizedString;
|
|
552
|
+
}>;
|
|
553
|
+
}
|
|
554
|
+
interface PluginUrlField extends PluginFieldBase<string> {
|
|
555
|
+
type: 'url';
|
|
556
|
+
placeholder?: string;
|
|
557
|
+
/** When true, relative paths (`/foo`, `./foo`) pass validation; default true. */
|
|
558
|
+
allowRelative?: boolean;
|
|
559
|
+
}
|
|
560
|
+
interface PluginCodeField extends PluginFieldBase<string> {
|
|
561
|
+
type: 'code';
|
|
562
|
+
/** Display-only language label (e.g. `'js'`, `'css'`, `'html'`). */
|
|
563
|
+
language?: string;
|
|
564
|
+
maxLength?: number;
|
|
565
|
+
placeholder?: string;
|
|
566
|
+
rows?: number;
|
|
567
|
+
}
|
|
568
|
+
interface PluginJsonField extends PluginFieldBase<unknown> {
|
|
569
|
+
type: 'json';
|
|
570
|
+
placeholder?: string;
|
|
571
|
+
rows?: number;
|
|
572
|
+
}
|
|
573
|
+
type PluginSettingField = PluginTextField | PluginTextareaField | PluginBooleanField | PluginNumberField | PluginSelectField | PluginUrlField | PluginCodeField | PluginJsonField;
|
|
574
|
+
/**
|
|
575
|
+
* Per-plugin settings declaration. Phase 2 implements `public`;
|
|
576
|
+
* `secret` is reserved for Phase 6a (admin-only storage; never reaches
|
|
577
|
+
* the public runtime).
|
|
578
|
+
*/
|
|
579
|
+
interface PluginSettingsManifest {
|
|
580
|
+
public?: readonly PluginSettingField[];
|
|
581
|
+
}
|
|
477
582
|
interface AmplessPlugin {
|
|
478
583
|
name: string;
|
|
479
584
|
/** Plugin API version. Currently 1; future versions will be additive. */
|
|
@@ -501,6 +606,15 @@ interface AmplessPlugin {
|
|
|
501
606
|
* continue to work unchanged.
|
|
502
607
|
*/
|
|
503
608
|
capabilities?: readonly PluginCapability[];
|
|
609
|
+
/**
|
|
610
|
+
* Public, admin-editable settings (Phase 2). Each declared field is
|
|
611
|
+
* stored under `pk='siteconfig', sk='plugins.<instanceId>.<key>'`,
|
|
612
|
+
* mirrored to S3 via the trusted processor, and surfaced to
|
|
613
|
+
* `publicHead` / `publicBodyEnd` via `ctx.setting<T>(key)`. Plugins
|
|
614
|
+
* without an admin UI continue to work — they just don't have a
|
|
615
|
+
* `settings` block.
|
|
616
|
+
*/
|
|
617
|
+
settings?: PluginSettingsManifest;
|
|
504
618
|
/** Async event hooks. Run in trust_level-matched Lambda. */
|
|
505
619
|
hooks?: {
|
|
506
620
|
[K in EventType]?: PluginEventHandler<K>;
|
|
@@ -914,6 +1028,62 @@ declare function decodeAwsJson(value: unknown): unknown;
|
|
|
914
1028
|
*/
|
|
915
1029
|
declare function formatPublicAssetUrl(bucket: string, region: string, key: string): string;
|
|
916
1030
|
|
|
1031
|
+
/**
|
|
1032
|
+
* Validate the user-supplied key for ctx.writePublicAsset.
|
|
1033
|
+
*
|
|
1034
|
+
* The trusted Lambda still has a processor-wide S3 grant, so path safety is
|
|
1035
|
+
* enforced at the runtime context boundary before the key is joined under the
|
|
1036
|
+
* plugin namespace.
|
|
1037
|
+
*/
|
|
1038
|
+
declare function validatePublicAssetKey(key: string): string | null;
|
|
1039
|
+
|
|
1040
|
+
declare const PLUGIN_KEY_PATTERN: RegExp;
|
|
1041
|
+
declare function isValidPluginKey(key: string): boolean;
|
|
1042
|
+
/**
|
|
1043
|
+
* Validate one raw value against a field definition. Returns the
|
|
1044
|
+
* (possibly-coerced) value on success, or `null` to signal rejection.
|
|
1045
|
+
*
|
|
1046
|
+
* Semantics:
|
|
1047
|
+
* - **string-like fields** (text / textarea / url / code) accept
|
|
1048
|
+
* `''` as valid when `required` is falsy. This is the "disable"
|
|
1049
|
+
* sentinel — e.g. `pattern: '^$|^G-...'` lets a GA4 plugin save
|
|
1050
|
+
* an empty measurementId to suppress the loader without dropping
|
|
1051
|
+
* the row.
|
|
1052
|
+
* - **non-string-like fields** (number / boolean / json / select)
|
|
1053
|
+
* always reject `''`. Allowing it would force callers to handle a
|
|
1054
|
+
* `string` reading where `number | undefined` is declared. To
|
|
1055
|
+
* "unset" these fields, delete the row (admin form's "Reset to
|
|
1056
|
+
* default" button).
|
|
1057
|
+
* - `undefined` is treated as "not set" by `resolvePluginSettings`
|
|
1058
|
+
* — `validatePluginSettingValue` itself returns `null` for it.
|
|
1059
|
+
* - Constraints (`pattern`, `min`, `max`, `maxLength`, `options`) run
|
|
1060
|
+
* only when the value is present and non-empty.
|
|
1061
|
+
*
|
|
1062
|
+
* The returned shape preserves the declared type — `text` stores a
|
|
1063
|
+
* sanitized string, `number` stores a `number`, `boolean` stores a
|
|
1064
|
+
* `boolean`, `json` stores the decoded value (object / array /
|
|
1065
|
+
* primitive — never the source string), etc.
|
|
1066
|
+
*/
|
|
1067
|
+
declare function validatePluginSettingValue(field: PluginSettingField, raw: unknown): unknown | null;
|
|
1068
|
+
/**
|
|
1069
|
+
* Merge stored values on top of manifest defaults for one plugin
|
|
1070
|
+
* instance. Both sides are validated through `validatePluginSettingValue`
|
|
1071
|
+
* — stored values that fail validation (manual DDB tampering, schema
|
|
1072
|
+
* drift) fall back to the validated default; defaults that themselves
|
|
1073
|
+
* fail validation surface as `undefined`.
|
|
1074
|
+
*
|
|
1075
|
+
* `stored` is a flat key → value map keyed by the field's `key` (not
|
|
1076
|
+
* the full DDB SK). The runtime extracts it from the site-settings
|
|
1077
|
+
* snapshot using the `plugins.<instanceId>.` prefix.
|
|
1078
|
+
*
|
|
1079
|
+
* Validation runs on the default too because the default can come
|
|
1080
|
+
* from a plugin's constructor argument (e.g. GA4 takes a
|
|
1081
|
+
* `measurementId` option and passes it through as the field's
|
|
1082
|
+
* `default`). If the operator supplies a bogus value at install time
|
|
1083
|
+
* we'd rather surface `undefined` than render garbage.
|
|
1084
|
+
*/
|
|
1085
|
+
declare function resolvePluginSettings(manifest: PluginSettingsManifest | undefined, stored: Record<string, unknown>): Record<string, unknown>;
|
|
1086
|
+
|
|
917
1087
|
/**
|
|
918
1088
|
* Extract the first image URL from a post body. Used by plugins (e.g.
|
|
919
1089
|
* plugin-og-image) that want to derive an image from the post content
|
|
@@ -1019,4 +1189,4 @@ declare function pickDefaultEntrypoint(files: readonly {
|
|
|
1019
1189
|
|
|
1020
1190
|
declare const VERSION = "0.0.1";
|
|
1021
1191
|
|
|
1022
|
-
export { type AmplessEvent, type AmplessPlugin, type AuthContext, type BundleExtractResult, type CacheConfig, type CacheStrategy, 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 MediaMetadata, type MediaProcessingDefaults, type OgImageConfig, type OgImageFont, type OgImageRenderContext, type Page, type PluginCapability, type PluginEventHandler, type PluginMetadata, type PluginPublicRenderContext, type PluginRuntimeContext, type Post, type PostIndexEventPayload, type PostIndexEventType, type PostMetadata, type PostStatus, type PostsProvider, type PublicBodyDescriptor, type PublicHeadDescriptor, type Role, SITE_CONFIG_PK, type ScriptStrategy, type SiteSettingsEventPayload, type SiteSettingsEventType, type StaticPostBody, type StaticPostFileMeta, 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 };
|
|
1192
|
+
export { type AmplessEvent, type AmplessPlugin, type AuthContext, type BundleExtractResult, type CacheConfig, type CacheStrategy, 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 MediaMetadata, type MediaProcessingDefaults, type OgImageConfig, type OgImageFont, type OgImageRenderContext, PLUGIN_KEY_PATTERN, type Page, type PluginBooleanField, type PluginCapability, type PluginCodeField, type PluginEventHandler, type PluginJsonField, type PluginMetadata, type PluginNumberField, type PluginPublicRenderContext, type PluginRuntimeContext, type PluginSelectField, type PluginSettingField, type PluginSettingsManifest, type PluginTextField, type PluginTextareaField, type PluginUrlField, type Post, type PostIndexEventPayload, type PostIndexEventType, type PostMetadata, type PostStatus, type PostsProvider, type PublicBodyDescriptor, type PublicHeadDescriptor, type Role, SITE_CONFIG_PK, type ScriptStrategy, type SiteSettingsEventPayload, type SiteSettingsEventType, type StaticPostBody, type StaticPostFileMeta, 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, isValidPluginKey, listPosts, listSiteSettings, mimeTypeFor, parseColorPair, parseLinkList, pickDefaultEntrypoint, resolveLocalized, resolvePluginSettings, resolveThemeValues, setKvStore, setPostsProvider, setSiteSetting, stringifyLinkList, stripCommonPrefix, themeSettingKey, unflattenSettings, updatePost, validateBundle, validateBundlePath, validatePluginSettingValue, validatePublicAssetKey, validateThemeValue };
|
package/dist/index.js
CHANGED
|
@@ -215,6 +215,132 @@ function definePlugin(p) {
|
|
|
215
215
|
return p;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
// src/plugin-asset-key.ts
|
|
219
|
+
function validatePublicAssetKey(key) {
|
|
220
|
+
if (key.length === 0) return "key must not be empty";
|
|
221
|
+
if (key.length > 256) return "key must be 256 characters or less";
|
|
222
|
+
if (key.startsWith("/")) return 'key must be relative and must not start with "/"';
|
|
223
|
+
if (key.includes("\\")) return "key must not contain backslashes";
|
|
224
|
+
if (/[\u0000-\u001f\u007f]/.test(key)) {
|
|
225
|
+
return "key must not contain control characters";
|
|
226
|
+
}
|
|
227
|
+
const segments = key.split("/");
|
|
228
|
+
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
229
|
+
return 'key must not contain "." or ".." path segments';
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/plugin-settings.ts
|
|
235
|
+
var PLUGIN_KEY_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
236
|
+
function isValidPluginKey(key) {
|
|
237
|
+
return typeof key === "string" && PLUGIN_KEY_PATTERN.test(key);
|
|
238
|
+
}
|
|
239
|
+
function validatePluginSettingValue(field, raw) {
|
|
240
|
+
if (raw === void 0) return null;
|
|
241
|
+
switch (field.type) {
|
|
242
|
+
case "text":
|
|
243
|
+
case "textarea":
|
|
244
|
+
case "code": {
|
|
245
|
+
if (typeof raw !== "string") return null;
|
|
246
|
+
const sanitized = raw.replace(/[\x00-\x1f<>]/g, "");
|
|
247
|
+
if (sanitized === "") {
|
|
248
|
+
return field.required ? null : "";
|
|
249
|
+
}
|
|
250
|
+
const maxLength = field.type === "text" && field.maxLength || field.type === "textarea" && field.maxLength || field.type === "code" && field.maxLength || void 0;
|
|
251
|
+
if (typeof maxLength === "number" && sanitized.length > maxLength) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
if (field.type === "text" && field.pattern) {
|
|
255
|
+
let re;
|
|
256
|
+
try {
|
|
257
|
+
re = new RegExp(field.pattern);
|
|
258
|
+
} catch {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
if (!re.test(sanitized)) return null;
|
|
262
|
+
}
|
|
263
|
+
return sanitized;
|
|
264
|
+
}
|
|
265
|
+
case "url": {
|
|
266
|
+
if (typeof raw !== "string") return null;
|
|
267
|
+
const trimmed = raw.trim();
|
|
268
|
+
if (trimmed === "") {
|
|
269
|
+
return field.required ? null : "";
|
|
270
|
+
}
|
|
271
|
+
if (/^\s*(javascript|vbscript|data|blob|file):/i.test(trimmed)) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
const allowRelative = field.allowRelative !== false;
|
|
275
|
+
const schemeMatch = trimmed.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/);
|
|
276
|
+
if (schemeMatch) {
|
|
277
|
+
const scheme = schemeMatch[1].toLowerCase();
|
|
278
|
+
if (scheme !== "http" && scheme !== "https") return null;
|
|
279
|
+
} else if (!allowRelative) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
return trimmed;
|
|
283
|
+
}
|
|
284
|
+
case "boolean": {
|
|
285
|
+
if (typeof raw === "boolean") return raw;
|
|
286
|
+
if (raw === "true" || raw === 1) return true;
|
|
287
|
+
if (raw === "false" || raw === 0) return false;
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
case "number": {
|
|
291
|
+
if (typeof raw === "number") {
|
|
292
|
+
if (Number.isNaN(raw)) return null;
|
|
293
|
+
} else if (typeof raw === "string") {
|
|
294
|
+
const trimmed = raw.trim();
|
|
295
|
+
if (trimmed === "") return null;
|
|
296
|
+
const n2 = Number(trimmed);
|
|
297
|
+
if (Number.isNaN(n2)) return null;
|
|
298
|
+
raw = n2;
|
|
299
|
+
} else {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
const n = raw;
|
|
303
|
+
if (typeof field.min === "number" && n < field.min) return null;
|
|
304
|
+
if (typeof field.max === "number" && n > field.max) return null;
|
|
305
|
+
return n;
|
|
306
|
+
}
|
|
307
|
+
case "select": {
|
|
308
|
+
if (typeof raw !== "string") return null;
|
|
309
|
+
if (raw === "") return null;
|
|
310
|
+
if (!field.options.some((opt) => opt.value === raw)) return null;
|
|
311
|
+
return raw;
|
|
312
|
+
}
|
|
313
|
+
case "json": {
|
|
314
|
+
if (raw === "") return null;
|
|
315
|
+
if (typeof raw === "string") return null;
|
|
316
|
+
return raw;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function resolvePluginSettings(manifest, stored) {
|
|
321
|
+
const out = {};
|
|
322
|
+
const fields = manifest?.public;
|
|
323
|
+
if (!fields) return out;
|
|
324
|
+
for (const field of fields) {
|
|
325
|
+
if (!isValidPluginKey(field.key)) {
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
let resolved = void 0;
|
|
329
|
+
if (Object.prototype.hasOwnProperty.call(stored, field.key)) {
|
|
330
|
+
const validated = validatePluginSettingValue(field, stored[field.key]);
|
|
331
|
+
if (validated !== null) resolved = validated;
|
|
332
|
+
}
|
|
333
|
+
if (resolved === void 0 && field.default !== void 0) {
|
|
334
|
+
const validatedDefault = validatePluginSettingValue(field, field.default);
|
|
335
|
+
if (validatedDefault !== null) resolved = validatedDefault;
|
|
336
|
+
}
|
|
337
|
+
if (resolved !== void 0) {
|
|
338
|
+
out[field.key] = resolved;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return out;
|
|
342
|
+
}
|
|
343
|
+
|
|
218
344
|
// src/post-images.ts
|
|
219
345
|
function extractFirstImageUrl(post) {
|
|
220
346
|
switch (post.format) {
|
|
@@ -560,6 +686,7 @@ var VERSION = "0.0.1";
|
|
|
560
686
|
export {
|
|
561
687
|
DEFAULT_ENTRYPOINT,
|
|
562
688
|
MAX_BUNDLE_BYTES,
|
|
689
|
+
PLUGIN_KEY_PATTERN,
|
|
563
690
|
SITE_CONFIG_PK,
|
|
564
691
|
TEXT_EXTENSIONS,
|
|
565
692
|
VERSION,
|
|
@@ -589,6 +716,7 @@ export {
|
|
|
589
716
|
hasKvStore,
|
|
590
717
|
hasPostsProvider,
|
|
591
718
|
isTagListUrl,
|
|
719
|
+
isValidPluginKey,
|
|
592
720
|
listPosts,
|
|
593
721
|
listSiteSettings,
|
|
594
722
|
mimeTypeFor,
|
|
@@ -596,6 +724,7 @@ export {
|
|
|
596
724
|
parseLinkList,
|
|
597
725
|
pickDefaultEntrypoint,
|
|
598
726
|
resolveLocalized,
|
|
727
|
+
resolvePluginSettings,
|
|
599
728
|
resolveThemeValues,
|
|
600
729
|
setKvStore,
|
|
601
730
|
setPostsProvider,
|
|
@@ -607,5 +736,7 @@ export {
|
|
|
607
736
|
updatePost,
|
|
608
737
|
validateBundle,
|
|
609
738
|
validateBundlePath,
|
|
739
|
+
validatePluginSettingValue,
|
|
740
|
+
validatePublicAssetKey,
|
|
610
741
|
validateThemeValue
|
|
611
742
|
};
|