velora-plugin 0.9.0
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/LICENSE +202 -0
- package/README.md +22 -0
- package/dist/build-federation-bundle.d.ts +29 -0
- package/dist/build-federation-bundle.js +93 -0
- package/dist/build.d.ts +41 -0
- package/dist/build.js +123 -0
- package/dist/bundle-server-manifest.d.ts +27 -0
- package/dist/bundle-server-manifest.js +93 -0
- package/dist/checks/audit.d.ts +2 -0
- package/dist/checks/audit.js +50 -0
- package/dist/checks/bundle-size.d.ts +3 -0
- package/dist/checks/bundle-size.js +21 -0
- package/dist/checks/db-compat.d.ts +3 -0
- package/dist/checks/db-compat.js +15 -0
- package/dist/checks/manifest.d.ts +3 -0
- package/dist/checks/manifest.js +12 -0
- package/dist/checks/shared-deps.d.ts +3 -0
- package/dist/checks/shared-deps.js +31 -0
- package/dist/checks/typescript.d.ts +2 -0
- package/dist/checks/typescript.js +34 -0
- package/dist/checks/views.d.ts +3 -0
- package/dist/checks/views.js +63 -0
- package/dist/copy-sample-content.d.ts +39 -0
- package/dist/copy-sample-content.js +57 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +159 -0
- package/dist/load-manifest.d.ts +18 -0
- package/dist/load-manifest.js +49 -0
- package/dist/pack-starter.d.ts +48 -0
- package/dist/pack-starter.js +118 -0
- package/dist/pack.d.ts +12 -0
- package/dist/pack.js +26 -0
- package/dist/publish.d.ts +24 -0
- package/dist/publish.js +48 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.js +1 -0
- package/dist/validate.d.ts +5 -0
- package/dist/validate.js +49 -0
- package/dist/write-manifest-json.d.ts +7 -0
- package/dist/write-manifest-json.js +72 -0
- package/package.json +54 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { create } from "tar";
|
|
4
|
+
// Only 'section' and 'bundle' manifests can carry a bundledTemplate
|
|
5
|
+
// contribution (SectionPluginContributions / BundlePluginContributions in
|
|
6
|
+
// manifest.ts) — 'template' plugins don't declare this key at all (see
|
|
7
|
+
// isStarterBearing below for how 'template' is still legally starter-
|
|
8
|
+
// bearing, just not through this specific structural check).
|
|
9
|
+
const BUNDLED_TEMPLATE_TYPES = ["section", "bundle"];
|
|
10
|
+
/**
|
|
11
|
+
* Structural check for "this manifest declares a bundledTemplate
|
|
12
|
+
* contribution" — mirrors the compile-time contribution matrix at
|
|
13
|
+
* runtime, the same way validate.ts's Zod schemas do, without importing
|
|
14
|
+
* any Zod schema here (loadManifest already validated the manifest before
|
|
15
|
+
* build.ts ever calls this).
|
|
16
|
+
*/
|
|
17
|
+
export function hasBundledTemplateContribution(manifest) {
|
|
18
|
+
return (BUNDLED_TEMPLATE_TYPES.includes(manifest.type) &&
|
|
19
|
+
"bundledTemplate" in manifest.contributions &&
|
|
20
|
+
manifest.contributions.bundledTemplate !== undefined);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Whether a starter/ directory is LEGAL for this manifest — the check
|
|
24
|
+
* packStarterIfPresent actually gates on. True in two cases:
|
|
25
|
+
* - the manifest declares a bundledTemplate contribution (section/bundle,
|
|
26
|
+
* hasBundledTemplateContribution above), or
|
|
27
|
+
* - the manifest's type is 'template'. Per manifest.ts's
|
|
28
|
+
* TemplatePluginContributions doc comment: "A template plugin IS a
|
|
29
|
+
* bundled template (per the matrix), so there is no separate
|
|
30
|
+
* bundledTemplate key — it contributes the document types its starter
|
|
31
|
+
* needs." A template plugin is implicitly starter-bearing by design,
|
|
32
|
+
* not by an explicit contribution flag, so it can never fail the
|
|
33
|
+
* 'declares bundledTemplate' structural check the way section/bundle
|
|
34
|
+
* manifests do — it's legal unconditionally.
|
|
35
|
+
*/
|
|
36
|
+
export function isStarterBearing(manifest) {
|
|
37
|
+
return hasBundledTemplateContribution(manifest) || manifest.type === "template";
|
|
38
|
+
}
|
|
39
|
+
export class UndeclaredStarterError extends Error {
|
|
40
|
+
constructor(pluginDir) {
|
|
41
|
+
super(`${pluginDir} has a starter/ directory but its manifest does not declare a bundledTemplate contribution ` +
|
|
42
|
+
`and is not a 'template'-type plugin. An undeclared starter/ is treated as a mistake, not a choice — ` +
|
|
43
|
+
`either add contributions.bundledTemplate (only 'section' and 'bundle' plugin types may), make the ` +
|
|
44
|
+
`plugin type 'template' (implicitly starter-bearing), or remove the starter/ directory.`);
|
|
45
|
+
this.name = "UndeclaredStarterError";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Directory names excluded from the packed archive by name, at ANY depth —
|
|
49
|
+
// starter/ packs SOURCE only (the 20MB artifact cap and the "never ship
|
|
50
|
+
// node_modules" rule both depend on this). A vendored dir doesn't only
|
|
51
|
+
// appear at the starter/ root — e.g. a monorepo-shaped starter can have
|
|
52
|
+
// `apps/site/node_modules/...` — so every path segment is checked, not just
|
|
53
|
+
// the first. `.astro` is Astro's generated dir (content types, dev logs) —
|
|
54
|
+
// build output, never source.
|
|
55
|
+
const EXCLUDED_DIRS = new Set(["node_modules", "dist", ".git", ".next", ".astro"]);
|
|
56
|
+
// Env files are excluded by segment at any depth — `.env`, `.env.local`,
|
|
57
|
+
// `.env.production`, etc. can hold live secrets and must never ship inside
|
|
58
|
+
// a served archive. The one deliberate exception is `.env.example`: it is
|
|
59
|
+
// starter documentation (placeholder values a user copies to a real .env),
|
|
60
|
+
// so it keeps packing.
|
|
61
|
+
const ENV_FILE_PATTERN = /^\.env(\..*)?$/;
|
|
62
|
+
function isEnvFileSegment(segment) {
|
|
63
|
+
return ENV_FILE_PATTERN.test(segment) && segment !== ".env.example";
|
|
64
|
+
}
|
|
65
|
+
function isExcluded(entryPath) {
|
|
66
|
+
// node-tar's create() filter receives archive-relative entry paths,
|
|
67
|
+
// posix-normalized (forward slashes) regardless of host OS, and
|
|
68
|
+
// "./"-prefixed for entries directly under cwd (e.g. "./node_modules",
|
|
69
|
+
// "./node_modules/some-dep/index.js") — strip that prefix before splitting
|
|
70
|
+
// into segments, and split on either separator defensively rather than
|
|
71
|
+
// depend on posix-only output. Excluded if ANY segment matches, so a
|
|
72
|
+
// FILE merely named e.g. "dist" (no extension) is also excluded — that's
|
|
73
|
+
// an accepted ambiguity, same as npm-pack-style ignore matching, not a
|
|
74
|
+
// bug: the archive is source-only and a bare `dist`/`node_modules`/`.git`/
|
|
75
|
+
// `.next` file is not a plausible legitimate source file in a starter.
|
|
76
|
+
const normalized = entryPath.replace(/^\.\/?/, "");
|
|
77
|
+
const segments = normalized.split(/[\\/]/);
|
|
78
|
+
return segments.some((segment) => EXCLUDED_DIRS.has(segment) || isEnvFileSegment(segment));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Packs <pluginDir>/starter/ into <outDir>/starter.tar.gz when present —
|
|
82
|
+
* the S148 delivery mechanism (the archive rides inside the plugin
|
|
83
|
+
* artifact; packPlugin tars the whole outDir, so nothing else has to
|
|
84
|
+
* change for it to reach the marketplace).
|
|
85
|
+
*
|
|
86
|
+
* Rules (S148 Global Constraints):
|
|
87
|
+
* - starter/ absent → no-op, regardless of whether bundledTemplate is
|
|
88
|
+
* declared (a bundledTemplate without an archive stays legal —
|
|
89
|
+
* repository-only or metadata-only).
|
|
90
|
+
* - starter/ present, NOT starter-bearing (see isStarterBearing) → throws
|
|
91
|
+
* UndeclaredStarterError. An undeclared starter/ is a mistake, not a
|
|
92
|
+
* choice.
|
|
93
|
+
* - starter/ present and starter-bearing (bundledTemplate declared, OR
|
|
94
|
+
* type 'template') → archive created, entries relative to starter/,
|
|
95
|
+
* excluding node_modules/dist/.git/.next/.astro and env files
|
|
96
|
+
* (.env, .env.* — except .env.example) at any depth.
|
|
97
|
+
*
|
|
98
|
+
* Returns the archive path, or null when no archive was created.
|
|
99
|
+
*/
|
|
100
|
+
export async function packStarterIfPresent(pluginDir, outDir, manifest) {
|
|
101
|
+
const starterDir = join(pluginDir, "starter");
|
|
102
|
+
if (!existsSync(starterDir))
|
|
103
|
+
return null;
|
|
104
|
+
if (!isStarterBearing(manifest)) {
|
|
105
|
+
throw new UndeclaredStarterError(pluginDir);
|
|
106
|
+
}
|
|
107
|
+
const archivePath = join(outDir, "starter.tar.gz");
|
|
108
|
+
await create({
|
|
109
|
+
gzip: true,
|
|
110
|
+
file: archivePath,
|
|
111
|
+
cwd: starterDir,
|
|
112
|
+
filter: (entryPath) => !isExcluded(entryPath),
|
|
113
|
+
},
|
|
114
|
+
// Same convention as pack.ts: "." packs everything directly under
|
|
115
|
+
// starter/ at the archive root, not nested under a "starter/" folder.
|
|
116
|
+
["."]);
|
|
117
|
+
return archivePath;
|
|
118
|
+
}
|
package/dist/pack.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type BuildResult } from "./build.js";
|
|
2
|
+
export interface PackResult {
|
|
3
|
+
pluginDir: string;
|
|
4
|
+
archivePath: string;
|
|
5
|
+
buildResult: BuildResult;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Always runs buildPlugin() first (never packs a stale package), then
|
|
9
|
+
* tars the resulting velora-package/ directory into
|
|
10
|
+
* <pluginDir>/<id>-<version>.tar.gz.
|
|
11
|
+
*/
|
|
12
|
+
export declare function packPlugin(pluginDir: string, outFile?: string): Promise<PackResult>;
|
package/dist/pack.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { create } from "tar";
|
|
3
|
+
import { buildPlugin } from "./build.js";
|
|
4
|
+
import { loadManifest } from "./load-manifest.js";
|
|
5
|
+
/**
|
|
6
|
+
* Always runs buildPlugin() first (never packs a stale package), then
|
|
7
|
+
* tars the resulting velora-package/ directory into
|
|
8
|
+
* <pluginDir>/<id>-<version>.tar.gz.
|
|
9
|
+
*/
|
|
10
|
+
export async function packPlugin(pluginDir, outFile) {
|
|
11
|
+
const buildResult = await buildPlugin(pluginDir);
|
|
12
|
+
const manifestResult = await loadManifest(pluginDir);
|
|
13
|
+
if (!manifestResult.ok) {
|
|
14
|
+
throw new Error(`manifest became invalid between build and pack for ${pluginDir}`);
|
|
15
|
+
}
|
|
16
|
+
const { id, version } = manifestResult.manifest;
|
|
17
|
+
const archivePath = outFile ?? join(pluginDir, `${id}-${version}.tar.gz`);
|
|
18
|
+
await create({ gzip: true, file: archivePath, cwd: buildResult.outDir },
|
|
19
|
+
// tar's `create` lists entries relative to `cwd` — "." packs
|
|
20
|
+
// everything directly under velora-package/ at the archive root
|
|
21
|
+
// (not nested under a "velora-package/" folder), so extracting the
|
|
22
|
+
// archive lands manifest.json/remoteEntry.js/server/ directly in
|
|
23
|
+
// the target directory.
|
|
24
|
+
["."]);
|
|
25
|
+
return { pluginDir, archivePath, buildResult };
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface PublishOptions {
|
|
2
|
+
registryUrl: string;
|
|
3
|
+
token: string;
|
|
4
|
+
}
|
|
5
|
+
export type PublishResult = {
|
|
6
|
+
ok: true;
|
|
7
|
+
submissionId: string;
|
|
8
|
+
state: string;
|
|
9
|
+
failedChecks?: string[];
|
|
10
|
+
} | {
|
|
11
|
+
ok: false;
|
|
12
|
+
status: number;
|
|
13
|
+
message: string;
|
|
14
|
+
};
|
|
15
|
+
/** Upload an already-packed archive. Split from publishPlugin so tests
|
|
16
|
+
* can exercise the HTTP contract without a 90-second validate+build. */
|
|
17
|
+
export declare function uploadArchive(archivePath: string, opts: PublishOptions): Promise<PublishResult>;
|
|
18
|
+
/** pack (which validates once, first, and fails fast — never uploads a
|
|
19
|
+
* package that can't pass its own local floor) → upload. validatePlugin
|
|
20
|
+
* is deliberately NOT called again here: buildPlugin (invoked by
|
|
21
|
+
* packPlugin) already runs the full validation suite, including a real
|
|
22
|
+
* federation build, and doing that twice per publish would double an
|
|
23
|
+
* already expensive step for no benefit. */
|
|
24
|
+
export declare function publishPlugin(pluginDir: string, opts: PublishOptions): Promise<PublishResult>;
|
package/dist/publish.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { BuildValidationError } from "./build.js";
|
|
4
|
+
import { packPlugin } from "./pack.js";
|
|
5
|
+
/** Upload an already-packed archive. Split from publishPlugin so tests
|
|
6
|
+
* can exercise the HTTP contract without a 90-second validate+build. */
|
|
7
|
+
export async function uploadArchive(archivePath, opts) {
|
|
8
|
+
const body = new FormData();
|
|
9
|
+
body.set("file", new Blob([readFileSync(archivePath)], { type: "application/gzip" }), basename(archivePath));
|
|
10
|
+
let response;
|
|
11
|
+
try {
|
|
12
|
+
response = await fetch(new URL("/api/v1/submissions", opts.registryUrl), {
|
|
13
|
+
method: "POST",
|
|
14
|
+
headers: { authorization: `Bearer ${opts.token}` },
|
|
15
|
+
body,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return { ok: false, status: 0, message: `could not reach the registry at ${opts.registryUrl}: ${error instanceof Error ? error.message : String(error)}` };
|
|
20
|
+
}
|
|
21
|
+
const payload = (await response.json().catch(() => ({})));
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
return { ok: false, status: response.status, message: payload.message ?? `registry replied ${response.status}` };
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
ok: true, submissionId: payload.submissionId ?? "", state: payload.state ?? "submitted",
|
|
27
|
+
...(payload.failedChecks ? { failedChecks: payload.failedChecks } : {}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** pack (which validates once, first, and fails fast — never uploads a
|
|
31
|
+
* package that can't pass its own local floor) → upload. validatePlugin
|
|
32
|
+
* is deliberately NOT called again here: buildPlugin (invoked by
|
|
33
|
+
* packPlugin) already runs the full validation suite, including a real
|
|
34
|
+
* federation build, and doing that twice per publish would double an
|
|
35
|
+
* already expensive step for no benefit. */
|
|
36
|
+
export async function publishPlugin(pluginDir, opts) {
|
|
37
|
+
try {
|
|
38
|
+
const packed = await packPlugin(pluginDir);
|
|
39
|
+
return uploadArchive(packed.archivePath, opts);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (error instanceof BuildValidationError) {
|
|
43
|
+
const failed = error.validation.checks.filter((c) => !c.passed).map((c) => c.name).join(", ");
|
|
44
|
+
return { ok: false, status: 0, message: `local validate failed (${failed}) — fix these before publishing` };
|
|
45
|
+
}
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ValidationResult } from "./types.js";
|
|
2
|
+
export type { CheckResult, ValidationResult } from "./types.js";
|
|
3
|
+
export { loadManifest } from "./load-manifest.js";
|
|
4
|
+
export { CLIENT_BUNDLE_TYPES, hasClientBundle } from "@velora-cms/plugin-sdk/manifest";
|
|
5
|
+
export declare function validatePlugin(pluginDir: string): Promise<ValidationResult>;
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { buildFederationBundle, cleanupFederationBuild } from "./build-federation-bundle.js";
|
|
2
|
+
import { checkNpmAudit } from "./checks/audit.js";
|
|
3
|
+
import { checkBundleSize } from "./checks/bundle-size.js";
|
|
4
|
+
import { checkDatabaseCompatibility } from "./checks/db-compat.js";
|
|
5
|
+
import { checkManifest } from "./checks/manifest.js";
|
|
6
|
+
import { checkSharedDeps } from "./checks/shared-deps.js";
|
|
7
|
+
import { checkTypeScript } from "./checks/typescript.js";
|
|
8
|
+
import { checkRequiredViews } from "./checks/views.js";
|
|
9
|
+
import { loadManifest } from "./load-manifest.js";
|
|
10
|
+
import { hasClientBundle } from "@velora-cms/plugin-sdk/manifest";
|
|
11
|
+
// Re-exported from the package root so consumers that only need manifest
|
|
12
|
+
// loading (e.g. the marketplace seed script) don't need a new subpath —
|
|
13
|
+
// validate.js is already the package's "." export.
|
|
14
|
+
export { loadManifest } from "./load-manifest.js";
|
|
15
|
+
// Same reasoning as loadManifest above: consumers of the artifact-shape
|
|
16
|
+
// contract (S144) get it from the package root without a new subpath.
|
|
17
|
+
export { CLIENT_BUNDLE_TYPES, hasClientBundle } from "@velora-cms/plugin-sdk/manifest";
|
|
18
|
+
export async function validatePlugin(pluginDir) {
|
|
19
|
+
const manifestResult = await loadManifest(pluginDir);
|
|
20
|
+
const checks = [
|
|
21
|
+
checkManifest(manifestResult),
|
|
22
|
+
checkRequiredViews(manifestResult),
|
|
23
|
+
checkTypeScript(pluginDir),
|
|
24
|
+
];
|
|
25
|
+
// S144: only client-bearing types ship a federation bundle — for
|
|
26
|
+
// theme/template/bundle there is nothing to size-check and no shared
|
|
27
|
+
// singletons to verify, and running Vite for them is pure waste. The
|
|
28
|
+
// checks stay in the report (passed, "not applicable") so a report
|
|
29
|
+
// always has the same shape regardless of type.
|
|
30
|
+
const clientBundle = manifestResult.ok ? hasClientBundle(manifestResult.manifest.type) : true;
|
|
31
|
+
if (clientBundle) {
|
|
32
|
+
const build = await buildFederationBundle(pluginDir);
|
|
33
|
+
try {
|
|
34
|
+
checks.push(checkBundleSize(build));
|
|
35
|
+
checks.push(checkSharedDeps(pluginDir, build));
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
cleanupFederationBuild(build);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const message = `not applicable — ${manifestResult.ok ? manifestResult.manifest.type : ""} plugins ship no client bundle`;
|
|
43
|
+
checks.push({ name: "bundle size", passed: true, message });
|
|
44
|
+
checks.push({ name: "react/react-dom not bundled", passed: true, message });
|
|
45
|
+
}
|
|
46
|
+
checks.push(checkDatabaseCompatibility(manifestResult));
|
|
47
|
+
checks.push(checkNpmAudit(pluginDir));
|
|
48
|
+
return { pluginDir, checks, passed: checks.every((check) => check.passed) };
|
|
49
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CMSPlugin } from "@velora-cms/plugin-sdk/manifest";
|
|
2
|
+
/** Writes `<outDir>/manifest.json` — a redacted, JSON-safe summary of a
|
|
3
|
+
* validated plugin manifest for marketplace listing/review without
|
|
4
|
+
* executing any code. Never the source of truth for install() (which
|
|
5
|
+
* needs the live manifest from server/plugin.js, see
|
|
6
|
+
* bundle-server-manifest.ts) — this file is display-only. */
|
|
7
|
+
export declare function writeManifestJson(manifest: CMSPlugin, outDir: string): void;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const CONTRIBUTION_ENTRY_FIELDS = ["id", "name", "label", "icon", "version", "method", "path", "cron"];
|
|
4
|
+
/**
|
|
5
|
+
* Redacts one contribution-array entry (e.g. one DataTypePlugin, one
|
|
6
|
+
* AdminSectionContribution) down to its plain string identifying fields
|
|
7
|
+
* — never the functions (views, serialize, onSelect, handler, ...) or
|
|
8
|
+
* raw JSON schemas a real contribution object also carries. A bare
|
|
9
|
+
* string entry (bundledPlugins' plugin ids) passes through unchanged.
|
|
10
|
+
*/
|
|
11
|
+
function summarizeContributionEntry(entry) {
|
|
12
|
+
if (entry === null || typeof entry !== "object")
|
|
13
|
+
return entry;
|
|
14
|
+
const record = entry;
|
|
15
|
+
const summary = {};
|
|
16
|
+
for (const field of CONTRIBUTION_ENTRY_FIELDS) {
|
|
17
|
+
if (typeof record[field] === "string")
|
|
18
|
+
summary[field] = record[field];
|
|
19
|
+
}
|
|
20
|
+
return summary;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Redacts a manifest's full `contributions` object to a JSON-safe
|
|
24
|
+
* summary suitable for marketplace listing/review without executing
|
|
25
|
+
* any code: array-valued contributions become arrays of
|
|
26
|
+
* summarizeContributionEntry results; `hooks` (a Record<event, handler
|
|
27
|
+
* function>) becomes just its event-name keys; `bundledTemplate` and
|
|
28
|
+
* `themeTokens` are already plain-string-valued objects, kept as-is;
|
|
29
|
+
* anything else present is recorded as `true` (contributed, no further
|
|
30
|
+
* detail available to redact safely).
|
|
31
|
+
*/
|
|
32
|
+
function summarizeContributions(contributions) {
|
|
33
|
+
const summary = {};
|
|
34
|
+
for (const [key, value] of Object.entries(contributions)) {
|
|
35
|
+
if (value == null)
|
|
36
|
+
continue;
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
summary[key] = value.map(summarizeContributionEntry);
|
|
39
|
+
}
|
|
40
|
+
else if (key === "hooks" && typeof value === "object") {
|
|
41
|
+
summary[key] = Object.keys(value);
|
|
42
|
+
}
|
|
43
|
+
else if (key === "bundledTemplate" || key === "themeTokens") {
|
|
44
|
+
summary[key] = value;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
summary[key] = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return summary;
|
|
51
|
+
}
|
|
52
|
+
/** Writes `<outDir>/manifest.json` — a redacted, JSON-safe summary of a
|
|
53
|
+
* validated plugin manifest for marketplace listing/review without
|
|
54
|
+
* executing any code. Never the source of truth for install() (which
|
|
55
|
+
* needs the live manifest from server/plugin.js, see
|
|
56
|
+
* bundle-server-manifest.ts) — this file is display-only. */
|
|
57
|
+
export function writeManifestJson(manifest, outDir) {
|
|
58
|
+
const summary = {
|
|
59
|
+
id: manifest.id,
|
|
60
|
+
name: manifest.name,
|
|
61
|
+
version: manifest.version,
|
|
62
|
+
type: manifest.type,
|
|
63
|
+
description: manifest.description,
|
|
64
|
+
author: manifest.author,
|
|
65
|
+
license: manifest.license,
|
|
66
|
+
cmsVersion: manifest.cmsVersion,
|
|
67
|
+
databaseCompatibility: manifest.databaseCompatibility,
|
|
68
|
+
permissions: manifest.permissions,
|
|
69
|
+
contributions: summarizeContributions(manifest.contributions),
|
|
70
|
+
};
|
|
71
|
+
writeFileSync(join(outDir, "manifest.json"), JSON.stringify(summary, null, 2));
|
|
72
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "velora-plugin",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"velora-plugin": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/validate.js",
|
|
10
|
+
"types": "dist/validate.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/velora-cms/velora.git",
|
|
17
|
+
"directory": "packages/velora-plugin"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/validate.d.ts",
|
|
25
|
+
"import": "./dist/validate.js",
|
|
26
|
+
"default": "./dist/validate.js"
|
|
27
|
+
},
|
|
28
|
+
"./pack": {
|
|
29
|
+
"types": "./dist/pack.d.ts",
|
|
30
|
+
"import": "./dist/pack.js",
|
|
31
|
+
"default": "./dist/pack.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@module-federation/vite": "1.16.12",
|
|
36
|
+
"@vitejs/plugin-react": "4.7.0",
|
|
37
|
+
"tar": "^7.5.19",
|
|
38
|
+
"tsx": "4.22.4",
|
|
39
|
+
"typescript": "6.0.3",
|
|
40
|
+
"vite": "6.4.3",
|
|
41
|
+
"@velora-cms/plugin-sdk": "0.9.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "20.19.43",
|
|
45
|
+
"@types/react": "19.2.17",
|
|
46
|
+
"react": "19.2.7",
|
|
47
|
+
"react-dom": "19.2.7",
|
|
48
|
+
"create-velora-plugin": "0.9.0"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsc -p .",
|
|
52
|
+
"test": "vitest run"
|
|
53
|
+
}
|
|
54
|
+
}
|