Skip to content

Commit e6f78d9

Browse files
committed
maintain a list of reserved route names to prevent collisions #106
1 parent bed5091 commit e6f78d9

File tree

1 file changed

+71
-0
lines changed

1 file changed

+71
-0
lines changed

tools/build-manifest.mjs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,27 @@ function formatError(message, options = {}) {
3939
const DEFAULT_ICON_NAME = "default";
4040
const DEFAULT_ICON_VIEWBOX = "0 0 24 24";
4141
const SUPPORTED_PLUGIN_TYPES = ["module", "project"];
42+
const RESERVED_NAMES = Object.freeze({
43+
routes: [
44+
"api",
45+
"auth",
46+
"login",
47+
"logout",
48+
"register",
49+
"ws",
50+
"public",
51+
"static",
52+
"assets",
53+
"css",
54+
"js",
55+
"manifest",
56+
"sw",
57+
"service-worker",
58+
"uploads",
59+
],
60+
plugins: ["sovereign", "platform", "core", "admin"],
61+
keywords: ["favicon", "robots", "security", "health", "status"],
62+
});
4263

4364
function normalizeRoleValue(role) {
4465
if (!role) return null;
@@ -170,6 +191,7 @@ const manifest = {
170191
modules: [],
171192
enabledPlugins: [], // [@<org>/<ns>]
172193
allowedPluginFrameworks: [],
194+
reservedNames: RESERVED_NAMES,
173195
__rootdir,
174196
__pluginsdir,
175197
__datadir,
@@ -350,6 +372,10 @@ const exists = async (p) => {
350372

351373
const buildManifest = async () => {
352374
const plugins = {};
375+
const validationErrors = [];
376+
const seenNamespaces = new Set();
377+
const seenIds = new Set();
378+
const reserved = manifest.reservedNames || RESERVED_NAMES;
353379

354380
// Preserve createdAt/instanceId and always update updatedAt
355381
let existingCreatedAt = null;
@@ -476,6 +502,46 @@ const buildManifest = async () => {
476502

477503
// TODO: split `id` field by "/", and take id[1] as one of the fallback namespace value
478504
const manifestNamespace = pluginManifest.namespace || plugingDirName;
505+
const manifestId = pluginManifest.id || "";
506+
507+
const isReserved =
508+
(reserved?.routes || []).includes(manifestNamespace) ||
509+
(reserved?.plugins || []).includes(manifestNamespace) ||
510+
(reserved?.keywords || []).includes(manifestNamespace);
511+
512+
if (isReserved) {
513+
validationErrors.push(
514+
formatError(
515+
`✗ namespace "${manifestNamespace}" is reserved (routes/plugins/keywords); update the plugin namespace.`,
516+
{ manifestPath: pluginManifestPath, pluginDir: plugingRoot }
517+
)
518+
);
519+
continue;
520+
}
521+
522+
if (seenNamespaces.has(manifestNamespace)) {
523+
validationErrors.push(
524+
formatError(`✗ duplicate namespace "${manifestNamespace}" detected`, {
525+
manifestPath: pluginManifestPath,
526+
pluginDir: plugingRoot,
527+
})
528+
);
529+
continue;
530+
}
531+
seenNamespaces.add(manifestNamespace);
532+
533+
if (manifestId) {
534+
if (seenIds.has(manifestId)) {
535+
validationErrors.push(
536+
formatError(`✗ duplicate plugin id "${manifestId}" detected`, {
537+
manifestPath: pluginManifestPath,
538+
pluginDir: plugingRoot,
539+
})
540+
);
541+
continue;
542+
}
543+
seenIds.add(manifestId);
544+
}
479545

480546
const enrollStrategy =
481547
pluginManifest.corePlugin === true
@@ -592,6 +658,11 @@ const buildManifest = async () => {
592658
};
593659
}
594660

661+
if (validationErrors.length) {
662+
validationErrors.forEach((msg) => console.error(msg));
663+
throw new Error(`Manifest build failed: ${validationErrors.length} validation error(s).`);
664+
}
665+
595666
const finalPlugins = {
596667
...manifest.plugins,
597668
...plugins,

0 commit comments

Comments
 (0)