@bsb/registry 1.0.3 → 1.1.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.
Files changed (39) hide show
  1. package/README.md +1 -1
  2. package/lib/.bsb/clients/service-bsb-registry.d.ts +348 -982
  3. package/lib/.bsb/clients/service-bsb-registry.d.ts.map +1 -1
  4. package/lib/.bsb/clients/service-bsb-registry.js +287 -291
  5. package/lib/.bsb/clients/service-bsb-registry.js.map +1 -1
  6. package/lib/plugins/service-bsb-registry/auth.d.ts +2 -2
  7. package/lib/plugins/service-bsb-registry/auth.d.ts.map +1 -1
  8. package/lib/plugins/service-bsb-registry/auth.js +5 -9
  9. package/lib/plugins/service-bsb-registry/auth.js.map +1 -1
  10. package/lib/plugins/service-bsb-registry/db/file.d.ts +2 -2
  11. package/lib/plugins/service-bsb-registry/db/file.d.ts.map +1 -1
  12. package/lib/plugins/service-bsb-registry/db/file.js +3 -40
  13. package/lib/plugins/service-bsb-registry/db/file.js.map +1 -1
  14. package/lib/plugins/service-bsb-registry/db/index.d.ts +1 -1
  15. package/lib/plugins/service-bsb-registry/db/index.d.ts.map +1 -1
  16. package/lib/plugins/service-bsb-registry/db/index.js +3 -6
  17. package/lib/plugins/service-bsb-registry/db/index.js.map +1 -1
  18. package/lib/plugins/service-bsb-registry/index.d.ts +377 -1020
  19. package/lib/plugins/service-bsb-registry/index.d.ts.map +1 -1
  20. package/lib/plugins/service-bsb-registry/index.js +58 -95
  21. package/lib/plugins/service-bsb-registry/index.js.map +1 -1
  22. package/lib/plugins/service-bsb-registry/types.d.ts +197 -517
  23. package/lib/plugins/service-bsb-registry/types.d.ts.map +1 -1
  24. package/lib/plugins/service-bsb-registry/types.js +164 -167
  25. package/lib/plugins/service-bsb-registry/types.js.map +1 -1
  26. package/lib/plugins/service-bsb-registry-ui/http-server.d.ts +3 -2
  27. package/lib/plugins/service-bsb-registry-ui/http-server.d.ts.map +1 -1
  28. package/lib/plugins/service-bsb-registry-ui/http-server.js +241 -181
  29. package/lib/plugins/service-bsb-registry-ui/http-server.js.map +1 -1
  30. package/lib/plugins/service-bsb-registry-ui/index.d.ts +27 -27
  31. package/lib/plugins/service-bsb-registry-ui/index.d.ts.map +1 -1
  32. package/lib/plugins/service-bsb-registry-ui/index.js +22 -26
  33. package/lib/plugins/service-bsb-registry-ui/index.js.map +1 -1
  34. package/lib/plugins/service-bsb-registry-ui/static/llms.txt +36 -0
  35. package/lib/schemas/service-bsb-registry-ui.json +1 -46
  36. package/lib/schemas/service-bsb-registry-ui.plugin.json +2 -47
  37. package/lib/schemas/service-bsb-registry.json +1903 -1693
  38. package/lib/schemas/service-bsb-registry.plugin.json +1 -47
  39. package/package.json +5 -8
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  /**
3
2
  * Registry UI & API HTTP Server (Event-Driven with Handlebars)
4
3
  *
@@ -6,56 +5,49 @@
6
5
  * using content negotiation (Accept header).
7
6
  * Communicates with registry core via typed BsbRegistryClient.
8
7
  */
