@aeptus/aexeo-emdash 0.8.17 → 0.8.18

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.
Files changed (73) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/INSTALL.md +9 -10
  3. package/LICENSE +0 -0
  4. package/README.md +6 -3
  5. package/dist/adapter.d.ts +32 -1
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/admin/Absorbability.d.ts +0 -0
  8. package/dist/admin/Absorbability.d.ts.map +0 -0
  9. package/dist/admin/Accessibility.d.ts +0 -0
  10. package/dist/admin/Accessibility.d.ts.map +0 -0
  11. package/dist/admin/Citability.d.ts +0 -0
  12. package/dist/admin/Citability.d.ts.map +0 -0
  13. package/dist/admin/EntityLegitimacy.d.ts +0 -0
  14. package/dist/admin/EntityLegitimacy.d.ts.map +0 -0
  15. package/dist/admin/EntityPresence.d.ts +0 -0
  16. package/dist/admin/EntityPresence.d.ts.map +0 -0
  17. package/dist/admin/Facts.d.ts +0 -0
  18. package/dist/admin/Facts.d.ts.map +0 -0
  19. package/dist/admin/Findings.d.ts +0 -0
  20. package/dist/admin/Findings.d.ts.map +0 -0
  21. package/dist/admin/PillarView.d.ts +0 -0
  22. package/dist/admin/PillarView.d.ts.map +0 -0
  23. package/dist/admin/Retrievability.d.ts +0 -0
  24. package/dist/admin/Retrievability.d.ts.map +0 -0
  25. package/dist/admin.d.ts +0 -0
  26. package/dist/admin.d.ts.map +0 -0
  27. package/dist/admin.js +0 -0
  28. package/dist/configured.d.ts +0 -0
  29. package/dist/configured.d.ts.map +1 -1
  30. package/dist/configured.js +217 -59
  31. package/dist/data-route.d.ts +0 -0
  32. package/dist/data-route.d.ts.map +1 -1
  33. package/dist/evaluator-sandbox.d.ts +0 -0
  34. package/dist/evaluator-sandbox.d.ts.map +0 -0
  35. package/dist/evaluator.d.ts +0 -0
  36. package/dist/evaluator.d.ts.map +0 -0
  37. package/dist/facts-route.d.ts +0 -0
  38. package/dist/facts-route.d.ts.map +0 -0
  39. package/dist/index.d.ts +0 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +12 -22
  42. package/dist/indexnow.d.ts +0 -0
  43. package/dist/indexnow.d.ts.map +0 -0
  44. package/dist/mcp.d.ts +0 -0
  45. package/dist/mcp.d.ts.map +0 -0
  46. package/dist/plugin.d.ts +3 -1
  47. package/dist/plugin.d.ts.map +1 -1
  48. package/dist/presence-check.d.ts +0 -0
  49. package/dist/presence-check.d.ts.map +0 -0
  50. package/dist/presence-route.d.ts +0 -0
  51. package/dist/presence-route.d.ts.map +0 -0
  52. package/dist/sandbox-entry.d.ts +0 -0
  53. package/dist/sandbox-entry.d.ts.map +1 -1
  54. package/dist/sandbox-entry.js +323 -115
  55. package/dist/sandbox.d.ts +0 -0
  56. package/dist/sandbox.d.ts.map +1 -1
  57. package/dist/sidecar.d.ts +0 -0
  58. package/dist/sidecar.d.ts.map +1 -1
  59. package/dist/suppressions.d.ts +0 -0
  60. package/dist/suppressions.d.ts.map +0 -0
  61. package/dist/types.d.ts +0 -0
  62. package/dist/types.d.ts.map +0 -0
  63. package/dist/version.d.ts +1 -1
  64. package/dist/version.d.ts.map +0 -0
  65. package/dist/wasm-init.d.ts +0 -0
  66. package/dist/wasm-init.d.ts.map +0 -0
  67. package/package.json +8 -4
  68. package/wasm/aexeo_emdash_bridge.d.ts +0 -0
  69. package/wasm/aexeo_emdash_bridge.js +0 -0
  70. package/wasm/aexeo_emdash_bridge_bg.d.ts +0 -0
  71. package/wasm/aexeo_emdash_bridge_bg.js +0 -0
  72. package/wasm/aexeo_emdash_bridge_bg.wasm +0 -0
  73. package/wasm/aexeo_emdash_bridge_bg.wasm.d.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,37 @@ and the project follows [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.8.18] - 2026-09-27
