@ampless/runtime 1.0.0-alpha.26 → 1.0.0-alpha.28
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 +81 -2
- package/dist/index.js +187 -16
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Post, MediaMetadata, Config, ThemeModule, ThemeManifest } from 'ampless';
|
|
1
|
+
import { Post, MediaMetadata, Config, ThemeModule, ThemeManifest, PluginPackageManifest } from 'ampless';
|
|
2
2
|
export { Config, Post, ThemeManifest } from 'ampless';
|
|
3
3
|
import { Metadata } from 'next';
|
|
4
4
|
import { ReactNode } from 'react';
|
|
@@ -153,7 +153,37 @@ interface PluginHeadApi {
|
|
|
153
153
|
renderHead(): Promise<ReactNode>;
|
|
154
154
|
/** React children safe to drop just before `</body>`. */
|
|
155
155
|
renderBodyEnd(): Promise<ReactNode>;
|
|
156
|
+
/**
|
|
157
|
+
* Per-post body descriptors (Phase 4). Themes call this from their
|
|
158
|
+
* post template to render plugin-supplied `<script
|
|
159
|
+
* type="application/ld+json">` descriptors keyed off the specific
|
|
160
|
+
* post being viewed. Limited to `inlineScript` with
|
|
161
|
+
* `scriptType: 'application/ld+json'` so the per-post surface stays
|
|
162
|
+
* scoped to structured data — see `PublicPostBodyDescriptor`.
|
|
163
|
+
*/
|
|
164
|
+
renderBodyForPost(post: Post): Promise<ReactNode>;
|
|
156
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Escape characters that would let a value break out of an inline
|
|
168
|
+
* `<script type="application/ld+json">` body. Applied automatically by
|
|
169
|
+
* the runtime to ANY inlineScript descriptor whose `scriptType` is
|
|
170
|
+
* `'application/ld+json'`, regardless of which surface
|
|
171
|
+
* (`publicHead` / `publicBodyEnd` / `publicBodyForPost`) emitted it.
|
|
172
|
+
* Plugin authors do not need to call this themselves; it's exported so
|
|
173
|
+
* other hand-rolled inline-JSON-LD code paths can reuse it.
|
|
174
|
+
*
|
|
175
|
+
* Each character is replaced with its `\uXXXX` form — a JSON-legal way
|
|
176
|
+
* to encode the same character inside a JSON string, so the JSON
|
|
177
|
+
* payload still parses but the HTML parser can no longer see a closing
|
|
178
|
+
* `</script>` sequence:
|
|
179
|
+
*
|
|
180
|
+
* '<' → '\u003c'
|
|
181
|
+
* '>' → '\u003e'
|
|
182
|
+
* '&' → '\u0026'
|
|
183
|
+
* U+2028 → '\u2028'
|
|
184
|
+
* U+2029 → '\u2029'
|
|
185
|
+
*/
|
|
186
|
+
declare function escapeJsonLdInlineBody(value: string): string;
|
|
157
187
|
/**
|
|
158
188
|
* Create the head/body renderer for a `Config`. The constructor-time
|
|
159
189
|
* pass logs a single dev warning when two plugins share an
|
|
@@ -239,6 +269,45 @@ interface ThemeConfigApi {
|
|
|
239
269
|
*/
|
|
240
270
|
declare function renderThemeCss(cssVars: Record<string, string>): string;
|
|
241
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Resolve `<packageName>/package.json`, read it, and return the
|
|
274
|
+
* `amplessPlugin` field as a typed `PluginPackageManifest`. Returns
|
|
275
|
+
* `null` for any failure — the caller skips cross-check rather than
|
|
276
|
+
* aborting plugin loading.
|
|
277
|
+
*
|
|
278
|
+
* Failure modes that resolve to `null`:
|
|
279
|
+
* - Package not installed at this resolution root
|
|
280
|
+
* (`ERR_MODULE_NOT_FOUND`)
|
|
281
|
+
* - `package.json` not in the package's `exports`
|
|
282
|
+
* (`ERR_PACKAGE_PATH_NOT_EXPORTED`) — see the spec under
|
|
283
|
+
* `docs/tmp/plugin-extension-phase5.md` §B
|
|
284
|
+
* - `readFileSync` throws (e.g. ENOENT, permissions)
|
|
285
|
+
* - JSON parse error
|
|
286
|
+
* - `amplessPlugin` field absent or not an object
|
|
287
|
+
* - `amplessPlugin` field present but structurally invalid (e.g.
|
|
288
|
+
* `apiVersion` not a number, `capabilities` not an array of
|
|
289
|
+
* strings, `trustLevel` not one of the three allowed values) —
|
|
290
|
+
* see `isValidManifest`
|
|
291
|
+
*
|
|
292
|
+
* The structural check matters: without it, a downstream consumer
|
|
293
|
+
* like `crossCheckStaticManifest`'s `for ... of` over the manifest's
|
|
294
|
+
* `capabilities` would crash on `capabilities: {}` or `capabilities: 42`,
|
|
295
|
+
* which is at odds with the "non-apiVersion mismatches warn rather
|
|
296
|
+
* than throw" policy.
|
|
297
|
+
*
|
|
298
|
+
* After this function returns a non-null value, the caller can trust
|
|
299
|
+
* every field it inspects has the declared type; mismatches against
|
|
300
|
+
* the factory return value are still surfaced as warnings (or, for
|
|
301
|
+
* `apiVersion`, throws) by the caller itself.
|
|
302
|
+
*/
|
|
303
|
+
declare function loadPackageManifest(packageName: string): PluginPackageManifest | null;
|
|
304
|
+
/**
|
|
305
|
+
* Maximum `apiVersion` value this runtime can host. Plugins declaring
|
|
306
|
+
* a higher value are rejected at constructor time — the runtime can't
|
|
307
|
+
* safely call into surfaces it doesn't know about yet.
|
|
308
|
+
*/
|
|
309
|
+
declare const SUPPORTED_API_VERSION: 1;
|
|
310
|
+
|
|
242
311
|
declare function renderBody(post: Post): string;
|
|
243
312
|
/**
|
|
244
313
|
* Convert a tiptap doc to its HTML form. Same renderer the public
|
|
@@ -384,6 +453,16 @@ interface Ampless {
|
|
|
384
453
|
siteMetadata(): Promise<Metadata>;
|
|
385
454
|
publicHead(): Promise<ReactNode>;
|
|
386
455
|
publicBodyEnd(): Promise<ReactNode>;
|
|
456
|
+
/**
|
|
457
|
+
* Per-post body descriptors (Phase 4 `schema` capability). Theme
|
|
458
|
+
* post templates render the result so plugins like
|
|
459
|
+
* `@ampless/plugin-schema-jsonld` can emit
|
|
460
|
+
* `<script type="application/ld+json">` Article schema keyed off
|
|
461
|
+
* the specific post being viewed. Limited to inline-script
|
|
462
|
+
* descriptors with `scriptType: 'application/ld+json'` — the
|
|
463
|
+
* runtime auto-escapes `<`, `>`, `&`, U+2028, U+2029 in the body.
|
|
464
|
+
*/
|
|
465
|
+
publicBodyForPost(post: Post): Promise<ReactNode>;
|
|
387
466
|
renderBody(post: Post): string;
|
|
388
467
|
renderThemeCss(cssVars: Record<string, string>): string;
|
|
389
468
|
publicAssetUrl(key: string): string;
|
|
@@ -410,4 +489,4 @@ interface Ampless {
|
|
|
410
489
|
*/
|
|
411
490
|
declare function createAmpless(opts: CreateAmplessOpts): Ampless;
|
|
412
491
|
|
|
413
|
-
export { type Ampless, type AmplessOutputs, COLOR_SCHEME_SETTING_KEY, type ColorScheme, type CreateAmplessOpts, DEFAULT_COLOR_SCHEME, type DataOutput, type EffectiveSiteSettings, type EffectiveThemeConfig, type ListPostsByTagOptions, type ListPostsOptions, type ListPostsResult, type MediaApi, type PluginHeadApi, type PluginSettingsApi, type PluginSettingsSnapshot, type PostsApi, type PublicMediaShape, type PublicPostConnectionShape, type PublicPostShape, type ResolvedAssetMeta, type ResolvedMedia, type ResolvedTheme, type SeoApi, type SiteSettingsApi, type StorageOutput, type StreamS3Options, type ThemeActiveApi, type ThemeConfigApi, type ThemesRegistry, _resetStreamS3Cache, createAmpless, createPluginHead, createPluginSettings, htmlToMarkdown, markdownToHtml, renderBody, renderThemeCss, streamS3Object, streamS3ObjectWithRunner, tiptapToHtml, tiptapToMarkdown, validateColorScheme };
|
|
492
|
+
export { type Ampless, type AmplessOutputs, COLOR_SCHEME_SETTING_KEY, type ColorScheme, type CreateAmplessOpts, DEFAULT_COLOR_SCHEME, type DataOutput, type EffectiveSiteSettings, type EffectiveThemeConfig, type ListPostsByTagOptions, type ListPostsOptions, type ListPostsResult, type MediaApi, type PluginHeadApi, type PluginSettingsApi, type PluginSettingsSnapshot, type PostsApi, type PublicMediaShape, type PublicPostConnectionShape, type PublicPostShape, type ResolvedAssetMeta, type ResolvedMedia, type ResolvedTheme, SUPPORTED_API_VERSION, type SeoApi, type SiteSettingsApi, type StorageOutput, type StreamS3Options, type ThemeActiveApi, type ThemeConfigApi, type ThemesRegistry, _resetStreamS3Cache, createAmpless, createPluginHead, createPluginSettings, escapeJsonLdInlineBody, htmlToMarkdown, loadPackageManifest, markdownToHtml, renderBody, renderThemeCss, streamS3Object, streamS3ObjectWithRunner, tiptapToHtml, tiptapToMarkdown, validateColorScheme };
|
package/dist/index.js
CHANGED
|
@@ -234,6 +234,53 @@ import {
|
|
|
234
234
|
isValidPluginKey,
|
|
235
235
|
resolvePluginSettings
|
|
236
236
|
} from "ampless";
|
|
237
|
+
|
|
238
|
+
// src/plugin-package-manifest.ts
|
|
239
|
+
import { readFileSync } from "fs";
|
|
240
|
+
import { fileURLToPath } from "url";
|
|
241
|
+
var TRUST_LEVELS = ["untrusted", "trusted", "privileged"];
|
|
242
|
+
function isValidManifest(value) {
|
|
243
|
+
if (typeof value !== "object" || value === null) return false;
|
|
244
|
+
const v = value;
|
|
245
|
+
if (typeof v.apiVersion !== "number") return false;
|
|
246
|
+
if (typeof v.name !== "string") return false;
|
|
247
|
+
if (typeof v.trustLevel !== "string") return false;
|
|
248
|
+
if (!TRUST_LEVELS.includes(v.trustLevel)) return false;
|
|
249
|
+
if (!Array.isArray(v.capabilities)) return false;
|
|
250
|
+
for (const c of v.capabilities) {
|
|
251
|
+
if (typeof c !== "string") return false;
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
function loadPackageManifest(packageName) {
|
|
256
|
+
let resolvedUrl;
|
|
257
|
+
try {
|
|
258
|
+
resolvedUrl = import.meta.resolve(`${packageName}/package.json`);
|
|
259
|
+
} catch {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
let raw;
|
|
263
|
+
try {
|
|
264
|
+
raw = readFileSync(fileURLToPath(resolvedUrl), "utf8");
|
|
265
|
+
} catch {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
let pkg;
|
|
269
|
+
try {
|
|
270
|
+
pkg = JSON.parse(raw);
|
|
271
|
+
} catch {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
if (typeof pkg !== "object" || pkg === null || !("amplessPlugin" in pkg)) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
const manifest = pkg.amplessPlugin;
|
|
278
|
+
if (!isValidManifest(manifest)) return null;
|
|
279
|
+
return manifest;
|
|
280
|
+
}
|
|
281
|
+
var SUPPORTED_API_VERSION = 1;
|
|
282
|
+
|
|
283
|
+
// src/plugin-head.ts
|
|
237
284
|
function isPlugin2(p) {
|
|
238
285
|
return typeof p === "object" && p !== null && "apiVersion" in p;
|
|
239
286
|
}
|
|
@@ -277,6 +324,50 @@ function warn(message) {
|
|
|
277
324
|
if (!isDev()) return;
|
|
278
325
|
console.warn(`[ampless plugin-head] ${message}`);
|
|
279
326
|
}
|
|
327
|
+
function crossCheckStaticManifest(plugin, label) {
|
|
328
|
+
const packageName = plugin.packageName;
|
|
329
|
+
const manifest = loadPackageManifest(packageName);
|
|
330
|
+
if (!manifest) return;
|
|
331
|
+
if (typeof manifest.apiVersion !== "number") {
|
|
332
|
+
throw new Error(
|
|
333
|
+
`${label}: package.json#amplessPlugin.apiVersion is not a number (got ${JSON.stringify(manifest.apiVersion)}). Update the plugin's package.json or remove the amplessPlugin field.`
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
if (manifest.apiVersion > SUPPORTED_API_VERSION) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
`${label}: package.json#amplessPlugin.apiVersion ${manifest.apiVersion} is newer than this runtime supports (max ${SUPPORTED_API_VERSION}). Upgrade @ampless/runtime, or pin an older version of the plugin.`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
if (manifest.apiVersion !== plugin.apiVersion) {
|
|
342
|
+
throw new Error(
|
|
343
|
+
`${label}: apiVersion mismatch \u2014 package.json declares ${manifest.apiVersion}, factory declares ${plugin.apiVersion}. The two must agree.`
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
if (manifest.name !== plugin.name) {
|
|
347
|
+
warn(
|
|
348
|
+
`${label}: name mismatch \u2014 package.json#amplessPlugin.name is "${manifest.name}", factory returns name="${plugin.name}". Align them so admin UI / capability gates can identify the plugin consistently.`
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
if (manifest.trustLevel !== plugin.trust_level) {
|
|
352
|
+
warn(
|
|
353
|
+
`${label}: trustLevel mismatch \u2014 package.json declares "${manifest.trustLevel}", factory declares trust_level="${plugin.trust_level}". The processor IAM policies are wired off trust_level; drift here usually means the deployment lambda runs in the wrong context.`
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
const factoryCaps = Array.isArray(plugin.capabilities) ? plugin.capabilities : [];
|
|
357
|
+
const manifestCaps = manifest.capabilities;
|
|
358
|
+
if (!setsEqual(factoryCaps, manifestCaps)) {
|
|
359
|
+
warn(
|
|
360
|
+
`${label}: capabilities mismatch \u2014 package.json declares [${manifestCaps.join(", ")}], factory declares [${factoryCaps.join(", ")}]. The static manifest is what npm registry / admin UI surfaces show before the plugin loads, so it should match what the factory actually returns.`
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function setsEqual(a, b) {
|
|
365
|
+
const sa = new Set(a);
|
|
366
|
+
const sb = new Set(b);
|
|
367
|
+
if (sa.size !== sb.size) return false;
|
|
368
|
+
for (const c of sb) if (!sa.has(c)) return false;
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
280
371
|
function applyAttrs(target, attrs, ownerLabel) {
|
|
281
372
|
if (!attrs) return;
|
|
282
373
|
for (const [k, v] of Object.entries(attrs)) {
|
|
@@ -289,6 +380,42 @@ function applyAttrs(target, attrs, ownerLabel) {
|
|
|
289
380
|
target[k] = v;
|
|
290
381
|
}
|
|
291
382
|
}
|
|
383
|
+
function escapeJsonLdInlineBody(value) {
|
|
384
|
+
return value.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
385
|
+
}
|
|
386
|
+
function inlineScriptTypeAllowed(surface, scriptType) {
|
|
387
|
+
if (scriptType === "application/ld+json") return true;
|
|
388
|
+
if (scriptType === void 0) {
|
|
389
|
+
return surface === "head" || surface === "body-end";
|
|
390
|
+
}
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
function renderInlineScript(descriptor, pluginLabel, index, surface) {
|
|
394
|
+
if (!descriptor.id) {
|
|
395
|
+
warn(
|
|
396
|
+
`${pluginLabel}: inlineScript descriptor #${index} dropped \u2014 missing required "id".`
|
|
397
|
+
);
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
if (!inlineScriptTypeAllowed(surface, descriptor.scriptType)) {
|
|
401
|
+
const got = descriptor.scriptType === void 0 ? "undefined" : `"${descriptor.scriptType}"`;
|
|
402
|
+
const allowed = surface === "body-for-post" ? `"application/ld+json" (required on publicBodyForPost \u2014 the per-post surface is scoped to JSON-LD)` : `undefined or "application/ld+json"`;
|
|
403
|
+
warn(
|
|
404
|
+
`${pluginLabel}: inlineScript "${descriptor.id}" dropped \u2014 scriptType ${got} not allowed on ${surface}. Allowed: ${allowed}.`
|
|
405
|
+
);
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
const body = descriptor.scriptType === "application/ld+json" ? escapeJsonLdInlineBody(descriptor.body) : descriptor.body;
|
|
409
|
+
const props = {
|
|
410
|
+
id: descriptor.id,
|
|
411
|
+
dangerouslySetInnerHTML: { __html: body }
|
|
412
|
+
};
|
|
413
|
+
if (descriptor.scriptType) props.type = descriptor.scriptType;
|
|
414
|
+
return {
|
|
415
|
+
id: descriptor.id,
|
|
416
|
+
element: createElement("script", props)
|
|
417
|
+
};
|
|
418
|
+
}
|
|
292
419
|
function renderHeadDescriptor(descriptor, pluginLabel, index) {
|
|
293
420
|
switch (descriptor.type) {
|
|
294
421
|
case "script": {
|
|
@@ -319,22 +446,8 @@ function renderHeadDescriptor(descriptor, pluginLabel, index) {
|
|
|
319
446
|
element: createElement("script", props)
|
|
320
447
|
};
|
|
321
448
|
}
|
|
322
|
-
case "inlineScript":
|
|
323
|
-
|
|
324
|
-
warn(
|
|
325
|
-
`${pluginLabel}: inlineScript descriptor #${index} dropped \u2014 missing required "id".`
|
|
326
|
-
);
|
|
327
|
-
return null;
|
|
328
|
-
}
|
|
329
|
-
const props = {
|
|
330
|
-
id: descriptor.id,
|
|
331
|
-
dangerouslySetInnerHTML: { __html: descriptor.body }
|
|
332
|
-
};
|
|
333
|
-
return {
|
|
334
|
-
id: descriptor.id,
|
|
335
|
-
element: createElement("script", props)
|
|
336
|
-
};
|
|
337
|
-
}
|
|
449
|
+
case "inlineScript":
|
|
450
|
+
return renderInlineScript(descriptor, pluginLabel, index, "head");
|
|
338
451
|
case "meta": {
|
|
339
452
|
const props = { content: descriptor.content };
|
|
340
453
|
if (descriptor.name) props.name = descriptor.name;
|
|
@@ -401,8 +514,20 @@ function renderBodyDescriptor(descriptor, pluginLabel, index) {
|
|
|
401
514
|
element: createElement("iframe", props)
|
|
402
515
|
};
|
|
403
516
|
}
|
|
517
|
+
if (descriptor.type === "inlineScript") {
|
|
518
|
+
return renderInlineScript(descriptor, pluginLabel, index, "body-end");
|
|
519
|
+
}
|
|
404
520
|
return renderHeadDescriptor(descriptor, pluginLabel, index);
|
|
405
521
|
}
|
|
522
|
+
function renderPostBodyDescriptor(descriptor, pluginLabel, index) {
|
|
523
|
+
if (descriptor.type !== "inlineScript") {
|
|
524
|
+
warn(
|
|
525
|
+
`${pluginLabel}: publicBodyForPost descriptor #${index} dropped \u2014 only inlineScript with scriptType "application/ld+json" is allowed on this surface.`
|
|
526
|
+
);
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
return renderInlineScript(descriptor, pluginLabel, index, "body-for-post");
|
|
530
|
+
}
|
|
406
531
|
function dedupeAndKey(entries) {
|
|
407
532
|
const lastIndexById = /* @__PURE__ */ new Map();
|
|
408
533
|
for (let i = 0; i < entries.length; i++) {
|
|
@@ -461,6 +586,31 @@ function collectFor(plugins, site, snapshot, surface, renderOne) {
|
|
|
461
586
|
const keyed = dedupeAndKey(entries);
|
|
462
587
|
return createElement(Fragment, null, ...keyed);
|
|
463
588
|
}
|
|
589
|
+
function collectForPost(plugins, site, snapshot, post) {
|
|
590
|
+
const entries = [];
|
|
591
|
+
for (const plugin of plugins) {
|
|
592
|
+
const factory = plugin.publicBodyForPost;
|
|
593
|
+
if (!factory) continue;
|
|
594
|
+
const ctx = makeCtx(plugin, site, snapshot);
|
|
595
|
+
let descriptors;
|
|
596
|
+
try {
|
|
597
|
+
descriptors = factory.call(plugin, post, ctx) ?? [];
|
|
598
|
+
} catch (err) {
|
|
599
|
+
warn(
|
|
600
|
+
`plugin "${plugin.instanceId ?? plugin.name}" threw inside publicBodyForPost callback: ${err instanceof Error ? err.message : String(err)}`
|
|
601
|
+
);
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
const label = `plugin "${plugin.instanceId ?? plugin.name}"`;
|
|
605
|
+
for (let i = 0; i < descriptors.length; i++) {
|
|
606
|
+
const entry = renderPostBodyDescriptor(descriptors[i], label, i);
|
|
607
|
+
if (entry) entries.push(entry);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
if (entries.length === 0) return null;
|
|
611
|
+
const keyed = dedupeAndKey(entries);
|
|
612
|
+
return createElement(Fragment, null, ...keyed);
|
|
613
|
+
}
|
|
464
614
|
function createPluginHead(cmsConfig, pluginSettings) {
|
|
465
615
|
const plugins = (cmsConfig.plugins ?? []).filter(isPlugin2);
|
|
466
616
|
const validPlugins = [];
|
|
@@ -490,6 +640,9 @@ function createPluginHead(cmsConfig, pluginSettings) {
|
|
|
490
640
|
}
|
|
491
641
|
}
|
|
492
642
|
validPlugins.push(plugin);
|
|
643
|
+
if (plugin.packageName) {
|
|
644
|
+
crossCheckStaticManifest(plugin, label);
|
|
645
|
+
}
|
|
493
646
|
const caps = plugin.capabilities;
|
|
494
647
|
if (caps) {
|
|
495
648
|
if (caps.includes("publicHead") && !plugin.publicHead) {
|
|
@@ -512,6 +665,16 @@ function createPluginHead(cmsConfig, pluginSettings) {
|
|
|
512
665
|
`${label}: implements \`publicBodyEnd\` but "publicBody" is not in declared capabilities. Add it so admin UI / capability gates see the surface.`
|
|
513
666
|
);
|
|
514
667
|
}
|
|
668
|
+
if (caps.includes("schema") && !plugin.publicBodyForPost) {
|
|
669
|
+
warn(
|
|
670
|
+
`${label}: declares capability "schema" but no \`publicBodyForPost\` implementation. Drop the capability or add the function.`
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
if (plugin.publicBodyForPost && !caps.includes("schema")) {
|
|
674
|
+
warn(
|
|
675
|
+
`${label}: implements \`publicBodyForPost\` but "schema" is not in declared capabilities. Add it so admin UI / capability gates see the surface.`
|
|
676
|
+
);
|
|
677
|
+
}
|
|
515
678
|
}
|
|
516
679
|
}
|
|
517
680
|
return {
|
|
@@ -534,6 +697,10 @@ function createPluginHead(cmsConfig, pluginSettings) {
|
|
|
534
697
|
(p) => p.publicBodyEnd,
|
|
535
698
|
renderBodyDescriptor
|
|
536
699
|
);
|
|
700
|
+
},
|
|
701
|
+
async renderBodyForPost(post) {
|
|
702
|
+
const snapshot = await pluginSettings.loadAll();
|
|
703
|
+
return collectForPost(validPlugins, cmsConfig.site, snapshot, post);
|
|
537
704
|
}
|
|
538
705
|
};
|
|
539
706
|
}
|
|
@@ -1025,6 +1192,7 @@ function createAmpless(opts) {
|
|
|
1025
1192
|
siteMetadata: () => seo.siteMetadata(),
|
|
1026
1193
|
publicHead: () => pluginHead.renderHead(),
|
|
1027
1194
|
publicBodyEnd: () => pluginHead.renderBodyEnd(),
|
|
1195
|
+
publicBodyForPost: (post) => pluginHead.renderBodyForPost(post),
|
|
1028
1196
|
renderBody: (post) => renderBody(post),
|
|
1029
1197
|
renderThemeCss: (cssVars) => renderThemeCss(cssVars),
|
|
1030
1198
|
publicAssetUrl: (key) => storage.publicAssetUrl(key),
|
|
@@ -1046,11 +1214,14 @@ function createAmpless(opts) {
|
|
|
1046
1214
|
export {
|
|
1047
1215
|
COLOR_SCHEME_SETTING_KEY,
|
|
1048
1216
|
DEFAULT_COLOR_SCHEME,
|
|
1217
|
+
SUPPORTED_API_VERSION,
|
|
1049
1218
|
_resetStreamS3Cache,
|
|
1050
1219
|
createAmpless,
|
|
1051
1220
|
createPluginHead,
|
|
1052
1221
|
createPluginSettings,
|
|
1222
|
+
escapeJsonLdInlineBody,
|
|
1053
1223
|
htmlToMarkdown,
|
|
1224
|
+
loadPackageManifest,
|
|
1054
1225
|
markdownToHtml,
|
|
1055
1226
|
renderBody,
|
|
1056
1227
|
renderThemeCss,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ampless/runtime",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.28",
|
|
4
4
|
"description": "Public-side runtime for ampless: post fetching, theme dispatch, middleware, public route handlers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"lucide-react": "^1.16.0",
|
|
50
50
|
"marked": "^18.0.4",
|
|
51
51
|
"tailwind-merge": "^3.6.0",
|
|
52
|
-
"ampless": "1.0.0-alpha.
|
|
53
|
-
"@ampless/plugin-og-image": "0.2.0-alpha.
|
|
52
|
+
"ampless": "1.0.0-alpha.22",
|
|
53
|
+
"@ampless/plugin-og-image": "0.2.0-alpha.22"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"next": "^15 || ^16",
|