9
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- var desc = Object.getOwnPropertyDescriptor(m, k);
12
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
- desc = { enumerable: true, get: function() { return m[k]; } };
14
- }
15
- Object.defineProperty(o, k2, desc);
16
- }) : (function(o, m, k, k2) {
17
- if (k2 === undefined) k2 = k;
18
- o[k2] = m[k];
19
- }));
20
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
- Object.defineProperty(o, "default", { enumerable: true, value: v });
22
- }) : function(o, v) {
23
- o["default"] = v;
24
- });
25
- var __importStar = (this && this.__importStar) || (function () {
26
- var ownKeys = function(o) {
27
- ownKeys = Object.getOwnPropertyNames || function (o) {
28
- var ar = [];
29
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
- return ar;
31
- };
32
- return ownKeys(o);
33
- };
34
- return function (mod) {
35
- if (mod && mod.__esModule) return mod;
36
- var result = {};
37
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
- __setModuleDefault(result, mod);
39
- return result;
8
+ import * as path from 'path';
9
+ import * as fs from 'node:fs';
10
+ import * as fsp from 'node:fs/promises';
11
+ import { pipeline } from 'node:stream/promises';
12
+ import Fastify from 'fastify';
13
+ import fastifyCors from '@fastify/cors';
14
+ import fastifyMultipart from '@fastify/multipart';
15
+ import fastifyStatic from '@fastify/static';
16
+ import fastifyView from '@fastify/view';
17
+ import handlebars from 'handlebars';
18
+ import { marked } from 'marked';
19
+ import * as av from '@anyvali/js';
20
+ function objectSchema(shape) {
21
+ return av.object(shape, { unknownKeys: 'strip' });
22
+ }
23
+ function createValidator(schema, options) {
24
+ return {
25
+ safeParse(input) {
26
+ const normalized = options?.normalize ? options.normalize(input) : input;
27
+ const result = schema.safeParse(normalized);
28
+ if (!result.success) {
29
+ return result;
30
+ }
31
+ const extraIssues = options?.extraIssues?.(result.data) ?? [];
32
+ if (extraIssues.length > 0) {
33
+ return {
34
+ success: false,
35
+ issues: extraIssues,
36
+ };
37
+ }
38
+ return result;
39
+ },
40
40
  };
41
- })();
42
- var __importDefault = (this && this.__importDefault) || function (mod) {
43
- return (mod && mod.__esModule) ? mod : { "default": mod };
44
- };
45
- Object.defineProperty(exports, "__esModule", { value: true });
46
- exports.RegistryUIServer = void 0;
47
- const path = __importStar(require("path"));
48
- const fs = __importStar(require("node:fs"));
49
- const fsp = __importStar(require("node:fs/promises"));
50
- const promises_1 = require("node:stream/promises");
51
- const fastify_1 = __importDefault(require("fastify"));
52
- const cors_1 = __importDefault(require("@fastify/cors"));
53
- const multipart_1 = __importDefault(require("@fastify/multipart"));
54
- const static_1 = __importDefault(require("@fastify/static"));
55
- const view_1 = __importDefault(require("@fastify/view"));
56
- const handlebars_1 = __importDefault(require("handlebars"));
57
- const marked_1 = require("marked");
58
- const zod_1 = require("zod");
41
+ }
42
+ function mapObjectInput(input, transform) {
43
+ if (typeof input !== 'object' || input === null || Array.isArray(input)) {
44
+ return input;
45
+ }
46
+ return transform({ ...input });
47
+ }
48
+ function emptyStringToUndefined(value) {
49
+ return value === '' ? undefined : value;
50
+ }
59
51
  // ============================================================================
60
52
  // Zod Validation Schemas — all external input validated at the boundary
61
53
  // ============================================================================
@@ -70,149 +62,206 @@ const semverPattern = /^\d{1,5}\.\d{1,5}\.\d{1,5}(-[a-zA-Z0-9.]+)?$/;
70
62
  const majorMinorPattern = /^\d{1,5}\.\d{1,5}$/;
71
63
  // Package name: npm-style scoped or unscoped
72
64
  const packageNamePattern = /^(@[a-zA-Z0-9_-]+\/)?[a-zA-Z0-9._-]+$/;
73
- const slugField = zod_1.z.string().min(1).max(100).regex(slugPattern, 'Only alphanumeric, dash, underscore, dot, @, /');
74
- const semverField = zod_1.z.string().min(5).max(50).regex(semverPattern, 'Must be semver (e.g. 1.0.0)');
75
- const languageEnum = zod_1.z.enum(['nodejs', 'csharp', 'go', 'java', 'python']);
76
- const categoryEnum = zod_1.z.enum(['service', 'observable', 'events', 'config']);
77
- const visibilityEnum = zod_1.z.enum(['public', 'private']);
78
- const safeString = (max) => zod_1.z.string().max(max).regex(safeAscii, 'ASCII printable characters only');
79
- const safeStringRequired = (min, max) => zod_1.z.string().min(min).max(max).regex(safeAscii, 'ASCII printable characters only');
80
- const optionalNonEmpty = (schema) => zod_1.z.preprocess((value) => value === '' ? undefined : value, schema.optional());
65
+ const slugField = av.string().minLength(1).maxLength(100).pattern(slugPattern.source);
66
+ const semverField = av.string().minLength(5).maxLength(50).pattern(semverPattern.source);
67
+ const languageEnum = av.enum_(['nodejs', 'csharp', 'go', 'java', 'python']);
68
+ const categoryEnum = av.enum_(['service', 'observable', 'events', 'config']);
69
+ const visibilityEnum = av.enum_(['public', 'private']);
70
+ const safeString = (max) => av.string().maxLength(max).pattern(safeAscii.source);
71
+ const safeStringRequired = (min, max) => av.string().minLength(min).maxLength(max).pattern(safeAscii.source);
81
72
  // ---- Route param schemas ----
82
- const OrgParamsSchema = zod_1.z.object({
73
+ const OrgParamsSchema = createValidator(objectSchema({
83
74
  org: slugField,
84
- });
85
- const PluginDetailParamsSchema = zod_1.z.object({
75
+ }));
76
+ const PluginDetailParamsSchema = createValidator(objectSchema({
86
77
  org: slugField,
87
78
  name: slugField,
88
- });
89
- const PluginVersionParamsSchema = zod_1.z.object({
79
+ }));
80
+ const PluginVersionParamsSchema = createValidator(objectSchema({
90
81
  org: slugField,
91
82
  name: slugField,
92
83
  version: semverField,
93
- });
94
- const PluginTypesParamsSchema = zod_1.z.object({
84
+ }));
85
+ const PluginTypesParamsSchema = createValidator(objectSchema({
95
86
  org: slugField,
96
87
  name: slugField,
97
88
  version: semverField,
98
89
  language: languageEnum,
99
- });
90
+ }));
100
91
  const packageLookupIdPattern = /^[A-Za-z0-9@._\-/:]+$/;
101
- const PackageLookupParamsSchema = zod_1.z.object({
92
+ const PackageLookupParamsSchema = createValidator(objectSchema({
102
93
  language: languageEnum,
103
- '*': zod_1.z.preprocess((value) => {
104
- if (typeof value !== 'string')
94
+ '*': av.string().minLength(1).maxLength(300).pattern(packageLookupIdPattern.source),
95
+ }), {
96
+ normalize: (input) => mapObjectInput(input, (value) => {
97
+ const packageId = value['*'];
98
+ if (typeof packageId !== 'string') {
105
99
  return value;
100
+ }
106
101
  try {
107
- return decodeURIComponent(value).trim();
102
+ return {
103
+ ...value,
104
+ '*': decodeURIComponent(packageId).trim(),
105
+ };
108
106
  }
109
107
  catch {
110
- return '__INVALID_PACKAGE_ID__';
108
+ return {
109
+ ...value,
110
+ '*': '__INVALID_PACKAGE_ID__',
111
+ };
111
112
  }
112
- }, zod_1.z.string().min(1).max(300).regex(packageLookupIdPattern, 'Invalid package identifier')),
113
+ }),
113
114
  });
114
115
  // ---- Query string schemas ----
115
- // Note: z.coerce handles string-to-number conversion for query params
116
- const BrowseQuerySchema = zod_1.z.object({
117
- page: zod_1.z.coerce.number().int().min(1).max(10000).optional(),
118
- query: optionalNonEmpty(safeString(200)),
119
- category: optionalNonEmpty(categoryEnum),
120
- language: optionalNonEmpty(languageEnum),
121
- limit: zod_1.z.coerce.number().int().min(1).max(100).optional(),
122
- offset: zod_1.z.coerce.number().int().min(0).max(100000).optional(),
123
- }).passthrough(); // ignore unexpected query params (referrer tracking etc.)
124
- const VersionsQuerySchema = zod_1.z.object({
125
- majorMinor: zod_1.z.string().max(11).regex(majorMinorPattern, 'Must be major.minor (e.g. 1.0)').optional(),
126
- }).passthrough();
127
- const MatchQuerySchema = zod_1.z.object({
128
- version: zod_1.z.string().min(1).max(20).regex(safeAscii, 'ASCII only'),
129
- }).passthrough();
130
- const DocsQuerySchema = zod_1.z.object({
131
- index: zod_1.z.coerce.number().int().min(0).max(100).optional(),
132
- }).passthrough();
116
+ const BrowseQuerySchema = createValidator(objectSchema({
117
+ page: av.optional(av.int32().coerce({ from: 'string' }).min(1).max(10000)),
118
+ query: av.optional(safeString(200)),
119
+ category: av.optional(categoryEnum),
120
+ language: av.optional(languageEnum),
121
+ limit: av.optional(av.int32().coerce({ from: 'string' }).min(1).max(100)),
122
+ offset: av.optional(av.int32().coerce({ from: 'string' }).min(0).max(100000)),
123
+ }), {
124
+ normalize: (input) => mapObjectInput(input, (value) => ({
125
+ ...value,
126
+ query: emptyStringToUndefined(value.query),
127
+ category: emptyStringToUndefined(value.category),
128
+ language: emptyStringToUndefined(value.language),
129
+ })),
130
+ });
131
+ const VersionsQuerySchema = createValidator(objectSchema({
132
+ majorMinor: av.optional(av.string().maxLength(11).pattern(majorMinorPattern.source)),
133
+ }));
134
+ const MatchQuerySchema = createValidator(objectSchema({
135
+ version: av.string().minLength(1).maxLength(20).pattern(safeAscii.source),
136
+ }));
137
+ const DocsQuerySchema = createValidator(objectSchema({
138
+ index: av.optional(av.int32().coerce({ from: 'string' }).min(0).max(100)),
139
+ }));
133
140
  // ---- EventSchemaExport validation (parsed from the JSON string clients send) ----
134
- const EventExportEntryZod = zod_1.z.object({
135
- type: zod_1.z.enum(['fire-and-forget', 'returnable', 'broadcast']),
136
- category: zod_1.z.enum([
141
+ const AnyValiDocumentSchema = av.record(av.unknown());
142
+ const EventExportEntrySchema = objectSchema({
143
+ type: av.enum_(['fire-and-forget', 'returnable', 'broadcast']),
144
+ category: av.enum_([
137
145
  'emitEvents', 'onEvents',
138
146
  'emitReturnableEvents', 'onReturnableEvents',
139
147
  'emitBroadcast', 'onBroadcast',
140
148
  ]),
141
- description: zod_1.z.string().max(1000).optional(),
142
- defaultTimeout: zod_1.z.number().int().min(0).max(300).optional(),
143
- inputSchema: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
144
- outputSchema: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).nullable(),
145
- }).passthrough();
146
- const EventSchemaExportZod = zod_1.z.object({
149
+ description: av.optional(av.string().maxLength(1000)),
150
+ defaultTimeout: av.optional(av.int32().min(0).max(300)),
151
+ inputSchema: AnyValiDocumentSchema,
152
+ outputSchema: av.nullable(AnyValiDocumentSchema),
153
+ });
154
+ const EventSchemaExportObjectSchema = objectSchema({
147
155
  pluginName: safeStringRequired(1, 200),
148
156
  version: semverField,
149
- events: zod_1.z.record(zod_1.z.string(), EventExportEntryZod).default({}),
150
- capabilities: zod_1.z.unknown().optional(),
151
- dependencies: zod_1.z.array(zod_1.z.object({
152
- id: zod_1.z.string().min(1).max(200),
157
+ events: av.record(EventExportEntrySchema).default({}),
158
+ capabilities: av.optional(av.unknown()),
159
+ dependencies: av.optional(av.array(objectSchema({
160
+ id: av.string().minLength(1).maxLength(200),
153
161
  version: safeStringRequired(1, 50),
154
- })).max(100).optional(),
155
- }).passthrough();
162
+ })).maxItems(100)),
163
+ });
156
164
  // ---- Publish body schema ----
157
- const AuthorSchema = zod_1.z.union([
165
+ const AuthorSchema = av.union([
158
166
  safeString(200),
159
- zod_1.z.object({
167
+ objectSchema({
160
168
  name: safeStringRequired(1, 200),
161
- email: zod_1.z.string().max(200).email().optional(),
162
- url: zod_1.z.string().max(500).url().optional(),
163
- }).strict(),
169
+ email: av.optional(av.string().maxLength(200).format('email')),
170
+ url: av.optional(av.string().maxLength(500).format('url')),
171
+ }),
164
172
  ]);
165
- const PublishBodySchema = zod_1.z.object({
166
- org: zod_1.z.string().min(1).max(100).regex(slugPattern, 'Invalid org name'),
167
- name: zod_1.z.string().min(1).max(100).regex(packageNamePattern, 'Invalid plugin name'),
173
+ 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),
168
176
  version: semverField,
169
177
  language: languageEnum,
170
- metadata: zod_1.z.object({
178
+ metadata: objectSchema({
171
179
  displayName: safeStringRequired(1, 200),
172
- description: zod_1.z.string().min(1).max(1000),
180
+ description: av.string().minLength(1).maxLength(1000),
173
181
  category: categoryEnum,
174
- tags: zod_1.z.array(safeString(50)).max(30),
175
- author: AuthorSchema.optional(),
176
- license: safeString(50).optional(),
177
- homepage: zod_1.z.string().max(500).url().optional(),
178
- repository: zod_1.z.string().max(500).url().optional(),
179
- }).strict(),
180
- eventSchema: EventSchemaExportZod, // parsed object, validated at HTTP boundary
181
- capabilities: zod_1.z.unknown().optional(),
182
- configSchema: zod_1.z.object({
183
- type: zod_1.z.literal('object'),
184
- properties: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
185
- required: zod_1.z.array(zod_1.z.string()).optional(),
186
- description: zod_1.z.string().optional(),
187
- }).passthrough().optional(),
188
- typeDefinitions: zod_1.z.object({
189
- nodejs: zod_1.z.string().max(5_000_000).optional(),
190
- csharp: zod_1.z.string().max(5_000_000).optional(),
191
- go: zod_1.z.string().max(5_000_000).optional(),
192
- java: zod_1.z.string().max(5_000_000).optional(),
193
- }).strict().optional(),
194
- documentation: zod_1.z.array(zod_1.z.string().max(1_000_000)).min(1).max(20),
195
- dependencies: zod_1.z.array(zod_1.z.object({
196
- id: zod_1.z.string().min(1).max(200).regex(slugPattern, 'Invalid plugin ID'),
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(av.string().maxLength(500).format('url')),
187
+ }),
188
+ eventSchema: EventSchemaExportObjectSchema,
189
+ capabilities: av.optional(av.unknown()),
190
+ configSchema: av.optional(AnyValiDocumentSchema),
191
+ 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),
198
+ dependencies: av.optional(av.array(objectSchema({
199
+ id: av.string().minLength(1).maxLength(200).pattern(slugPattern.source),
197
200
  version: safeStringRequired(1, 50),
198
- }).strict()).max(100).optional(),
199
- package: zod_1.z.object({
200
- nodejs: safeString(200).optional(),
201
- csharp: safeString(200).optional(),
202
- go: safeString(200).optional(),
203
- java: safeString(200).optional(),
204
- python: safeString(200).optional(),
205
- }).strict().optional(),
206
- runtime: zod_1.z.object({
207
- nodejs: safeString(50).optional(),
208
- dotnet: safeString(50).optional(),
209
- go: safeString(50).optional(),
210
- java: safeString(50).optional(),
211
- python: safeString(50).optional(),
212
- }).strict().optional(),
213
- visibility: visibilityEnum.optional(),
214
- }).strict();
215
- class RegistryUIServer {
201
+ })).maxItems(100)),
202
+ 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
+ })),
209
+ 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
+ });
218
+ function validateAnyValiDocument(value, path) {
219
+ if (!value || typeof value !== 'object') {
220
+ return [{
221
+ code: 'invalid_type',
222
+ message: 'Expected AnyVali document object',
223
+ path,
224
+ expected: 'object',
225
+ received: typeof value,
226
+ }];
227
+ }
228
+ try {
229
+ av.importSchema(value);
230
+ return [];
231
+ }
232
+ catch (error) {
233
+ const issues = error instanceof av.ValidationError
234
+ ? error.issues
235
+ : [{
236
+ code: 'invalid_schema',
237
+ message: error instanceof Error ? error.message : 'Invalid AnyVali document',
238
+ path: [],
239
+ }];
240
+ return issues.map((issue) => ({
241
+ ...issue,
242
+ path: [...path, ...issue.path],
243
+ }));
244
+ }
245
+ }
246
+ function validateEventSchemaExportDocuments(value, path = []) {
247
+ const nestedIssues = [];
248
+ for (const [eventName, eventDef] of Object.entries(value.events)) {
249
+ nestedIssues.push(...validateAnyValiDocument(eventDef.inputSchema, [...path, 'events', eventName, 'inputSchema']));
250
+ if (eventDef.outputSchema !== null) {
251
+ nestedIssues.push(...validateAnyValiDocument(eventDef.outputSchema, [...path, 'events', eventName, 'outputSchema']));
252
+ }
253
+ }
254
+ return nestedIssues;
255
+ }
256
+ const PublishBodySchema = createValidator(PublishBodyObjectSchema, {
257
+ extraIssues: (value) => [
258
+ ...validateEventSchemaExportDocuments(value.eventSchema, ['eventSchema']),
259
+ ...(value.configSchema !== undefined
260
+ ? validateAnyValiDocument(value.configSchema, ['configSchema'])
261
+ : []),
262
+ ],
263
+ });
264
+ export class RegistryUIServer {
216
265
  app;
217
266
  port;
218
267
  host;
@@ -225,6 +274,7 @@ class RegistryUIServer {
225
274
  badgeMap = {};
226
275
  registryClient;
227
276
  createTrace;
277
+ pluginCwd;
228
278
  constructor(port, host, pageSize, uploadDir, badgesFile, maxImageUploadMb) {
229
279
  this.port = port;
230
280
  this.host = host;
@@ -233,7 +283,7 @@ class RegistryUIServer {
233
283
  this.badgesFile = path.resolve(badgesFile);
234
284
  this.maxImageUploadBytes = maxImageUploadMb * 1024 * 1024;
235
285
  this.imageIndexPath = path.join(this.uploadDir, 'images.json');
236
- this.app = (0, fastify_1.default)({
286
+ this.app = Fastify({
237
287
  logger: false,
238
288
  disableRequestLogging: true,
239
289
  bodyLimit: 10 * 1024 * 1024, // 10MB max request body
@@ -307,7 +357,7 @@ class RegistryUIServer {
307
357
  registerHandlebarsHelpers() {
308
358
  const helpers = this.getHandlebarsHelpers();
309
359
  for (const [name, fn] of Object.entries(helpers)) {
310
- handlebars_1.default.registerHelper(name, fn);
360
+ handlebars.registerHelper(name, fn);
311
361
  }
312
362
  }
313
363
  async init(obs, plugin) {
@@ -316,9 +366,10 @@ class RegistryUIServer {
316
366
  // Bind plugin context to this server instance
317
367
  this.registryClient = plugin.registryClient;
318
368
  this.createTrace = plugin.createTrace.bind(plugin);
369
+ this.pluginCwd = plugin.pluginCwd;
319
370
  // Register CORS
320
371
  const corsSpan = obs.startSpan('register.cors');
321
- await this.app.register(cors_1.default, {
372
+ await this.app.register(fastifyCors, {
322
373
  origin: '*',
323
374
  methods: ['GET', 'POST', 'OPTIONS'],
324
375
  allowedHeaders: ['Content-Type', 'Authorization', 'Accept'],
@@ -326,7 +377,7 @@ class RegistryUIServer {
326
377
  corsSpan.end();
327
378
  // Register multipart upload handling for plugin images
328
379
  const multipartSpan = obs.startSpan('register.multipart');
329
- await this.app.register(multipart_1.default, {
380
+ await this.app.register(fastifyMultipart, {
330
381
  limits: {
331
382
  fileSize: this.maxImageUploadBytes,
332
383
  files: 1,
@@ -340,13 +391,13 @@ class RegistryUIServer {
340
391
  // Register static file serving
341
392
  const staticSpan = obs.startSpan('register.static');
342
393
  const staticPath = path.join(plugin.pluginCwd, 'static');
343
- await this.app.register(static_1.default, {
394
+ await this.app.register(fastifyStatic, {
344
395
  root: staticPath,
345
396
  prefix: '/static/',
346
397
  index: false,
347
398
  });
348
399
  await fsp.mkdir(this.uploadDir, { recursive: true });
349
- await this.app.register(static_1.default, {
400
+ await this.app.register(fastifyStatic, {
350
401
  root: this.uploadDir,
351
402
  prefix: '/images/',
352
403
  decorateReply: false,
@@ -358,9 +409,9 @@ class RegistryUIServer {
358
409
  const viewSpan = obs.startSpan('register.handlebars');
359
410
  const templatesPath = path.join(plugin.pluginCwd, 'templates');
360
411
  obs.log.debug('Registering Handlebars templates from {path}', { path: templatesPath });
361
- await this.app.register(view_1.default, {
412
+ await this.app.register(fastifyView, {
362
413
  engine: {
363
- handlebars: handlebars_1.default,
414
+ handlebars,
364
415
  },
365
416
  root: templatesPath,
366
417
  layout: 'layouts/main.hbs',
@@ -426,6 +477,16 @@ class RegistryUIServer {
426
477
  this.app.get('/', async (request, reply) => {
427
478
  return this.handleHome(request, reply);
428
479
  });
480
+ // LLM guidance
481
+ this.app.get('/llms.txt', async (_request, reply) => {
482
+ const llmsPath = path.join(this.pluginCwd, 'static', 'llms.txt');
483
+ if (fs.existsSync(llmsPath)) {
484
+ reply.type('text/plain').send(fs.readFileSync(llmsPath, 'utf8'));
485
+ }
486
+ else {
487
+ reply.code(404).type('text/plain').send('Not found');
488
+ }
489
+ });
429
490
  // Browse + search plugins (combined list/search)
430
491
  this.app.get('/plugins', async (request, reply) => {
431
492
  return this.handleBrowse(request, reply);
@@ -534,24 +595,24 @@ class RegistryUIServer {
534
595
  return request.headers.accept?.includes('application/json') === true;
535
596
  }
536
597
  /**
537
- * Validate input against a Zod schema. Returns parsed data on success,
598
+ * Validate input against an AnyVali-backed schema. Returns parsed data on success,
538
599
  * or sends a 400 response and returns null on failure.
539
600
  */
540
601
  validateInput(schema, data, reply) {
541
602
  const result = schema.safeParse(data);
542
- if (!result.success) {
543
- const issues = result.error.issues.map((issue) => ({
544
- path: issue.path.join('.'),
545
- message: issue.message,
546
- }));
547
- reply.code(400).send({
548
- error: 'Validation Error',
549
- code: 'INVALID_INPUT',
550
- details: issues,
551
- });
552
- return null;
603
+ if (result.success) {
604
+ return result.data;
553
605
  }
554
- return result.data;
606
+ const issues = result.issues.map((issue) => ({
607
+ path: issue.path.join('.'),
608
+ message: issue.message,
609
+ }));
610
+ reply.code(400).send({
611
+ error: 'Validation Error',
612
+ code: 'INVALID_INPUT',
613
+ details: issues,
614
+ });
615
+ return null;
555
616
  }
556
617
  /** Render an error page (HTML) or send JSON error depending on Accept header */
557
618
  async renderError(request, reply, statusCode, title, message) {
@@ -969,13 +1030,13 @@ a.s:hover{background:#333;border-color:#FB8C00}
969
1030
  * External links open in a new tab. Raw HTML in the source is escaped.
970
1031
  */
971
1032
  renderMarkdown(md) {
972
- const renderer = new marked_1.marked.Renderer();
1033
+ const renderer = new marked.Renderer();
973
1034
  // Open external links in new tab with noopener
974
1035
  renderer.link = ({ href, title, text }) => {
975
1036
  const titleAttr = title ? ` title="${title}"` : '';
976
1037
  return `<a href="${href}"${titleAttr} target="_blank" rel="noopener noreferrer">${text}</a>`;
977
1038
  };
978
- return marked_1.marked.parse(md, {
1039
+ return marked.parse(md, {
979
1040
  renderer,
980
1041
  gfm: true,
981
1042
  breaks: false,
@@ -1706,7 +1767,7 @@ a.s:hover{background:#333;border-color:#FB8C00}
1706
1767
  const safeFileName = `${params.org}__${params.name}${ext}`;
1707
1768
  const outputPath = path.join(this.uploadDir, safeFileName);
1708
1769
  await fsp.mkdir(this.uploadDir, { recursive: true });
1709
- await (0, promises_1.pipeline)(filePart.file, fs.createWriteStream(outputPath));
1770
+ await pipeline(filePart.file, fs.createWriteStream(outputPath));
1710
1771
  if (filePart.file.truncated) {
1711
1772
  await fsp.unlink(outputPath).catch(() => { });
1712
1773
  reply.code(413).send({
@@ -1821,5 +1882,4 @@ a.s:hover{background:#333;border-color:#FB8C00}
1821
1882
  this.app.close();
1822
1883
  }
1823
1884
  }
1824
- exports.RegistryUIServer = RegistryUIServer;
1825
1885
  //# sourceMappingURL=http-server.js.map