10
+
11
+ Updates the EmDash integration to the current plugin and content APIs.
12
+
13
+ **Compatibility target:** EmDash `>=0.41.0 <0.42.0`.
14
+
15
+ ### Fixed
16
+
17
+ - **Sandbox access declaration:** replace obsolete capability strings with
18
+ `content:read` and conditional `network:request`, scoped to the configured
19
+ evaluator host. Remove unused schema, artifact, KV, and IndexNow host grants.
20
+ - **Typed `blocks` content:** distinguish EmDash block compositions from
21
+ Portable Text and adapt their editorial strings into the evaluator's text
22
+ format instead of discarding them.
23
+ - **Routes and translations:** use EmDash's `getPublicUrl()` and
24
+ `getTranslations()` APIs for collection patterns, locale paths, trailing
25
+ slash policy, and hreflang alternates. Keep draft and unroutable entries
26
+ under stable collection/ID keys, and link only to URLs the host resolves.
27
+ - **Sandbox save evaluation:** `content:afterSave` now persists and evaluates
28
+ the saved item through the configured sidecar, matching configured mode.
29
+ - **Route migrations:** remove obsolete document and finding entries when a
30
+ document's resolved route changes or a successful refresh no longer finds
31
+ an entry in the selected collections.
32
+
33
+ ### Changed
34
+
35
+ - Bump the EmDash peer and development ranges to `0.41.x`; update the README
36
+ and install guide to name the supported range and sandbox save behavior.
37
+ - Add Vitest coverage using EmDash's official plugin runtime test host for
38
+ sandbox hooks, blocks extraction, resolved URLs, and translation alternates.
39
+
9
40
  ## [0.8.17] - 2026-05-29
10
41
 
11
42
  Hotfix release. Closes the production admin-route regression
