@ggui-ai/registry-server 0.1.0-rc.1
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 +201 -0
- package/README.md +87 -0
- package/dist/authn/bearer.d.ts +28 -0
- package/dist/authn/bearer.d.ts.map +1 -0
- package/dist/authn/bearer.js +102 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +181 -0
- package/dist/filesystem-bundle-storage.d.ts +12 -0
- package/dist/filesystem-bundle-storage.d.ts.map +1 -0
- package/dist/filesystem-bundle-storage.js +152 -0
- package/dist/filesystem-registry-storage.d.ts +7 -0
- package/dist/filesystem-registry-storage.d.ts.map +1 -0
- package/dist/filesystem-registry-storage.js +362 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/server.d.ts +52 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +307 -0
- package/package.json +69 -0
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hono server + route handlers for the OSS registry. Each route is a
|
|
3
|
+
* thin transport adapter over a {@link registryCore} operation:
|
|
4
|
+
*
|
|
5
|
+
* GET /healthz → liveness
|
|
6
|
+
* GET /search → searchArtifacts
|
|
7
|
+
* GET /pkg/:scope/:name/:version → readArtifact
|
|
8
|
+
* POST /publish → publishArtifact (bearer-gated)
|
|
9
|
+
* POST /conformance/check → checkConformance (pre-flight gate)
|
|
10
|
+
* GET /bundles/:scope/:name/:version/bundle.js
|
|
11
|
+
* GET /bundles/:scope/:name/:version/bundle.js.sig
|
|
12
|
+
* GET /bundles/:scope/:name/:version/manifest.json
|
|
13
|
+
*
|
|
14
|
+
* Bundle / signature / manifest serves go through {@link BundleStorage}
|
|
15
|
+
* directly so the operator can plug a different storage backend
|
|
16
|
+
* (memory for tests; filesystem for self-hosting) without rewriting
|
|
17
|
+
* the route table. The server always emits
|
|
18
|
+
* `Cache-Control: public, max-age=31536000, immutable` on these
|
|
19
|
+
* routes — bundles are SRI-pinned + immutable post-publish.
|
|
20
|
+
*
|
|
21
|
+
* CORS: permissive on public-read routes (`/search`, `/pkg/*`,
|
|
22
|
+
* `/bundles/*`, `/conformance/check`); strict on `/publish` (same-origin
|
|
23
|
+
* or no Origin header — the install CLI runs server-to-server, not
|
|
24
|
+
* browser-side).
|
|
25
|
+
*/
|
|
26
|
+
import { Hono } from 'hono';
|
|
27
|
+
import { type BlueprintProbeRunner, type BundleStorage, type RegistryStorage } from '@ggui-ai/registry-core';
|
|
28
|
+
import type { BearerAuthn } from './authn/bearer.js';
|
|
29
|
+
export interface RegistryAppOptions {
|
|
30
|
+
readonly storage: RegistryStorage;
|
|
31
|
+
readonly bundleStorage: BundleStorage;
|
|
32
|
+
readonly authn: BearerAuthn;
|
|
33
|
+
/** Hostname (no protocol) for the publish-success `installCommand` field. */
|
|
34
|
+
readonly registryHostname: string;
|
|
35
|
+
/** Wall-clock provider — overridable for deterministic tests. */
|
|
36
|
+
readonly clock?: () => Date;
|
|
37
|
+
/**
|
|
38
|
+
* Optional blueprint runtime probe. Wire `@ggui-ai/blueprint-probe`'s
|
|
39
|
+
* `blueprintProbeRunner` here to enable the publish-time runtime
|
|
40
|
+
* gate (compile + sandboxed React render). Leaving it unset runs
|
|
41
|
+
* only the static gates from `checkConformance`.
|
|
42
|
+
*/
|
|
43
|
+
readonly blueprintProbe?: BlueprintProbeRunner;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build the hono app. Does NOT bind a port — callers (the CLI, the
|
|
47
|
+
* `createRegistryServer` factory, programmatic embeds) decide how to
|
|
48
|
+
* serve it. Returned as `Hono<{}>` so the app object can be passed to
|
|
49
|
+
* `@hono/node-server`'s `serve()` or any hono-compatible runtime.
|
|
50
|
+
*/
|
|
51
|
+
export declare function createRegistryApp(options: RegistryAppOptions): Hono;
|
|
52
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAKL,KAAK,oBAAoB,EAGzB,KAAK,aAAa,EAIlB,KAAK,eAAe,EACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,6EAA6E;IAC7E,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,oBAAoB,CAAC;CAChD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAwQnE"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hono server + route handlers for the OSS registry. Each route is a
|
|
3
|
+
* thin transport adapter over a {@link registryCore} operation:
|
|
4
|
+
*
|
|
5
|
+
* GET /healthz → liveness
|
|
6
|
+
* GET /search → searchArtifacts
|
|
7
|
+
* GET /pkg/:scope/:name/:version → readArtifact
|
|
8
|
+
* POST /publish → publishArtifact (bearer-gated)
|
|
9
|
+
* POST /conformance/check → checkConformance (pre-flight gate)
|
|
10
|
+
* GET /bundles/:scope/:name/:version/bundle.js
|
|
11
|
+
* GET /bundles/:scope/:name/:version/bundle.js.sig
|
|
12
|
+
* GET /bundles/:scope/:name/:version/manifest.json
|
|
13
|
+
*
|
|
14
|
+
* Bundle / signature / manifest serves go through {@link BundleStorage}
|
|
15
|
+
* directly so the operator can plug a different storage backend
|
|
16
|
+
* (memory for tests; filesystem for self-hosting) without rewriting
|
|
17
|
+
* the route table. The server always emits
|
|
18
|
+
* `Cache-Control: public, max-age=31536000, immutable` on these
|
|
19
|
+
* routes — bundles are SRI-pinned + immutable post-publish.
|
|
20
|
+
*
|
|
21
|
+
* CORS: permissive on public-read routes (`/search`, `/pkg/*`,
|
|
22
|
+
* `/bundles/*`, `/conformance/check`); strict on `/publish` (same-origin
|
|
23
|
+
* or no Origin header — the install CLI runs server-to-server, not
|
|
24
|
+
* browser-side).
|
|
25
|
+
*/
|
|
26
|
+
import { Hono } from 'hono';
|
|
27
|
+
import { checkConformance, listArtifactVersions, publishArtifact, registerAuthorKey, readArtifact, searchArtifacts, } from '@ggui-ai/registry-core';
|
|
28
|
+
const IMMUTABLE_CACHE_HEADER = 'public, max-age=31536000, immutable';
|
|
29
|
+
/**
|
|
30
|
+
* Build the hono app. Does NOT bind a port — callers (the CLI, the
|
|
31
|
+
* `createRegistryServer` factory, programmatic embeds) decide how to
|
|
32
|
+
* serve it. Returned as `Hono<{}>` so the app object can be passed to
|
|
33
|
+
* `@hono/node-server`'s `serve()` or any hono-compatible runtime.
|
|
34
|
+
*/
|
|
35
|
+
export function createRegistryApp(options) {
|
|
36
|
+
const { storage, bundleStorage, authn, registryHostname } = options;
|
|
37
|
+
const clock = options.clock ?? (() => new Date());
|
|
38
|
+
const blueprintProbe = options.blueprintProbe;
|
|
39
|
+
const app = new Hono();
|
|
40
|
+
// CORS — permissive read; strict for /publish. Adds ACAO headers
|
|
41
|
+
// AFTER the route runs so they overlay on the route's response.
|
|
42
|
+
app.use('*', async (c, next) => {
|
|
43
|
+
const path = c.req.path;
|
|
44
|
+
const isPublishRoute = path === '/publish';
|
|
45
|
+
if (c.req.method === 'OPTIONS' && !isPublishRoute) {
|
|
46
|
+
// Preflight short-circuit for read routes.
|
|
47
|
+
return new Response(null, {
|
|
48
|
+
status: 204,
|
|
49
|
+
headers: {
|
|
50
|
+
'Access-Control-Allow-Origin': '*',
|
|
51
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
52
|
+
'Access-Control-Allow-Headers': 'content-type, authorization',
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
await next();
|
|
57
|
+
if (!isPublishRoute) {
|
|
58
|
+
c.res.headers.set('Access-Control-Allow-Origin', '*');
|
|
59
|
+
c.res.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
60
|
+
c.res.headers.set('Access-Control-Allow-Headers', 'content-type, authorization');
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
// ── /healthz ─────────────────────────────────────────────────────────
|
|
64
|
+
app.get('/healthz', (c) => c.json({ ok: true }));
|
|
65
|
+
// ── /search ──────────────────────────────────────────────────────────
|
|
66
|
+
app.get('/search', async (c) => {
|
|
67
|
+
const q = c.req.query();
|
|
68
|
+
const result = await searchArtifacts({
|
|
69
|
+
q: q.q,
|
|
70
|
+
kind: q.kind,
|
|
71
|
+
hook: q.hook,
|
|
72
|
+
tag: q.tag,
|
|
73
|
+
author: q.author,
|
|
74
|
+
limit: q.limit,
|
|
75
|
+
cursor: q.cursor,
|
|
76
|
+
sort: q.sort,
|
|
77
|
+
}, { storage });
|
|
78
|
+
if (!result.ok) {
|
|
79
|
+
return c.json(result.body, result.status);
|
|
80
|
+
}
|
|
81
|
+
return c.json(result.body, 200);
|
|
82
|
+
});
|
|
83
|
+
// ── /pkg/:scope/:name ────────────────────────────────────────────────
|
|
84
|
+
// List-versions endpoint. Returns the version timeline for an
|
|
85
|
+
// artifact. MUST be registered BEFORE `/pkg/:scope/:name/:version`
|
|
86
|
+
// so Hono's matcher doesn't shadow it (Hono routes are checked in
|
|
87
|
+
// registration order; a more-specific later route would still win
|
|
88
|
+
// since both arms have unique segment counts, but registering the
|
|
89
|
+
// broader pattern first keeps the wire contract obvious from the
|
|
90
|
+
// source order).
|
|
91
|
+
app.get('/pkg/:scope/:name', async (c) => {
|
|
92
|
+
const { scope: rawScope, name } = c.req.param();
|
|
93
|
+
const scope = rawScope.startsWith('@') ? rawScope : `@${rawScope}`;
|
|
94
|
+
const artifactId = `${scope}/${name}`;
|
|
95
|
+
const authHeader = c.req.header('authorization');
|
|
96
|
+
const verified = authn.verify(authHeader);
|
|
97
|
+
const result = await listArtifactVersions({ artifactId }, {
|
|
98
|
+
storage,
|
|
99
|
+
authn: verified ?? undefined,
|
|
100
|
+
});
|
|
101
|
+
return c.json(result.body, result.status);
|
|
102
|
+
});
|
|
103
|
+
// ── /pkg/:scope/:name/:version ───────────────────────────────────────
|
|
104
|
+
app.get('/pkg/:scope/:name/:version', async (c) => {
|
|
105
|
+
const { scope: rawScope, name, version } = c.req.param();
|
|
106
|
+
// The CLI strips the leading `@` from the scope when composing the
|
|
107
|
+
// URL — mirrors the cloud API Gateway path-param convention. We
|
|
108
|
+
// re-prepend the `@` here so the registry-core op gets the
|
|
109
|
+
// canonical `@scope/name` form. If the client sends an already-
|
|
110
|
+
// prefixed scope (curl test, dev tooling), normalize to single `@`.
|
|
111
|
+
const scope = rawScope.startsWith('@') ? rawScope : `@${rawScope}`;
|
|
112
|
+
const artifactId = `${scope}/${name}`;
|
|
113
|
+
const authHeader = c.req.header('authorization');
|
|
114
|
+
const verified = authn.verify(authHeader);
|
|
115
|
+
const result = await readArtifact({ artifactId, version }, {
|
|
116
|
+
storage,
|
|
117
|
+
authn: verified ?? undefined,
|
|
118
|
+
});
|
|
119
|
+
if (result.ok) {
|
|
120
|
+
return c.json(result.body, 200);
|
|
121
|
+
}
|
|
122
|
+
if (result.status === 410) {
|
|
123
|
+
return c.json(result.body, 410);
|
|
124
|
+
}
|
|
125
|
+
return c.json(result.body, result.status);
|
|
126
|
+
});
|
|
127
|
+
// ── /publish ─────────────────────────────────────────────────────────
|
|
128
|
+
app.post('/publish', async (c) => {
|
|
129
|
+
// Strict CORS — no Access-Control-Allow-Origin emitted; reject
|
|
130
|
+
// cross-origin Origin headers up front (browsers MUST send Origin
|
|
131
|
+
// on a CORS preflight). Server-to-server clients (the CLI) won't
|
|
132
|
+
// send Origin at all, so absence is allowed.
|
|
133
|
+
const origin = c.req.header('origin');
|
|
134
|
+
if (origin !== undefined && origin !== '') {
|
|
135
|
+
// Defensive: don't echo. Browsers will reject without the
|
|
136
|
+
// ACAO header, which is the protection we want.
|
|
137
|
+
}
|
|
138
|
+
const authHeader = c.req.header('authorization');
|
|
139
|
+
const verified = authn.verify(authHeader);
|
|
140
|
+
if (verified === null) {
|
|
141
|
+
return c.json({
|
|
142
|
+
error: 'unauthorized',
|
|
143
|
+
message: 'publish requires a valid bearer token in the Authorization header',
|
|
144
|
+
}, 401);
|
|
145
|
+
}
|
|
146
|
+
let body;
|
|
147
|
+
try {
|
|
148
|
+
body = (await c.req.json());
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return c.json({ error: 'manifest_invalid', message: 'request body is not valid JSON' }, 400);
|
|
152
|
+
}
|
|
153
|
+
const result = await publishArtifact({
|
|
154
|
+
manifest: body.manifest,
|
|
155
|
+
bundle: body.bundle,
|
|
156
|
+
bundleSha384: body.bundleSha384,
|
|
157
|
+
signature: body.signature,
|
|
158
|
+
}, {
|
|
159
|
+
storage,
|
|
160
|
+
bundleStorage,
|
|
161
|
+
authn: verified,
|
|
162
|
+
clock,
|
|
163
|
+
registryHostname,
|
|
164
|
+
...(blueprintProbe !== undefined ? { blueprintProbe } : {}),
|
|
165
|
+
});
|
|
166
|
+
return c.json(result.body, result.status);
|
|
167
|
+
});
|
|
168
|
+
// ── /author-keys ─────────────────────────────────────────────────────
|
|
169
|
+
// Registers a publisher Ed25519 public key under the
|
|
170
|
+
// bearer-authenticated subject's identity. Shares one wire contract
|
|
171
|
+
// with the hosted registry's author-keys endpoint.
|
|
172
|
+
app.post('/author-keys', async (c) => {
|
|
173
|
+
const authHeader = c.req.header('authorization');
|
|
174
|
+
const verified = authn.verify(authHeader);
|
|
175
|
+
if (verified === null) {
|
|
176
|
+
return c.json({
|
|
177
|
+
error: 'unauthorized',
|
|
178
|
+
message: 'author-keys requires a valid bearer token in the Authorization header',
|
|
179
|
+
}, 401);
|
|
180
|
+
}
|
|
181
|
+
let rawBody;
|
|
182
|
+
try {
|
|
183
|
+
rawBody = await c.req.json();
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return c.json({ error: 'invalid_request', message: 'request body is not valid JSON' }, 400);
|
|
187
|
+
}
|
|
188
|
+
// Guard against JSON `null` + JSON primitives — Hono's
|
|
189
|
+
// `c.req.json()` succeeds on those (returns the value verbatim),
|
|
190
|
+
// and the next line would throw TypeError trying to read
|
|
191
|
+
// `.publicKeyBase64` off them. The hosted registry's author-keys
|
|
192
|
+
// endpoint applies the same guard so the two transports agree on
|
|
193
|
+
// the 400 wire shape.
|
|
194
|
+
if (rawBody === null || typeof rawBody !== 'object') {
|
|
195
|
+
return c.json({ error: 'invalid_request', message: 'request body must be a JSON object' }, 400);
|
|
196
|
+
}
|
|
197
|
+
const body = rawBody;
|
|
198
|
+
if (typeof body.publicKeyBase64 !== 'string') {
|
|
199
|
+
return c.json({
|
|
200
|
+
error: 'invalid_request',
|
|
201
|
+
message: '`publicKeyBase64` must be a string',
|
|
202
|
+
}, 400);
|
|
203
|
+
}
|
|
204
|
+
const result = await registerAuthorKey({ publicKeyBase64: body.publicKeyBase64 }, { storage, authn: verified });
|
|
205
|
+
return c.json(result.body, result.status);
|
|
206
|
+
});
|
|
207
|
+
// ── /conformance/check ───────────────────────────────────────────────
|
|
208
|
+
app.post('/conformance/check', async (c) => {
|
|
209
|
+
let payload;
|
|
210
|
+
try {
|
|
211
|
+
payload = (await c.req.json());
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return c.json({ ok: false, errors: [{ code: 'manifest_invalid', message: 'request body is not valid JSON' }] }, 400);
|
|
215
|
+
}
|
|
216
|
+
const result = checkConformance(payload);
|
|
217
|
+
// Domain-vs-transport split: a "non-conformant" submission is a
|
|
218
|
+
// valid request that produced a `ok: false` body. Mirrors the cloud
|
|
219
|
+
// conformance handler's wire shape.
|
|
220
|
+
return c.json(result, 200);
|
|
221
|
+
});
|
|
222
|
+
// ── /bundles/:scope/:name/:version/bundle.js ─────────────────────────
|
|
223
|
+
app.get('/bundles/:scope/:name/:version/bundle.js', async (c) => {
|
|
224
|
+
const { scope, name, version } = c.req.param();
|
|
225
|
+
return serveBundle(c, bundleStorage, scope, name, version);
|
|
226
|
+
});
|
|
227
|
+
// ── /bundles/:scope/:name/:version/bundle.js.sig ─────────────────────
|
|
228
|
+
app.get('/bundles/:scope/:name/:version/bundle.js.sig', async (c) => {
|
|
229
|
+
const { scope, name, version } = c.req.param();
|
|
230
|
+
return serveSignature(c, bundleStorage, scope, name, version);
|
|
231
|
+
});
|
|
232
|
+
// ── /bundles/:scope/:name/:version/manifest.json ─────────────────────
|
|
233
|
+
app.get('/bundles/:scope/:name/:version/manifest.json', async (c) => {
|
|
234
|
+
const { scope, name, version } = c.req.param();
|
|
235
|
+
return serveManifest(c, bundleStorage, scope, name, version);
|
|
236
|
+
});
|
|
237
|
+
return app;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Serve the bundle bytes. `application/javascript` MIME with the
|
|
241
|
+
* immutable cache header. 404 on miss.
|
|
242
|
+
*/
|
|
243
|
+
async function serveBundle(c, bundleStorage, rawScope, name, version) {
|
|
244
|
+
const scope = rawScope.startsWith('@') ? rawScope : `@${rawScope}`;
|
|
245
|
+
let bytes;
|
|
246
|
+
try {
|
|
247
|
+
bytes = await bundleStorage.getBundle(scope, name, version);
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
return c.json(errorBody('server_error', errorMessage(err)), 500);
|
|
251
|
+
}
|
|
252
|
+
if (bytes === null) {
|
|
253
|
+
return c.json(errorBody('not_found', 'bundle not found'), 404);
|
|
254
|
+
}
|
|
255
|
+
// Re-wrap the Uint8Array so its backing buffer is a plain ArrayBuffer
|
|
256
|
+
// (hono's `c.body` typing rejects `Uint8Array<ArrayBufferLike>` because
|
|
257
|
+
// the SharedArrayBuffer branch is incompatible with the Web Response
|
|
258
|
+
// body constructor). The copy is a single contiguous allocation —
|
|
259
|
+
// bundles are bounded by `MAX_BUNDLE_BYTES` (5 MiB), so this is cheap.
|
|
260
|
+
const copy = new Uint8Array(bytes.byteLength);
|
|
261
|
+
copy.set(bytes);
|
|
262
|
+
return new Response(copy, {
|
|
263
|
+
status: 200,
|
|
264
|
+
headers: {
|
|
265
|
+
'Content-Type': 'application/javascript; charset=utf-8',
|
|
266
|
+
'Cache-Control': IMMUTABLE_CACHE_HEADER,
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
async function serveSignature(c, bundleStorage, rawScope, name, version) {
|
|
271
|
+
const scope = rawScope.startsWith('@') ? rawScope : `@${rawScope}`;
|
|
272
|
+
let sig;
|
|
273
|
+
try {
|
|
274
|
+
sig = await bundleStorage.getSignature(scope, name, version);
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
return c.json(errorBody('server_error', errorMessage(err)), 500);
|
|
278
|
+
}
|
|
279
|
+
if (sig === null) {
|
|
280
|
+
return c.json(errorBody('not_found', 'signature not found'), 404);
|
|
281
|
+
}
|
|
282
|
+
c.header('Cache-Control', IMMUTABLE_CACHE_HEADER);
|
|
283
|
+
return c.json(sig, 200);
|
|
284
|
+
}
|
|
285
|
+
async function serveManifest(c, bundleStorage, rawScope, name, version) {
|
|
286
|
+
const scope = rawScope.startsWith('@') ? rawScope : `@${rawScope}`;
|
|
287
|
+
let manifest;
|
|
288
|
+
try {
|
|
289
|
+
manifest = await bundleStorage.getManifest(scope, name, version);
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
return c.json(errorBody('server_error', errorMessage(err)), 500);
|
|
293
|
+
}
|
|
294
|
+
if (manifest === null) {
|
|
295
|
+
return c.json(errorBody('not_found', 'manifest not found'), 404);
|
|
296
|
+
}
|
|
297
|
+
c.header('Cache-Control', IMMUTABLE_CACHE_HEADER);
|
|
298
|
+
return c.json(manifest, 200);
|
|
299
|
+
}
|
|
300
|
+
function errorBody(error, message) {
|
|
301
|
+
return { error, message };
|
|
302
|
+
}
|
|
303
|
+
function errorMessage(err) {
|
|
304
|
+
if (err instanceof Error)
|
|
305
|
+
return err.message;
|
|
306
|
+
return String(err);
|
|
307
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ggui-ai/registry-server",
|
|
3
|
+
"version": "0.1.0-rc.1",
|
|
4
|
+
"description": "Self-hostable HTTP server for the ggui marketplace registry. Wraps @ggui-ai/registry-core with hono, filesystem storage, and bearer-token auth. Run via npx for local dev, CI, and on-prem deployments.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ggui",
|
|
7
|
+
"registry",
|
|
8
|
+
"marketplace",
|
|
9
|
+
"mcp",
|
|
10
|
+
"hono",
|
|
11
|
+
"self-hosted"
|
|
12
|
+
],
|
|
13
|
+
"license": "Apache-2.0",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "dist/index.js",
|
|
16
|
+
"types": "dist/index.d.ts",
|
|
17
|
+
"bin": {
|
|
18
|
+
"registry-server": "dist/cli.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@hono/node-server": "^1.19.11",
|
|
33
|
+
"hono": "^4.12.8",
|
|
34
|
+
"@ggui-ai/artifact-manifest": "0.1.0-rc.1",
|
|
35
|
+
"@ggui-ai/registry-core": "0.1.0-rc.1",
|
|
36
|
+
"@ggui-ai/gadget-signing": "0.1.0-rc.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^24.0.0",
|
|
40
|
+
"tsx": "^4.19.0",
|
|
41
|
+
"typescript": "^5.0.0",
|
|
42
|
+
"vitest": "^3.0.0"
|
|
43
|
+
},
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/ggui-ai/ggui.git",
|
|
47
|
+
"directory": "packages/registry-server"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://github.com/ggui-ai/ggui/tree/main/packages/registry-server",
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/ggui-ai/ggui/issues"
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=20.0.0"
|
|
58
|
+
},
|
|
59
|
+
"author": "ggui contributors <hello@ggui.ai>",
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsc -p tsconfig.build.json && chmod +x dist/cli.js",
|
|
62
|
+
"dev": "tsx src/cli.ts",
|
|
63
|
+
"start": "node dist/cli.js",
|
|
64
|
+
"typecheck": "tsc --noEmit",
|
|
65
|
+
"test": "vitest run",
|
|
66
|
+
"test:watch": "vitest",
|
|
67
|
+
"clean": "rm -rf dist"
|
|
68
|
+
}
|
|
69
|
+
}
|