ampless 1.0.0-alpha.18 → 1.0.0-alpha.20

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 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
- * Phase 1 active capabilities:
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' | 'schema' | 'writePublicAsset' | 'contentFields' | 'adminPage' | 'serverRoute' | 'secretSettings' | 'network' | 'scheduler' | 'storageWrite' | 'privilegedSystem';
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
  *
@@ -1026,6 +1028,15 @@ declare function decodeAwsJson(value: unknown): unknown;
1026
1028
  */
1027
1029
  declare function formatPublicAssetUrl(bucket: string, region: string, key: string): string;
1028
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
+
1029
1040
  declare const PLUGIN_KEY_PATTERN: RegExp;
1030
1041
  declare function isValidPluginKey(key: string): boolean;
1031
1042
  /**
@@ -1178,4 +1189,4 @@ declare function pickDefaultEntrypoint(files: readonly {
1178
1189
 
1179
1190
  declare const VERSION = "0.0.1";
1180
1191
 
1181
- 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, 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,25 @@ 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
+ if (!/^[A-Za-z0-9._/-]+$/.test(key)) {
228
+ return 'key may only contain ASCII letters, digits, ".", "_", "-", and "/" (path separator)';
229
+ }
230
+ const segments = key.split("/");
231
+ if (segments.some((segment) => segment === "." || segment === "..")) {
232
+ return 'key must not contain "." or ".." path segments';
233
+ }
234
+ return null;
235
+ }
236
+
218
237
  // src/plugin-settings.ts
219
238
  var PLUGIN_KEY_PATTERN = /^[a-zA-Z0-9_-]+$/;
220
239
  function isValidPluginKey(key) {
@@ -721,5 +740,6 @@ export {
721
740
  validateBundle,
722
741
  validateBundlePath,
723
742
  validatePluginSettingValue,
743
+ validatePublicAssetKey,
724
744
  validateThemeValue
725
745
  };
@@ -139,7 +139,7 @@ analyticsGa4Plugin({ instanceId: 'product' })
139
139
  | 階層 | IAM | 用途 |
140
140
  |---|---|---|
141
141
  | `untrusted` | なし (SQS consume のみ) | head/body descriptor、webhook 配送、コンテンツ変換 |
142
- | `trusted` | 投稿読み出し、`public/plugins/<name>/...` への書き込み | RSS フィード、sitemap、計算済み JSON インデックス |
142
+ | `trusted` | 投稿読み出し、`public/plugins/<instanceId ?? name>/...` への書き込み | RSS フィード、sitemap、計算済み JSON インデックス |
143
143
  | `privileged` | 予約 | 将来: SES、secret、private S3 |
144
144
 
145
145
  決め方の目安:
@@ -273,6 +273,26 @@ hooks: {
273
273
  }
274
274
  ```
275
275
 
276
+ ### `writePublicAsset`
277
+
278
+ 公開生成ファイルを書き出す trusted plugin は capability を宣言してください:
279
+
280
+ ```ts
281
+ capabilities: ['eventHooks', 'writePublicAsset']
282
+ ```
283
+
284
+ 同じ plugin が `metadata()` または `siteMetadata()` も実装する場合は、`metadata` も宣言します。この capability 名は両方の metadata 関数をまとめて表し、別個の `siteMetadata` capability はありません。
285
+
286
+ trusted processor は次の場所に書き込みます:
287
+
288
+ ```txt
289
+ public/plugins/<instanceId ?? name>/<key>
290
+ ```
291
+
292
+ `key` は allowlist `[A-Za-z0-9._/-]+` に一致する必要があります。それ以外(スペース、URL 予約文字 `#` `?` `&` `=` `+`、非 ASCII 文字 (`日本語.xml` 等)、空文字、絶対パス(`/` 始まり)、`.` / `..` path segment、backslash、制御文字、256 文字超)は S3 呼び出し前に拒否されます。`indexes/posts.json` のような nested path や `feed.v2.xml` のような複数 dot は許可されます。allowlist を厳しく絞っているのは、返却 URL と実 S3 key を byte 等しい文字列に保つため — URL 予約文字は S3 では生バイトとして通るが、URL を consumer が parse すると別 object を指す状態になる。user 由来の文字を key に入れたい場合は事前に sanitize (hash、slugify 等) してから `ctx.writePublicAsset()` を呼んでください。戻り値は書き込まれた object の public URL です。
293
+
294
+ 移行期間中、`capabilities` フィールドが無い plugin はそのまま動きます。`capabilities` を宣言しているのに `writePublicAsset` を省いた plugin は、実際に `ctx.writePublicAsset()` を呼んだ時に 1 回だけ warn します。
295
+
276
296
  ### ベストプラクティス
277
297
 
278
298
  - **冪等にする**。SQS は at-least-once 配送 — 同じイベントが 2 回 fire する可能性があります。同じ入力で同じ出力 (決定論的なフィード等) を生成するようにしてください
@@ -467,6 +487,8 @@ it('admin が空文字保存した場合は空配列', () => {
467
487
  参考実装:
468
488
 
469
489
  - [`packages/plugin-analytics-ga4`](https://github.com/heavymoons/ampless/tree/main/packages/plugin-analytics-ga4) — descriptor ベース、Phase 2 settings
490
+ - [`packages/plugin-gtm`](https://github.com/heavymoons/ampless/tree/main/packages/plugin-gtm) — `publicHead`(ローダーインラインスクリプト)+ `publicBodyEnd`(`<noscript>` iframe フォールバック)を両方使用、コンテナ ID は admin 編集可能
491
+ - [`packages/plugin-plausible`](https://github.com/heavymoons/ampless/tree/main/packages/plugin-plausible) — `data-*` attrs 付きの単一 `<script>` descriptor、`required` な URL field(self-hosted Plausible 上書き対応)
470
492
  - [`packages/plugin-rss`](https://github.com/heavymoons/ampless/tree/main/packages/plugin-rss) — trusted、非同期 hooks + `writePublicAsset`
471
493
  - [`packages/plugin-seo`](https://github.com/heavymoons/ampless/tree/main/packages/plugin-seo) — `metadata()` + `siteMetadata()`
472
494
  - [`packages/plugin-webhook`](https://github.com/heavymoons/ampless/tree/main/packages/plugin-webhook) — untrusted hook + 外向き HTTP
@@ -168,7 +168,7 @@ touch IAM):
168
168
  | Tier | IAM | Used by |
169
169
  |---|---|---|
170
170
  | `untrusted` | none (SQS consume only) | head/body descriptors, webhook delivery, content transforms |
171
- | `trusted` | read posts, write `public/plugins/<name>/...` | RSS feed, sitemap, computed JSON indexes |
171
+ | `trusted` | read posts, write `public/plugins/<instanceId ?? name>/...` | RSS feed, sitemap, computed JSON indexes |
172
172
  | `privileged` | reserved | future: SES, secrets, private S3 |
173
173
 
174
174
  Rule of thumb:
@@ -332,6 +332,44 @@ hooks: {
332
332
  }
333
333
  ```
334
334
 
335
+ ### `writePublicAsset`
336
+
337
+ Trusted plugins that write public generated files should declare the
338
+ capability:
339
+
340
+ ```ts
341
+ capabilities: ['eventHooks', 'writePublicAsset']
342
+ ```
343
+
344
+ If the same plugin implements `metadata()` or `siteMetadata()`, also
345
+ declare `metadata`. That capability name covers both metadata
346
+ functions; there is no separate `siteMetadata` capability.
347
+
348
+ The trusted processor writes under:
349
+
350
+ ```txt
351
+ public/plugins/<instanceId ?? name>/<key>
352
+ ```
353
+
354
+ `key` must match the allowlist `[A-Za-z0-9._/-]+`. Anything outside
355
+ that — spaces, URL-reserved characters (`#`, `?`, `&`, `=`, `+`),
356
+ non-ASCII (`日本語.xml`), empty strings, absolute paths (leading `/`),
357
+ `.` / `..` path segments, backslashes, control characters, or keys
358
+ over 256 characters — is rejected before S3 is called. Nested paths
359
+ such as `indexes/posts.json` and dotted extensions such as
360
+ `feed.v2.xml` are allowed. The allowlist is deliberately tight so
361
+ the returned public URL and the underlying S3 key are byte-identical
362
+ strings; URL-reserved characters would survive S3 but reshape the URL
363
+ when a consumer parses it. If you need to include user-supplied
364
+ characters in a key, sanitize first (e.g. hash, slugify) before
365
+ calling `ctx.writePublicAsset()`. The return value is the public URL
366
+ for the written object.
367
+
368
+ During the migration period, plugins with no `capabilities` field
369
+ keep working. Plugins that do declare `capabilities` but omit
370
+ `writePublicAsset` warn once when they actually call
371
+ `ctx.writePublicAsset()`.
372
+
335
373
  ### Best practices
336
374
 
337
375
  - **Be idempotent.** SQS delivery is at-least-once: the same event
@@ -575,6 +613,8 @@ a normal npm package:
575
613
  Worked examples to crib from:
576
614
 
577
615
  - [`packages/plugin-analytics-ga4`](https://github.com/heavymoons/ampless/tree/main/packages/plugin-analytics-ga4) — descriptor-based, Phase 2 settings.
616
+ - [`packages/plugin-gtm`](https://github.com/heavymoons/ampless/tree/main/packages/plugin-gtm) — uses both `publicHead` (loader inline script) and `publicBodyEnd` (`<noscript>` iframe fallback) with the container ID admin-edited.
617
+ - [`packages/plugin-plausible`](https://github.com/heavymoons/ampless/tree/main/packages/plugin-plausible) — single `<script>` descriptor with `data-*` attrs and a required URL field (self-hosted Plausible override).
578
618
  - [`packages/plugin-rss`](https://github.com/heavymoons/ampless/tree/main/packages/plugin-rss) — trusted, async event hooks + `writePublicAsset`.
579
619
  - [`packages/plugin-seo`](https://github.com/heavymoons/ampless/tree/main/packages/plugin-seo) — `metadata()` + `siteMetadata()`.
580
620
  - [`packages/plugin-webhook`](https://github.com/heavymoons/ampless/tree/main/packages/plugin-webhook) — untrusted hook with outbound HTTP.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ampless",
3
- "version": "1.0.0-alpha.18",
3
+ "version": "1.0.0-alpha.20",
4
4
  "description": "Serverless CMS for AWS Amplify",
5
5
  "license": "MIT",
6
6
  "type": "module",