package/INSTALL.md CHANGED
@@ -31,7 +31,8 @@ when it's actually needed.
31
31
  (`@astrojs/cloudflare` + `@emdash-cms/cloudflare`'s `sandbox()`
32
32
  runner is fine — sandboxed plugins use Worker Loader, configured
33
33
  plugins don't, but both adapters import cleanly).
34
- - emdash 0.7.0 or later as a peer dependency.
34
+ - EmDash `>=0.41.0 <0.42.0` as a peer dependency. The integration
35
+ coverage targets `0.41.0` with EmDash's official plugin test host.
35
36
 
36
37
  ### Install
37
38
 
@@ -106,9 +107,8 @@ no sidecar to deploy.
106
107
  — toast says "Refreshed N routes (M findings across K documents)"
107
108
  and the table populates.
108
109
 
109
- 4. Save a document: the `content:afterSave` hook auto-evaluates that
110
- one document; refresh the findings page and that route's findings
111
- are updated. (The dashboard widget also picks up the new score.)
110
+ 4. Save a document: `content:afterSave` evaluates that document. Reload
111
+ the findings page to see the updated row and dashboard score.
112
112
 
113
113
  ### Production deploy
114
114
 
@@ -171,8 +171,8 @@ Pre-flight before bumping:
171
171
 
172
172
  1. Look up the plugin version's tested emdash range in our
173
173
  [CHANGELOG](./CHANGELOG.md). The latest plugin version's
174
- `Compatibility` line says e.g. "tested against emdash 0.7.0
175
- through 0.8.0." If the emdash version you want is **inside that
174
+ `Compatibility` line names the supported EmDash range and test-host
175
+ target. If the EmDash version you want is **inside that
176
176
  range**, the bump is safe. If it's **above the range**, treat it
177
177
  as untested.
178
178
  2. If untested, scan the emdash release notes for breaking changes
@@ -181,7 +181,7 @@ Pre-flight before bumping:
181
181
  - Hook signatures (`content:afterSave`, etc.)
182
182
  - Block Kit element schemas (banner variants, button styles,
183
183
  table column formats)
184
- - Capability validation strings (`read:content`, `network:fetch`)
184
+ - Capability names (`content:read`, `network:request`)
185
185
  - The `definePlugin` API
186
186
  - The `createPlugin` / configured-plugin descriptor contract
187
187
  If any of those changed, expect to bump the plugin too.
@@ -240,9 +240,8 @@ Trade-offs vs. configured mode:
240
240
  prevents the plugin from ever reading host state directly.
241
241
  - **Con**: separate sidecar Worker to deploy and maintain; an
242
242
  EVAL_TOKEN to rotate; a Setup admin page to configure. The
243
- `content:afterSave` hook is also non-functional in emdash 0.7.x's
244
- sandbox (post-response bridge invalidation — known upstream issue);
245
- evaluation is manual-Refresh-only.
243
+ `content:afterSave` hook evaluates through the sidecar after you save
244
+ its URL and token on the Setup page. Refresh still runs a full-site sweep.
246
245
 
247
246
  ```js
248
247
  // astro.config.mjs (sandboxed mode)
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -47,12 +47,15 @@ troubleshooting list, see [INSTALL.md](./INSTALL.md).
47
47
  answer-pack, external-trust dimensions). Banner appears below 60.
48
48
  - **Refresh button**: re-evaluate every document in the configured
49
49
  collections (`posts`, `pages` by default).
50
- - **Auto-evaluate on save**: emdash's `content:afterSave` hook fires,
51
- re-evaluates the saved document, updates the findings table.
50
+ - **Auto-evaluate on save**: EmDash's `content:afterSave` hook evaluates
51
+ the saved document and updates its findings. Sandboxed installs need a
52
+ configured evaluator sidecar; configured installs run evaluation in-process.
52
53
 
53
54
  ## Compatibility
54
55
 
55
- - emdash `>= 0.7.0` (peer dep)
56
+ - EmDash `>= 0.41.0 < 0.42.0` (peer dependency; current supported range)
57
+ - Runtime integration coverage targets EmDash `0.41.0` via the official
58
+ `@emdash-cms/plugin-test` host.
56
59
  - Cloudflare Workers via `@astrojs/cloudflare`. The plugin uses
57
60
  Cloudflare-specific APIs and silently no-ops on the Node adapter.
58
61
  - WASM eval ships with the package; the consumer's Vite/Wrangler
package/dist/adapter.d.ts CHANGED
@@ -5,6 +5,7 @@ export interface EmdashContentItem {
5
5
  slug: string | null;
6
6
  status: string;
7
7
  locale: string | null;
8
+ translationGroup?: string | null;
8
9
  data: Record<string, unknown>;
9
10
  seo?: {
10
11
  title?: string | null;
@@ -18,11 +19,41 @@ export interface EmdashContentMeta {
18
19
  slug: string | null;
19
20
  status: string;
20
21
  title: string;
22
+ /** Exact URL returned by EmDash, or null when this entry has no public route. */
23
+ publicUrl: string | null;
24
+ }
25
+ export interface ContentUrlApi {
26
+ getPublicUrl(collection: string, id: string): Promise<string | null>;
27
+ getTranslations(collection: string, id: string): Promise<{
28
+ translationGroup: string;
29
+ translations: Array<{
30
+ id: string;
31
+ locale: string | null;
32
+ slug: string | null;
33
+ status: string;
34
+ updatedAt: string;
35
+ }>;
36
+ }>;
21
37
  }
22
38
  export interface AdaptedContent {
23
39
  document: EmdashDocument;
24
40
  meta: EmdashContentMeta;
25
41
  }
42
+ /**
43
+ * Adapt a stored content row using EmDash's route resolver. The host API
44
+ * accounts for collection URL patterns, locale prefixes, and slash policy;
45
+ * slug-derived paths are not equivalent to the site's actual public URLs.
46
+ */
47
+ export declare function adaptContentItemWithContext(content: EmdashContentItem, contentApi: ContentUrlApi, defaultLocale?: string): Promise<AdaptedContent>;
48
+ /**
49
+ * Synchronous adapter for callers that do not have a host content API.
50
+ * It deliberately uses a stable identity route; callers should use
51
+ * adaptContentItemWithContext whenever EmDash can resolve the public URL.
52
+ */
26
53
  export declare function contentItemToEmdashDocument(content: EmdashContentItem): EmdashDocument;
27
- export declare function adaptContentItem(content: EmdashContentItem): AdaptedContent;
54
+ export declare function adaptContentItem(content: EmdashContentItem, options?: {
55
+ route?: string;
56
+ publicUrl?: string | null;
57
+ defaultLocale?: string;
58
+ }): AdaptedContent;
28
59
  //# sourceMappingURL=adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAC;AAmBpE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,GAAG,CAAC,EAAE;QACJ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC;CACH;AAOD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IAIf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,cAAc,CAAC;IACzB,IAAI,EAAE,iBAAiB,CAAC;CACzB;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,iBAAiB,GACzB,cAAc,CAEhB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAkC3E"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAC;AAEpE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,GAAG,CAAC,EAAE;QACJ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC;CACH;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACrE,eAAe,CACb,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC;QACT,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,KAAK,CAAC;YAClB,EAAE,EAAE,MAAM,CAAC;YACX,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;YACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;YACpB,MAAM,EAAE,MAAM,CAAC;YACf,SAAS,EAAE,MAAM,CAAC;SACnB,CAAC,CAAC;KACJ,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,cAAc,CAAC;IACzB,IAAI,EAAE,iBAAiB,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,iBAAiB,EAC1B,UAAU,EAAE,aAAa,EACzB,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,cAAc,CAAC,CA2CzB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,iBAAiB,GACzB,cAAc,CAEhB;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,iBAAiB,EAC1B,OAAO,GAAE;IACP,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACnB,GACL,cAAc,CAgChB"}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/dist/admin.d.ts CHANGED
File without changes
File without changes
package/dist/admin.js CHANGED
File without changes
File without changes
@@ -1 +1 @@
1
- {"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../src/configured.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA0HrD,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEhC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CACvC;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CA4H3E;AAID,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../src/configured.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA0HrD,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEhC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CACvC;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CA0H3E;AAID,eAAe,YAAY,CAAC"}
@@ -5,15 +5,42 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/adapter.ts
8
- function adaptContentItem(content) {
9
- const route = deriveRoute(content);
8
+ async function adaptContentItemWithContext(content, contentApi, defaultLocale) {
9
+ const publicUrl = content.status === "published" ? await contentApi.getPublicUrl(content.type, content.id) : null;
10
+ const route = publicUrl === null ? deriveRoute(content) : routeFromPublicUrl(publicUrl);
11
+ const adapted = adaptContentItem(content, {
12
+ route,
13
+ publicUrl,
14
+ ...defaultLocale === void 0 ? {} : { defaultLocale }
15
+ });
16
+ const translationSet = await contentApi.getTranslations(
17
+ content.type,
18
+ content.id
19
+ );
20
+ const alternates = await Promise.all(
21
+ translationSet.translations.filter(
22
+ (translation) => translation.status === "published" && typeof translation.locale === "string" && translation.locale.length > 0
23
+ ).map(async (translation) => {
24
+ const href = translation.id === content.id ? publicUrl : await contentApi.getPublicUrl(content.type, translation.id);
25
+ if (href === null) return null;
26
+ return { lang: translation.locale, href };
27
+ })
28
+ );
29
+ const uniqueAlternates = /* @__PURE__ */ new Map();
30
+ for (const alternate of alternates) {
31
+ if (alternate !== null) uniqueAlternates.set(alternate.lang, alternate);
32
+ }
33
+ if (uniqueAlternates.size > 0) {
34
+ adapted.document.alternates = [...uniqueAlternates.values()];
35
+ }
36
+ return adapted;
37
+ }
38
+ function adaptContentItem(content, options = {}) {
39
+ const route = options.route ?? deriveRoute(content);
10
40
  const title = stringOrEmpty(
11
41
  content.seo?.title ?? content.data["title"] ?? content.slug ?? content.id
12
42
  );
13
- const document = {
14
- route,
15
- title
16
- };
43
+ const document = { route, title };
17
44
  const description = content.seo?.description ?? content.data["description"];
18
45
  if (typeof description === "string" && description.length > 0) {
19
46
  document.description = description;
@@ -22,13 +49,12 @@ function adaptContentItem(content) {
22
49
  if (typeof canonical === "string" && canonical.length > 0) {
23
50
  document.canonical = canonical;
24
51
  }
25
- if (content.locale !== null) {
26
- document.lang = content.locale;
52
+ const locale = content.locale || options.defaultLocale;
53
+ if (typeof locale === "string" && locale.length > 0) {
54
+ document.lang = locale;
27
55
  }
28
56
  const body = extractPortableText(content.data);
29
- if (body !== null) {
30
- document.body = body;
31
- }
57
+ if (body !== null) document.body = body;
32
58
  return {
33
59
  document,
34
60
  meta: {
@@ -36,49 +62,152 @@ function adaptContentItem(content) {
36
62
  collection: content.type,
37
63
  slug: content.slug,
38
64
  status: content.status,
39
- title
65
+ title,
66
+ publicUrl: options.publicUrl ?? null
40
67
  }
41
68
  };
42
69
  }
43
70
  function deriveRoute(content) {
44
- if (typeof content.slug === "string" && content.slug.length > 0) {
45
- return content.slug.startsWith("/") ? content.slug : `/${content.slug}`;
71
+ return `/${encodeURIComponent(content.type)}/${encodeURIComponent(content.id)}`;
72
+ }
73
+ function routeFromPublicUrl(publicUrl) {
74
+ try {
75
+ return new URL(publicUrl).pathname || "/";
76
+ } catch (cause) {
77
+ throw new Error(`EmDash returned an invalid public URL: ${publicUrl}`, {
78
+ cause
79
+ });
46
80
  }
47
- return `/${content.type}/${content.id}`;
48
81
  }
49
82
  function stringOrEmpty(value) {
50
83
  return typeof value === "string" ? value : "";
51
84
  }
52
85
  function extractPortableText(data) {
53
- const candidates = ["body", "content"];
54
- for (const slug of candidates) {
86
+ for (const slug of ["body", "content"]) {
55
87
  const value = data[slug];
56
- if (isPortableTextArray(value)) {
57
- return value;
88
+ if (isPortableTextArray(value)) return value;
89
+ if (isEmdashBlocksArray(value, slug)) return blocksToPortableText(value);
90
+ if (typeof value === "string" && value.trim().length > 0) {
91
+ return [portableTextBlock(value.trim(), `${slug}-text`)];
58
92
  }
59
93
  }
60
- for (const value of Object.values(data)) {
61
- if (isPortableTextArray(value)) {
62
- return value;
63
- }
94
+ const blocks = data["blocks"];
95
+ if (isPortableTextArray(blocks)) return blocks;
96
+ if (isEmdashBlocksArray(blocks, "blocks")) return blocksToPortableText(blocks);
97
+ for (const [field, value] of Object.entries(data)) {
98
+ if (field === "body" || field === "content" || field === "blocks") continue;
99
+ if (isPortableTextArray(value)) return value;
100
+ if (isEmdashBlocksArray(value, field)) return blocksToPortableText(value);
64
101
  }
65
102
  return null;
66
103
  }
67
104
  function isPortableTextArray(value) {
68
- if (!Array.isArray(value) || value.length === 0) {
69
- return false;
105
+ return Array.isArray(value) && value.length > 0 && value.every(
106
+ (item) => isRecord(item) && item["_type"] === "block" && Array.isArray(item["children"]) && item["children"].every(
107
+ (child) => isRecord(child) && typeof child["text"] === "string"
108
+ )
109
+ );
110
+ }
111
+ function isEmdashBlocksArray(value, field) {
112
+ if (!Array.isArray(value) || value.length === 0) return false;
113
+ return value.every(
114
+ (item) => isRecord(item) && typeof item["_type"] === "string" && typeof item["_key"] === "string" && (typeof item["_version"] === "number" || field === "blocks")
115
+ );
116
+ }
117
+ function blocksToPortableText(blocks) {
118
+ const paragraphs = [];
119
+ for (const block of blocks) {
120
+ const segments = [];
121
+ collectText(block, "", segments);
122
+ const text = segments.map((segment) => segment.trim()).filter(Boolean).join(" ");
123
+ if (text.length > 0) {
124
+ paragraphs.push(portableTextBlock(text, stringOrEmpty(block["_key"])));
125
+ }
126
+ }
127
+ return paragraphs;
128
+ }
129
+ function collectText(value, key, out) {
130
+ if (typeof value === "string") {
131
+ if (!isStructuralField(key) && value.trim().length > 0) out.push(value);
132
+ return;
133
+ }
134
+ if (Array.isArray(value)) {
135
+ for (const item of value) collectText(item, key, out);
136
+ return;
70
137
  }
71
- const first = value[0];
72
- return typeof first === "object" && first !== null && "_type" in first && typeof first._type === "string";
138
+ if (!isRecord(value)) return;
139
+ for (const [childKey, childValue] of Object.entries(value)) {
140
+ if (isStructuralField(childKey)) continue;
141
+ collectText(childValue, childKey, out);
142
+ }
143
+ }
144
+ var STRUCTURAL_FIELDS = /* @__PURE__ */ new Set([
145
+ "_key",
146
+ "_ref",
147
+ "_type",
148
+ "_version",
149
+ "asset",
150
+ "align",
151
+ "alignment",
152
+ "autoplay",
153
+ "color",
154
+ "controls",
155
+ "focalpoint",
156
+ "height",
157
+ "href",
158
+ "id",
159
+ "layout",
160
+ "level",
161
+ "loop",
162
+ "markdefs",
163
+ "marks",
164
+ "mime_type",
165
+ "mimetype",
166
+ "position",
167
+ "rel",
168
+ "size",
169
+ "src",
170
+ "style",
171
+ "target",
172
+ "theme",
173
+ "tone",
174
+ "url",
175
+ "variant",
176
+ "width"
177
+ ]);
178
+ function isStructuralField(key) {
179
+ const normalized = key.toLowerCase();
180
+ return normalized.startsWith("_") || STRUCTURAL_FIELDS.has(normalized);
181
+ }
182
+ function portableTextBlock(text, key) {
183
+ return {
184
+ _type: "block",
185
+ ...key.length === 0 ? {} : { _key: key },
186
+ children: [
187
+ {
188
+ _type: "span",
189
+ ...key.length === 0 ? {} : { _key: `${key}-text` },
190
+ text,
191
+ marks: []
192
+ }
193
+ ]
194
+ };
195
+ }
196
+ function isRecord(value) {
197
+ return typeof value === "object" && value !== null && !Array.isArray(value);
73
198
  }
74
199
 
75
200
  // src/plugin.ts
76
201
  async function handleAfterSaveConfigured(event, ctx, evaluator, suppressionFilter) {
77
- const adapted = adaptContentItem(event.content);
202
+ const adapted = await adaptContentItemWithContext(
203
+ event.content,
204
+ ctx.content,
205
+ ctx.site?.locale
206
+ );
78
207
  const document = adapted.document;
79
208
  const { kv, log } = ctx;
80
209
  const stored = { document, meta: adapted.meta };
81
- await kv.set(documentKey(document.route), stored);
210
+ await persistDocument(ctx, stored);
82
211
  const result = await evaluator([document]);
83
212
  if (!result.ok) {
84
213
  log?.error?.(`afterSave evaluator failure (${result.reason})`, {
@@ -125,7 +254,11 @@ async function evaluateAndPersistAll(ctx, options) {
125
254
  ...cursor === void 0 ? {} : { cursor }
126
255
  });
127
256
  for (const item of page.items) {
128
- const adapted = adaptContentItem(item);
257
+ const adapted = await adaptContentItemWithContext(
258
+ item,
259
+ ctx.content,
260
+ ctx.site?.locale
261
+ );
129
262
  documents.push(adapted.document);
130
263
  documentRoutes.add(adapted.document.route);
131
264
  adaptedByRoute.set(adapted.document.route, {
@@ -177,8 +310,57 @@ async function evaluateAndPersistAll(ctx, options) {
177
310
  totalAfterSuppression += filtered.length;
178
311
  }
179
312
  summary.totalFindings = totalAfterSuppression;
313
+ if (summary.errors.length === 0) {
314
+ await removeStaleDocuments(ctx, collections, documentRoutes);
315
+ }
180
316
  return summary;
181
317
  }
318
+ async function persistDocument(ctx, stored) {
319
+ const route = stored.document.route;
320
+ const entries = await ctx.kv.list("document:");
321
+ for (const entry of entries) {
322
+ const previous = toStoredDocument(entry.value);
323
+ if (previous === null || previous.document.route === route || previous.meta.id !== stored.meta.id || previous.meta.collection !== stored.meta.collection) {
324
+ continue;
325
+ }
326
+ await ctx.kv.delete(entry.key);
327
+ await ctx.kv.delete(findingsKey(previous.document.route));
328
+ }
329
+ await ctx.kv.set(documentKey(route), stored);
330
+ }
331
+ async function removeStaleDocuments(ctx, collections, currentRoutes) {
332
+ const scannedCollections = new Set(collections);
333
+ const entries = await ctx.kv.list("document:");
334
+ for (const entry of entries) {
335
+ const stored = toStoredDocument(entry.value);
336
+ if (stored === null || !scannedCollections.has(stored.meta.collection) || currentRoutes.has(stored.document.route)) {
337
+ continue;
338
+ }
339
+ await ctx.kv.delete(entry.key);
340
+ await ctx.kv.delete(findingsKey(stored.document.route));
341
+ }
342
+ }
343
+ function toStoredDocument(value) {
344
+ if (value === null || typeof value !== "object") return null;
345
+ if ("document" in value && "meta" in value && value.document !== null && typeof value.document === "object" && "route" in value.document && typeof value.document.route === "string" && value.meta !== null && typeof value.meta === "object") {
346
+ return value;
347
+ }
348
+ if ("route" in value && typeof value.route === "string") {
349
+ const document = value;
350
+ return {
351
+ document,
352
+ meta: {
353
+ id: document.route,
354
+ collection: "",
355
+ slug: null,
356
+ status: "",
357
+ title: document.title ?? "",
358
+ publicUrl: null
359
+ }
360
+ };
361
+ }
362
+ return null;
363
+ }
182
364
  function findingsKey(route) {
183
365
  const normalized = route === "" || route === "/" ? "/" : route;
184
366
  return `findings:${normalized}`;
@@ -199,25 +381,8 @@ async function readAllStoredDocuments(kv) {
199
381
  const entries = await kv.list("document:");
200
382
  const out = [];
201
383
  for (const entry of entries) {
202
- const value = entry.value;
203
- if (value === null || typeof value !== "object") continue;
204
- if ("document" in value && "meta" in value) {
205
- out.push(value);
206
- continue;
207
- }
208
- if ("route" in value) {
209
- const document = value;
210
- out.push({
211
- document,
212
- meta: {
213
- id: document.route,
214
- collection: "",
215
- slug: null,
216
- status: "",
217
- title: document.title ?? ""
218
- }
219
- });
220
- }
384
+ const stored = toStoredDocument(entry.value);
385
+ if (stored !== null) out.push(stored);
221
386
  }
222
387
  return out;
223
388
  }
@@ -559,7 +724,6 @@ async function buildFindingsPayload(ctx) {
559
724
  }
560
725
  }
561
726
  findings.sort((a, b) => severityRank(a.severity) - severityRank(b.severity));
562
- const siteUrl = ctx.site?.url ?? "";
563
727
  const routes = [];
564
728
  for (const [route, bucket] of perRoute) {
565
729
  const stored2 = documents.get(route);
@@ -570,7 +734,7 @@ async function buildFindingsPayload(ctx) {
570
734
  collection: stored2?.meta.collection ?? "",
571
735
  id: stored2?.meta.id ?? "",
572
736
  editUrl: stored2 ? buildEditUrl(stored2.meta) : "",
573
- publishedUrl: stored2 ? buildPublishedUrl(stored2.meta, route, siteUrl) : "",
737
+ publishedUrl: stored2?.meta.publicUrl ?? "",
574
738
  findingCount: bucket.findings.length,
575
739
  errorCount: bucket.errors,
576
740
  warningCount: bucket.warnings
@@ -696,12 +860,6 @@ function buildEditUrl(meta) {
696
860
  if (meta.collection === "" || meta.id === "") return "";
697
861
  return `/_emdash/admin/content/${encodeURIComponent(meta.collection)}/${encodeURIComponent(meta.id)}`;
698
862
  }
699
- function buildPublishedUrl(meta, route, siteUrl) {
700
- if (meta.status !== "published") return "";
701
- if (siteUrl === "") return "";
702
- const trimmed = siteUrl.endsWith("/") ? siteUrl.slice(0, -1) : siteUrl;
703
- return `${trimmed}${route}`;
704
- }
705
863
 
706
864
  // src/facts-route.ts
707
865
  import { PluginRouteError } from "emdash";
@@ -1215,7 +1373,7 @@ function globToMatcher(pattern) {
1215
1373
  }
1216
1374
 
1217
1375
  // src/version.ts
1218
- var PACKAGE_VERSION = "0.8.17";
1376
+ var PACKAGE_VERSION = "0.8.18";
1219
1377
 
1220
1378
  // src/configured.ts
1221
1379
  import { definePlugin } from "emdash";
@@ -1284,7 +1442,7 @@ function createPlugin(options = {}) {
1284
1442
  return definePlugin({
1285
1443
  id: "aexeo-emdash",
1286
1444
  version: PACKAGE_VERSION,
1287
- capabilities: ["read:content"],
1445
+ capabilities: ["content:read"],
1288
1446
  hooks: {
1289
1447
  // afterSave processes one saved document at a time — collections
1290
1448
  // list isn't needed here. The hook always runs regardless of
File without changes
@@ -1 +1 @@
1
- {"version":3,"file":"data-route.d.ts","sourceRoot":"","sources":["../src/data-route.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACd,UAAU,EAEX,MAAM,aAAa,CAAC;AAQrB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAM1C,MAAM,WAAW,eAAe;IAG9B,MAAM,EAAE,YAAY,EAAE,CAAC;IAIvB,QAAQ,EAAE,UAAU,EAAE,CAAC;IAIvB,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;IAIF,cAAc,EAAE,cAAc,EAAE,CAAC;IAIjC,gBAAgB,EAAE,OAAO,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,OAAO,YAAY,EAAE,KAAK,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IAGd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IAIX,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAM1B,MAAM,CAAC,EAAE,OAAO,YAAY,EAAE,UAAU,CAAC;CAC1C;AAED,UAAU,gBAAgB;IACxB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,SAAS,EAAE,WAAW,CAAC;IAIvB,OAAO,EAAE,OAAO,CAAC;IAIjB,iBAAiB,CAAC,EAAE,OAAO,mBAAmB,EAAE,iBAAiB,CAAC;CACnE;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC;IACT,OAAO,EAAE,eAAe,CAAC;IACzB,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;CAChC,CAAC,CAmBD;AA6ID,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,IAAI,CAAC,CAGf"}
1
+ {"version":3,"file":"data-route.d.ts","sourceRoot":"","sources":["../src/data-route.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACd,UAAU,EAEX,MAAM,aAAa,CAAC;AAQrB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAM1C,MAAM,WAAW,eAAe;IAG9B,MAAM,EAAE,YAAY,EAAE,CAAC;IAIvB,QAAQ,EAAE,UAAU,EAAE,CAAC;IAIvB,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;IAIF,cAAc,EAAE,cAAc,EAAE,CAAC;IAIjC,gBAAgB,EAAE,OAAO,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,OAAO,YAAY,EAAE,KAAK,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IAGd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IAIX,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAM1B,MAAM,CAAC,EAAE,OAAO,YAAY,EAAE,UAAU,CAAC;CAC1C;AAED,UAAU,gBAAgB;IACxB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,SAAS,EAAE,WAAW,CAAC;IAIvB,OAAO,EAAE,OAAO,CAAC;IAIjB,iBAAiB,CAAC,EAAE,OAAO,mBAAmB,EAAE,iBAAiB,CAAC;CACnE;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC;IACT,OAAO,EAAE,eAAe,CAAC;IACzB,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;CAChC,CAAC,CAmBD;AA4ID,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,IAAI,CAAC,CAGf"}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/dist/index.d.ts CHANGED
File without changes
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AAYtB,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IAOnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,UAAU,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,YAAY,EAAE,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACxE;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEhC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,YAAY,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CACvC;AAED,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,wBAAgB,WAAW,CACzB,OAAO,GAAE,kBAAuB,GAC/B,0BAA0B,CAqE5B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AAYtB,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IAOnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,UAAU,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,YAAY,EAAE,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACxE;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEhC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,YAAY,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CACvC;AAED,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,wBAAgB,WAAW,CACzB,OAAO,GAAE,kBAAuB,GAC/B,0BAA0B,CAiE5B"}