@bsb/registry 1.1.7 → 9.6.14
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/README.md +4 -2
- package/bsb-plugin.json +8 -2
- package/lib/.bsb/clients/service-bsb-registry.d.ts +301 -282
- package/lib/.bsb/clients/service-bsb-registry.d.ts.map +1 -1
- package/lib/.bsb/clients/service-bsb-registry.js +71 -10
- package/lib/.bsb/clients/service-bsb-registry.js.map +1 -1
- package/lib/plugins/service-bsb-registry/index.d.ts +10 -10
- package/lib/plugins/service-bsb-registry/index.d.ts.map +1 -1
- package/lib/plugins/service-bsb-registry/index.js +7 -7
- package/lib/plugins/service-bsb-registry/index.js.map +1 -1
- package/lib/plugins/service-bsb-registry/types.d.ts +158 -158
- package/lib/plugins/service-bsb-registry/types.d.ts.map +1 -1
- package/lib/plugins/service-bsb-registry-ui/BADGES.json +22 -0
- package/lib/plugins/service-bsb-registry-ui/http-server.d.ts +4 -2
- package/lib/plugins/service-bsb-registry-ui/http-server.d.ts.map +1 -1
- package/lib/plugins/service-bsb-registry-ui/http-server.js +160 -102
- package/lib/plugins/service-bsb-registry-ui/http-server.js.map +1 -1
- package/lib/plugins/service-bsb-registry-ui/index.d.ts +16 -16
- package/lib/plugins/service-bsb-registry-ui/index.d.ts.map +1 -1
- package/lib/plugins/service-bsb-registry-ui/index.js +8 -8
- package/lib/plugins/service-bsb-registry-ui/index.js.map +1 -1
- package/lib/plugins/service-bsb-registry-ui/templates/partials/plugin-card.hbs +1 -1
- package/lib/schemas/service-bsb-registry-ui.json +49 -37
- package/lib/schemas/service-bsb-registry-ui.plugin.json +49 -37
- package/lib/schemas/service-bsb-registry.json +58 -44
- package/lib/schemas/service-bsb-registry.plugin.json +40 -26
- package/package.json +16 -8
|
@@ -16,9 +16,9 @@ import fastifyStatic from '@fastify/static';
|
|
|
16
16
|
import fastifyView from '@fastify/view';
|
|
17
17
|
import handlebars from 'handlebars';
|
|
18
18
|
import { marked } from 'marked';
|
|
19
|
-
import * as av from '
|
|
19
|
+
import * as av from 'anyvali';
|
|
20
20
|
function objectSchema(shape) {
|
|
21
|
-
return av.object(shape
|
|
21
|
+
return av.object(shape);
|
|
22
22
|
}
|
|
23
23
|
function createValidator(schema, options) {
|
|
24
24
|
return {
|
|
@@ -62,37 +62,46 @@ const semverPattern = /^\d{1,5}\.\d{1,5}\.\d{1,5}(-[a-zA-Z0-9.]+)?$/;
|
|
|
62
62
|
const majorMinorPattern = /^\d{1,5}\.\d{1,5}$/;
|
|
63
63
|
// Package name: npm-style scoped or unscoped
|
|
64
64
|
const packageNamePattern = /^(@[a-zA-Z0-9_-]+\/)?[a-zA-Z0-9._-]+$/;
|
|
65
|
-
const slugField = av.string().minLength(1).maxLength(100).pattern(slugPattern.source)
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
const
|
|
65
|
+
const slugField = () => av.string().minLength(1).maxLength(100).pattern(slugPattern.source)
|
|
66
|
+
.describe('Slug containing letters, numbers, dash, underscore, dot, at-sign, or slash');
|
|
67
|
+
const semverField = () => av.string().minLength(5).maxLength(50).pattern(semverPattern.source)
|
|
68
|
+
.describe('Strict semantic version in major.minor.patch format with optional prerelease');
|
|
69
|
+
const languageEnum = () => av.enum_(['nodejs', 'csharp', 'go', 'java', 'python'])
|
|
70
|
+
.describe('Plugin implementation language');
|
|
71
|
+
const categoryEnum = () => av.enum_(['service', 'observable', 'events', 'config'])
|
|
72
|
+
.describe('Plugin category');
|
|
73
|
+
const visibilityEnum = () => av.enum_(['public', 'private'])
|
|
74
|
+
.describe('Plugin visibility level');
|
|
75
|
+
const safeString = (max) => av.string().maxLength(max).pattern(safeAscii.source)
|
|
76
|
+
.describe('Printable ASCII string with no control characters');
|
|
77
|
+
const safeStringRequired = (min, max) => av.string().minLength(min).maxLength(max).pattern(safeAscii.source)
|
|
78
|
+
.describe('Required printable ASCII string with no control characters');
|
|
72
79
|
// ---- Route param schemas ----
|
|
73
80
|
const OrgParamsSchema = createValidator(objectSchema({
|
|
74
|
-
org: slugField,
|
|
75
|
-
}));
|
|
81
|
+
org: slugField().describe('Organization or user namespace'),
|
|
82
|
+
}).describe('Organization route parameters'));
|
|
76
83
|
const PluginDetailParamsSchema = createValidator(objectSchema({
|
|
77
|
-
org: slugField,
|
|
78
|
-
name: slugField,
|
|
79
|
-
}));
|
|
84
|
+
org: slugField().describe('Organization or user namespace'),
|
|
85
|
+
name: slugField().describe('Plugin name'),
|
|
86
|
+
}).describe('Plugin detail route parameters'));
|
|
80
87
|
const PluginVersionParamsSchema = createValidator(objectSchema({
|
|
81
|
-
org: slugField,
|
|
82
|
-
name: slugField,
|
|
83
|
-
version: semverField,
|
|
84
|
-
}));
|
|
88
|
+
org: slugField().describe('Organization or user namespace'),
|
|
89
|
+
name: slugField().describe('Plugin name'),
|
|
90
|
+
version: semverField().describe('Plugin semantic version'),
|
|
91
|
+
}).describe('Plugin version route parameters'));
|
|
85
92
|
const PluginTypesParamsSchema = createValidator(objectSchema({
|
|
86
|
-
org: slugField,
|
|
87
|
-
name: slugField,
|
|
88
|
-
version: semverField,
|
|
89
|
-
language: languageEnum,
|
|
90
|
-
}));
|
|
93
|
+
org: slugField().describe('Organization or user namespace'),
|
|
94
|
+
name: slugField().describe('Plugin name'),
|
|
95
|
+
version: semverField().describe('Plugin semantic version'),
|
|
96
|
+
language: languageEnum().describe('Type definition language'),
|
|
97
|
+
}).describe('Plugin type definition route parameters'));
|
|
91
98
|
const packageLookupIdPattern = /^[A-Za-z0-9@._\-/:]+$/;
|
|
99
|
+
const staticLookupPattern = /(^|\/)(README|LICENSE|CHANGELOG|CONTRIBUTING)(\..*)?$|[.](md|json|txt|ya?ml|xml|html?|css|js|map|ico|png|jpe?g|svg|webp|gif)$/i;
|
|
92
100
|
const PackageLookupParamsSchema = createValidator(objectSchema({
|
|
93
|
-
language: languageEnum,
|
|
94
|
-
'*': av.string().minLength(1).maxLength(300).pattern(packageLookupIdPattern.source)
|
|
95
|
-
|
|
101
|
+
language: languageEnum().describe('Package language to resolve'),
|
|
102
|
+
'*': av.string().minLength(1).maxLength(300).pattern(packageLookupIdPattern.source)
|
|
103
|
+
.describe('Package lookup identifier, including scoped package names and encoded path segments'),
|
|
104
|
+
}).describe('Package lookup route parameters'), {
|
|
96
105
|
normalize: (input) => mapObjectInput(input, (value) => {
|
|
97
106
|
const packageId = value['*'];
|
|
98
107
|
if (typeof packageId !== 'string') {
|
|
@@ -114,13 +123,16 @@ const PackageLookupParamsSchema = createValidator(objectSchema({
|
|
|
114
123
|
});
|
|
115
124
|
// ---- Query string schemas ----
|
|
116
125
|
const BrowseQuerySchema = createValidator(objectSchema({
|
|
117
|
-
page: av.optional(av.int32().coerce({ from: 'string' }).min(1).max(10000))
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
page: av.optional(av.int32().coerce({ from: 'string' }).min(1).max(10000))
|
|
127
|
+
.describe('One-based page number for browse results'),
|
|
128
|
+
query: av.optional(safeString(200)).describe('Search text used to filter browse results'),
|
|
129
|
+
category: av.optional(categoryEnum()).describe('Optional plugin category filter'),
|
|
130
|
+
language: av.optional(languageEnum()).describe('Optional plugin language filter'),
|
|
131
|
+
limit: av.optional(av.int32().coerce({ from: 'string' }).min(1).max(100))
|
|
132
|
+
.describe('Maximum number of results to return'),
|
|
133
|
+
offset: av.optional(av.int32().coerce({ from: 'string' }).min(0).max(100000))
|
|
134
|
+
.describe('Zero-based result offset for pagination'),
|
|
135
|
+
}).describe('Plugin browse query parameters'), {
|
|
124
136
|
normalize: (input) => mapObjectInput(input, (value) => ({
|
|
125
137
|
...value,
|
|
126
138
|
query: emptyStringToUndefined(value.query),
|
|
@@ -129,92 +141,98 @@ const BrowseQuerySchema = createValidator(objectSchema({
|
|
|
129
141
|
})),
|
|
130
142
|
});
|
|
131
143
|
const VersionsQuerySchema = createValidator(objectSchema({
|
|
132
|
-
majorMinor: av.optional(av.string().maxLength(11).pattern(majorMinorPattern.source))
|
|
133
|
-
|
|
144
|
+
majorMinor: av.optional(av.string().maxLength(11).pattern(majorMinorPattern.source))
|
|
145
|
+
.describe('Optional major.minor version filter'),
|
|
146
|
+
}).describe('Plugin versions query parameters'));
|
|
134
147
|
const MatchQuerySchema = createValidator(objectSchema({
|
|
135
|
-
version: av.string().minLength(1).maxLength(20).pattern(safeAscii.source)
|
|
136
|
-
|
|
148
|
+
version: av.string().minLength(1).maxLength(20).pattern(safeAscii.source)
|
|
149
|
+
.describe('Version or version range to match'),
|
|
150
|
+
}).describe('Plugin version match query parameters'));
|
|
137
151
|
const DocsQuerySchema = createValidator(objectSchema({
|
|
138
|
-
index: av.optional(av.int32().coerce({ from: 'string' }).min(0).max(100))
|
|
139
|
-
|
|
152
|
+
index: av.optional(av.int32().coerce({ from: 'string' }).min(0).max(100))
|
|
153
|
+
.describe('Zero-based documentation file index'),
|
|
154
|
+
}).describe('Plugin documentation query parameters'));
|
|
140
155
|
// ---- EventSchemaExport validation (parsed from the JSON string clients send) ----
|
|
141
|
-
const
|
|
156
|
+
const anyValiDocumentSchema = () => av.record(av.unknown().describe('AnyVali document property value'))
|
|
157
|
+
.describe('AnyVali schema document');
|
|
142
158
|
const EventExportEntrySchema = objectSchema({
|
|
143
|
-
type: av.enum_(['fire-and-forget', 'returnable', 'broadcast'])
|
|
159
|
+
type: av.enum_(['fire-and-forget', 'returnable', 'broadcast'])
|
|
160
|
+
.describe('Event delivery type'),
|
|
144
161
|
category: av.enum_([
|
|
145
162
|
'emitEvents', 'onEvents',
|
|
146
163
|
'emitReturnableEvents', 'onReturnableEvents',
|
|
147
164
|
'emitBroadcast', 'onBroadcast',
|
|
148
|
-
]),
|
|
149
|
-
description: av.optional(av.string().maxLength(1000)),
|
|
150
|
-
defaultTimeout: av.optional(av.int32().min(0).max(300)),
|
|
151
|
-
inputSchema:
|
|
152
|
-
outputSchema: av.nullable(
|
|
153
|
-
});
|
|
165
|
+
]).describe('Event schema registry category'),
|
|
166
|
+
description: av.optional(av.string().maxLength(1000)).describe('Human-readable event description'),
|
|
167
|
+
defaultTimeout: av.optional(av.int32().min(0).max(300)).describe('Default returnable event timeout in seconds'),
|
|
168
|
+
inputSchema: anyValiDocumentSchema().describe('AnyVali document describing the event input payload'),
|
|
169
|
+
outputSchema: av.nullable(anyValiDocumentSchema()).describe('AnyVali document describing the event output payload, or null when there is no output'),
|
|
170
|
+
}).describe('Exported plugin event definition');
|
|
154
171
|
const EventSchemaExportObjectSchema = objectSchema({
|
|
155
|
-
pluginName: safeStringRequired(1, 200),
|
|
156
|
-
version: semverField,
|
|
157
|
-
events: av.record(EventExportEntrySchema).default({}),
|
|
158
|
-
capabilities: av.optional(av.unknown()),
|
|
172
|
+
pluginName: safeStringRequired(1, 200).describe('Plugin name that produced the exported event schema'),
|
|
173
|
+
version: semverField().describe('Plugin schema semantic version'),
|
|
174
|
+
events: av.record(EventExportEntrySchema).default({}).describe('Map of event names to exported event definitions'),
|
|
175
|
+
capabilities: av.optional(av.unknown()).describe('Optional plugin capability metadata'),
|
|
159
176
|
dependencies: av.optional(av.array(objectSchema({
|
|
160
|
-
id: av.string().minLength(1).maxLength(200),
|
|
161
|
-
version: safeStringRequired(1, 50),
|
|
162
|
-
})).maxItems(100)),
|
|
163
|
-
});
|
|
177
|
+
id: av.string().minLength(1).maxLength(200).describe('Dependency plugin ID'),
|
|
178
|
+
version: safeStringRequired(1, 50).describe('Dependency version constraint'),
|
|
179
|
+
}).describe('Generated client dependency')).maxItems(100)).describe('Plugins referenced by generated client imports'),
|
|
180
|
+
}).describe('Exported plugin event schema object');
|
|
164
181
|
// ---- Publish body schema ----
|
|
165
182
|
const AuthorSchema = av.union([
|
|
166
|
-
safeString(200),
|
|
183
|
+
safeString(200).describe('Author name as a string'),
|
|
167
184
|
objectSchema({
|
|
168
|
-
name: safeStringRequired(1, 200),
|
|
169
|
-
email: av.optional(av.string().maxLength(200).format('email')),
|
|
170
|
-
url: av.optional(av.string().maxLength(500).format('url')),
|
|
171
|
-
}),
|
|
172
|
-
]);
|
|
185
|
+
name: safeStringRequired(1, 200).describe('Author display name'),
|
|
186
|
+
email: av.optional(av.string().maxLength(200).format('email')).describe('Author email address'),
|
|
187
|
+
url: av.optional(av.string().maxLength(500).format('url')).describe('Author homepage URL'),
|
|
188
|
+
}).describe('Author object'),
|
|
189
|
+
]).describe('Author value, either a string or an object with name, email, and URL fields');
|
|
173
190
|
const PublishBodyObjectSchema = objectSchema({
|
|
174
|
-
org: av.string().minLength(1).maxLength(100).pattern(slugPattern.source),
|
|
175
|
-
name: av.string().minLength(1).maxLength(100).pattern(packageNamePattern.source),
|
|
176
|
-
version: semverField,
|
|
177
|
-
language: languageEnum,
|
|
191
|
+
org: av.string().minLength(1).maxLength(100).pattern(slugPattern.source).describe('Organization or user namespace'),
|
|
192
|
+
name: av.string().minLength(1).maxLength(100).pattern(packageNamePattern.source).describe('Plugin package name'),
|
|
193
|
+
version: semverField().describe('Published plugin semantic version'),
|
|
194
|
+
language: languageEnum().describe('Published plugin implementation language'),
|
|
178
195
|
metadata: objectSchema({
|
|
179
|
-
displayName: safeStringRequired(1, 200),
|
|
180
|
-
description: av.string().minLength(1).maxLength(1000),
|
|
181
|
-
category: categoryEnum,
|
|
182
|
-
tags: av.array(safeString(50)).maxItems(30),
|
|
183
|
-
author: av.optional(AuthorSchema),
|
|
184
|
-
license: av.optional(safeString(50)),
|
|
185
|
-
homepage: av.optional(av.string().maxLength(500).format('url')),
|
|
186
|
-
repository: av.optional(safeString(500)),
|
|
187
|
-
}),
|
|
188
|
-
eventSchema: EventSchemaExportObjectSchema,
|
|
189
|
-
capabilities: av.optional(av.unknown()),
|
|
190
|
-
configSchema: av.optional(
|
|
196
|
+
displayName: safeStringRequired(1, 200).describe('Human-readable plugin name'),
|
|
197
|
+
description: av.string().minLength(1).maxLength(1000).describe('Short plugin description'),
|
|
198
|
+
category: categoryEnum().describe('Plugin category'),
|
|
199
|
+
tags: av.array(safeString(50)).maxItems(30).describe('Searchable plugin tags'),
|
|
200
|
+
author: av.optional(AuthorSchema).describe('Plugin author, accepting npm-style string or object formats'),
|
|
201
|
+
license: av.optional(safeString(50)).describe('License identifier'),
|
|
202
|
+
homepage: av.optional(av.string().maxLength(500).format('url')).describe('Plugin homepage or documentation URL'),
|
|
203
|
+
repository: av.optional(safeString(500)).describe('Source repository URL or identifier'),
|
|
204
|
+
}).describe('Plugin metadata supplied by the publisher'),
|
|
205
|
+
eventSchema: EventSchemaExportObjectSchema.describe('Exported event schema for the published plugin'),
|
|
206
|
+
capabilities: av.optional(av.unknown()).describe('Optional top-level plugin capability metadata'),
|
|
207
|
+
configSchema: av.optional(anyValiDocumentSchema()).describe('AnyVali document describing plugin configuration'),
|
|
191
208
|
typeDefinitions: av.optional(objectSchema({
|
|
192
|
-
nodejs: av.optional(av.string().maxLength(5_000_000)),
|
|
193
|
-
csharp: av.optional(av.string().maxLength(5_000_000)),
|
|
194
|
-
go: av.optional(av.string().maxLength(5_000_000)),
|
|
195
|
-
java: av.optional(av.string().maxLength(5_000_000)),
|
|
196
|
-
})),
|
|
197
|
-
documentation: av.array(av.string().maxLength(1_000_000)).minItems(1).maxItems(20)
|
|
209
|
+
nodejs: av.optional(av.string().maxLength(5_000_000)).describe('TypeScript declaration output'),
|
|
210
|
+
csharp: av.optional(av.string().maxLength(5_000_000)).describe('C# type definition output'),
|
|
211
|
+
go: av.optional(av.string().maxLength(5_000_000)).describe('Go type definition output'),
|
|
212
|
+
java: av.optional(av.string().maxLength(5_000_000)).describe('Java type definition output'),
|
|
213
|
+
}).describe('Generated type definitions by language')).describe('Language-specific generated type definitions'),
|
|
214
|
+
documentation: av.array(av.string().maxLength(1_000_000)).minItems(1).maxItems(20)
|
|
215
|
+
.describe('Markdown documentation files published with the plugin'),
|
|
198
216
|
dependencies: av.optional(av.array(objectSchema({
|
|
199
|
-
id: av.string().minLength(1).maxLength(200).pattern(slugPattern.source),
|
|
200
|
-
version: safeStringRequired(1, 50),
|
|
201
|
-
})).maxItems(100)),
|
|
217
|
+
id: av.string().minLength(1).maxLength(200).pattern(slugPattern.source).describe('Dependency plugin ID'),
|
|
218
|
+
version: safeStringRequired(1, 50).describe('Dependency version constraint'),
|
|
219
|
+
}).describe('Published plugin dependency')).maxItems(100)).describe('Plugins this plugin depends on'),
|
|
202
220
|
package: av.optional(objectSchema({
|
|
203
|
-
nodejs: av.optional(safeString(200)),
|
|
204
|
-
csharp: av.optional(safeString(200)),
|
|
205
|
-
go: av.optional(safeString(200)),
|
|
206
|
-
java: av.optional(safeString(200)),
|
|
207
|
-
python: av.optional(safeString(200)),
|
|
208
|
-
})),
|
|
221
|
+
nodejs: av.optional(safeString(200)).describe('NPM package name'),
|
|
222
|
+
csharp: av.optional(safeString(200)).describe('NuGet package name'),
|
|
223
|
+
go: av.optional(safeString(200)).describe('Go module path'),
|
|
224
|
+
java: av.optional(safeString(200)).describe('Maven coordinates'),
|
|
225
|
+
python: av.optional(safeString(200)).describe('PyPI package name'),
|
|
226
|
+
}).describe('Package identifiers by language')).describe('Language-specific package identifiers'),
|
|
209
227
|
runtime: av.optional(objectSchema({
|
|
210
|
-
nodejs: av.optional(safeString(50)),
|
|
211
|
-
dotnet: av.optional(safeString(50)),
|
|
212
|
-
go: av.optional(safeString(50)),
|
|
213
|
-
java: av.optional(safeString(50)),
|
|
214
|
-
python: av.optional(safeString(50)),
|
|
215
|
-
})),
|
|
216
|
-
visibility: av.optional(visibilityEnum),
|
|
217
|
-
});
|
|
228
|
+
nodejs: av.optional(safeString(50)).describe('Node.js runtime requirement'),
|
|
229
|
+
dotnet: av.optional(safeString(50)).describe('.NET runtime requirement'),
|
|
230
|
+
go: av.optional(safeString(50)).describe('Go runtime requirement'),
|
|
231
|
+
java: av.optional(safeString(50)).describe('Java runtime requirement'),
|
|
232
|
+
python: av.optional(safeString(50)).describe('Python runtime requirement'),
|
|
233
|
+
}).describe('Runtime requirements by language')).describe('Runtime version requirements'),
|
|
234
|
+
visibility: av.optional(visibilityEnum()).describe('Optional plugin visibility override'),
|
|
235
|
+
}).describe('Plugin publish request body');
|
|
218
236
|
function validateAnyValiDocument(value, path) {
|
|
219
237
|
if (!value || typeof value !== 'object') {
|
|
220
238
|
return [{
|
|
@@ -267,6 +285,7 @@ export class RegistryUIServer {
|
|
|
267
285
|
host;
|
|
268
286
|
pageSize;
|
|
269
287
|
uploadDir;
|
|
288
|
+
badgesFileInput;
|
|
270
289
|
badgesFile;
|
|
271
290
|
maxImageUploadBytes;
|
|
272
291
|
imageIndexPath;
|
|
@@ -281,7 +300,7 @@ export class RegistryUIServer {
|
|
|
281
300
|
this.host = host;
|
|
282
301
|
this.pageSize = pageSize;
|
|
283
302
|
this.uploadDir = path.resolve(uploadDir);
|
|
284
|
-
this.
|
|
303
|
+
this.badgesFileInput = badgesFile;
|
|
285
304
|
this.maxImageUploadBytes = maxImageUploadMb * 1024 * 1024;
|
|
286
305
|
this.imageIndexPath = path.join(this.uploadDir, 'images.json');
|
|
287
306
|
this.app = Fastify({
|
|
@@ -368,6 +387,7 @@ export class RegistryUIServer {
|
|
|
368
387
|
this.registryClient = plugin.registryClient;
|
|
369
388
|
this.createTrace = plugin.createTrace.bind(plugin);
|
|
370
389
|
this.pluginCwd = plugin.pluginCwd;
|
|
390
|
+
this.badgesFile = this.resolveBadgesFile(plugin.pluginCwd, plugin.packageCwd);
|
|
371
391
|
const packageJsonPath = path.join(plugin.packageCwd, 'package.json');
|
|
372
392
|
if (fs.existsSync(packageJsonPath)) {
|
|
373
393
|
const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, 'utf-8'));
|
|
@@ -651,6 +671,11 @@ export class RegistryUIServer {
|
|
|
651
671
|
await fsp.writeFile(this.imageIndexPath, JSON.stringify(this.imageIndex, null, 2), 'utf-8');
|
|
652
672
|
}
|
|
653
673
|
async loadBadgeMap(obs) {
|
|
674
|
+
if (!this.badgesFile) {
|
|
675
|
+
this.badgeMap = {};
|
|
676
|
+
obs.log.debug('No badge map file found');
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
654
679
|
try {
|
|
655
680
|
const raw = await fsp.readFile(this.badgesFile, 'utf-8');
|
|
656
681
|
const parsed = JSON.parse(raw);
|
|
@@ -658,11 +683,36 @@ export class RegistryUIServer {
|
|
|
658
683
|
? parsed
|
|
659
684
|
: {};
|
|
660
685
|
}
|
|
661
|
-
catch {
|
|
686
|
+
catch (error) {
|
|
662
687
|
this.badgeMap = {};
|
|
688
|
+
obs.log.warn('Badge map not loaded from {path}: {error}', {
|
|
689
|
+
path: this.badgesFile,
|
|
690
|
+
error: error.message,
|
|
691
|
+
});
|
|
663
692
|
}
|
|
664
693
|
obs.log.debug('Loaded badge map with {count} entries', { count: Object.keys(this.badgeMap).length });
|
|
665
694
|
}
|
|
695
|
+
resolveBadgesFile(pluginCwd, packageCwd) {
|
|
696
|
+
const defaultCandidates = [
|
|
697
|
+
path.resolve(pluginCwd, 'BADGES.json'),
|
|
698
|
+
path.resolve(packageCwd, 'BADGES.json'),
|
|
699
|
+
path.resolve('BADGES.json'),
|
|
700
|
+
];
|
|
701
|
+
if (this.badgesFileInput && path.isAbsolute(this.badgesFileInput)) {
|
|
702
|
+
return [
|
|
703
|
+
this.badgesFileInput,
|
|
704
|
+
...defaultCandidates,
|
|
705
|
+
].find((candidate) => fs.existsSync(candidate));
|
|
706
|
+
}
|
|
707
|
+
const filename = this.badgesFileInput ?? 'BADGES.json';
|
|
708
|
+
const candidates = [
|
|
709
|
+
path.resolve(pluginCwd, filename),
|
|
710
|
+
path.resolve(packageCwd, filename),
|
|
711
|
+
path.resolve(filename),
|
|
712
|
+
...defaultCandidates,
|
|
713
|
+
];
|
|
714
|
+
return candidates.find((candidate) => fs.existsSync(candidate));
|
|
715
|
+
}
|
|
666
716
|
resolvePluginImageUrl(pluginId) {
|
|
667
717
|
const filename = this.imageIndex[pluginId];
|
|
668
718
|
if (!filename)
|
|
@@ -1162,6 +1212,10 @@ a.s:hover{background:#333;border-color:#FB8C00}
|
|
|
1162
1212
|
if (!params)
|
|
1163
1213
|
return;
|
|
1164
1214
|
const packageId = params['*'];
|
|
1215
|
+
if (staticLookupPattern.test(packageId)) {
|
|
1216
|
+
reply.code(404).send({ error: 'Not Found' });
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1165
1219
|
const trace = this.createTrace('ui.package.lookup', {
|
|
1166
1220
|
url: request.url,
|
|
1167
1221
|
method: request.method,
|
|
@@ -1358,6 +1412,10 @@ a.s:hover{background:#333;border-color:#FB8C00}
|
|
|
1358
1412
|
const params = this.validateInput(PluginDetailParamsSchema, request.params, reply);
|
|
1359
1413
|
if (!params)
|
|
1360
1414
|
return;
|
|
1415
|
+
if (staticLookupPattern.test(`${params.org}/${params.name}`)) {
|
|
1416
|
+
reply.code(404).send({ error: 'Not Found' });
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1361
1419
|
const trace = this.createTrace('ui.plugin.detail', {
|
|
1362
1420
|
url: request.url,
|
|
1363
1421
|
method: request.method,
|