@ampless/runtime 1.0.0-alpha.27 → 1.0.0-alpha.29
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 +41 -2
- package/dist/index.js +96 -0
- 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';
|
|
@@ -269,6 +269,45 @@ interface ThemeConfigApi {
|
|
|
269
269
|
*/
|
|
270
270
|
declare function renderThemeCss(cssVars: Record<string, string>): string;
|
|
271
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
|
+
|
|
272
311
|
declare function renderBody(post: Post): string;
|
|
273
312
|
/**
|
|
274
313
|
* Convert a tiptap doc to its HTML form. Same renderer the public
|
|
@@ -450,4 +489,4 @@ interface Ampless {
|
|
|
450
489
|
*/
|
|
451
490
|
declare function createAmpless(opts: CreateAmplessOpts): Ampless;
|
|
452
491
|
|
|
453
|
-
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, escapeJsonLdInlineBody, 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)) {
|
|
@@ -549,6 +640,9 @@ function createPluginHead(cmsConfig, pluginSettings) {
|
|
|
549
640
|
}
|
|
550
641
|
}
|
|
551
642
|
validPlugins.push(plugin);
|
|
643
|
+
if (plugin.packageName) {
|
|
644
|
+
crossCheckStaticManifest(plugin, label);
|
|
645
|
+
}
|
|
552
646
|
const caps = plugin.capabilities;
|
|
553
647
|
if (caps) {
|
|
554
648
|
if (caps.includes("publicHead") && !plugin.publicHead) {
|
|
@@ -1120,12 +1214,14 @@ function createAmpless(opts) {
|
|
|
1120
1214
|
export {
|
|
1121
1215
|
COLOR_SCHEME_SETTING_KEY,
|
|
1122
1216
|
DEFAULT_COLOR_SCHEME,
|
|
1217
|
+
SUPPORTED_API_VERSION,
|
|
1123
1218
|
_resetStreamS3Cache,
|
|
1124
1219
|
createAmpless,
|
|
1125
1220
|
createPluginHead,
|
|
1126
1221
|
createPluginSettings,
|
|
1127
1222
|
escapeJsonLdInlineBody,
|
|
1128
1223
|
htmlToMarkdown,
|
|
1224
|
+
loadPackageManifest,
|
|
1129
1225
|
markdownToHtml,
|
|
1130
1226
|
renderBody,
|
|
1131
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.29",
|
|
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": "
|
|
53
|
-
"
|
|
52
|
+
"@ampless/plugin-og-image": "0.2.0-alpha.23",
|
|
53
|
+
"ampless": "1.0.0-alpha.23"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"next": "^15 || ^16",
|