@xtrable-ltd/nanoesis 0.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.
- package/LICENSE +21 -0
- package/README.md +73 -0
- package/dist/adapter-azure-blob.d.ts +97 -0
- package/dist/adapter-azure-blob.js +127 -0
- package/dist/adapter-cloudflare.d.ts +28 -0
- package/dist/adapter-cloudflare.js +32 -0
- package/dist/adapter-fs.d.ts +38 -0
- package/dist/adapter-fs.js +54 -0
- package/dist/adapter-local-jwt.d.ts +205 -0
- package/dist/adapter-local-jwt.js +550 -0
- package/dist/adapter-sharp.d.ts +11 -0
- package/dist/adapter-sharp.js +39 -0
- package/dist/adapter-shell.d.ts +48 -0
- package/dist/adapter-shell.js +56 -0
- package/dist/adapter-trusted-header.d.ts +43 -0
- package/dist/adapter-trusted-header.js +21 -0
- package/dist/chunk-G2UEZTYC.js +2541 -0
- package/dist/editor-api.d.ts +198 -0
- package/dist/editor-api.js +592 -0
- package/dist/editor.d.ts +13 -0
- package/dist/editor.js +6 -0
- package/dist/index.d.ts +1238 -0
- package/dist/index.js +124 -0
- package/editor/assets/TemplatesPane-5qsDAK_B.js +792 -0
- package/editor/assets/TemplatesPane-B4_sg2u5.css +1 -0
- package/editor/assets/abap-BrgZPUOV.js +6 -0
- package/editor/assets/apex-DyP6w7ZV.js +6 -0
- package/editor/assets/azcli-BaLxmfj-.js +6 -0
- package/editor/assets/bat-CFOPXBzS.js +6 -0
- package/editor/assets/bicep-BfEKNvv3.js +7 -0
- package/editor/assets/cameligo-BFG1Mk7z.js +6 -0
- package/editor/assets/clojure-DTECt2xU.js +6 -0
- package/editor/assets/codicon-DCmgc-ay.ttf +0 -0
- package/editor/assets/coffee-CDGzqUPQ.js +6 -0
- package/editor/assets/cpp-CLLBncYj.js +6 -0
- package/editor/assets/csharp-dUCx_-0o.js +6 -0
- package/editor/assets/csp-5Rap-vPy.js +6 -0
- package/editor/assets/css-D3h14YRZ.js +8 -0
- package/editor/assets/css.worker-DaIe3gwK.js +84 -0
- package/editor/assets/cssMode-CGp4MIjR.js +9 -0
- package/editor/assets/cypher-DrQuvNYM.js +6 -0
- package/editor/assets/dart-CFKIUWau.js +6 -0
- package/editor/assets/dockerfile-Zznr-cwX.js +6 -0
- package/editor/assets/ecl-Ce3n6wWz.js +6 -0
- package/editor/assets/editor.worker-BCzxt1at.js +12 -0
- package/editor/assets/elixir-deUWdS0T.js +6 -0
- package/editor/assets/flow9-i9-g7ZhI.js +6 -0
- package/editor/assets/freemarker2-CJkwxmPv.js +8 -0
- package/editor/assets/fsharp-CzKuDChf.js +6 -0
- package/editor/assets/go-Cphgjts3.js +6 -0
- package/editor/assets/graphql-Cg7bfA9N.js +6 -0
- package/editor/assets/handlebars-CKb5i2nM.js +6 -0
- package/editor/assets/hcl-0cvrggvQ.js +6 -0
- package/editor/assets/html-DyMbQx0w.js +6 -0
- package/editor/assets/html.worker-CKrFyw_2.js +461 -0
- package/editor/assets/htmlMode-DVPeqtn-.js +9 -0
- package/editor/assets/index-CbuWEnUB.css +7 -0
- package/editor/assets/index-DJmSgobK.js +129 -0
- package/editor/assets/ini-Drc7WvVn.js +6 -0
- package/editor/assets/java-B_fMsGYe.js +6 -0
- package/editor/assets/javascript-Bp1Qh9wR.js +6 -0
- package/editor/assets/json.worker-B7c_PmGb.js +49 -0
- package/editor/assets/jsonMode-FLEeVtx7.js +15 -0
- package/editor/assets/julia-Bqgm2twL.js +6 -0
- package/editor/assets/kotlin-BSkB5QuD.js +6 -0
- package/editor/assets/less-BsTHnhdd.js +7 -0
- package/editor/assets/lexon-YWi4-JPR.js +6 -0
- package/editor/assets/liquid-Bh8c534t.js +6 -0
- package/editor/assets/lua-nf6ki56Z.js +6 -0
- package/editor/assets/m3-Cpb6xl2v.js +6 -0
- package/editor/assets/markdown-DSZPf7rp.js +6 -0
- package/editor/assets/mdx-BUbo8M9l.js +6 -0
- package/editor/assets/mips-B_c3zf-v.js +6 -0
- package/editor/assets/msdax-rUNN04Wq.js +6 -0
- package/editor/assets/mysql-DDwshQtU.js +6 -0
- package/editor/assets/nanoesis-logo-CgieIWPg.png +0 -0
- package/editor/assets/objective-c-B5zXfXm9.js +6 -0
- package/editor/assets/pascal-CXOwvkN_.js +6 -0
- package/editor/assets/pascaligo-Bc-ZgV77.js +6 -0
- package/editor/assets/perl-CwNk8-XU.js +6 -0
- package/editor/assets/pgsql-tGk8EFnU.js +6 -0
- package/editor/assets/php-CpIb_Oan.js +6 -0
- package/editor/assets/pla-B03wrqEc.js +6 -0
- package/editor/assets/postiats-BKlk5iyT.js +6 -0
- package/editor/assets/powerquery-Bhzvs7bI.js +6 -0
- package/editor/assets/powershell-Dd3NCNK9.js +6 -0
- package/editor/assets/protobuf-COyEY5Pt.js +7 -0
- package/editor/assets/pug-BaJupSGV.js +6 -0
- package/editor/assets/python-CuJlk8g3.js +6 -0
- package/editor/assets/qsharp-DXyYeYxl.js +6 -0
- package/editor/assets/r-CdQndTaG.js +6 -0
- package/editor/assets/razor-CuQT_1Ku.js +6 -0
- package/editor/assets/redis-CVwtpugi.js +6 -0
- package/editor/assets/redshift-25W9uPmb.js +6 -0
- package/editor/assets/restructuredtext-DfzH4Xui.js +6 -0
- package/editor/assets/ruby-Cp1zYvxS.js +6 -0
- package/editor/assets/rust-D5C2fndG.js +6 -0
- package/editor/assets/sb-CDntyWJ8.js +6 -0
- package/editor/assets/scala-BoFRg7Ot.js +6 -0
- package/editor/assets/scheme-Bio4gycK.js +6 -0
- package/editor/assets/scss-4Ik7cdeQ.js +8 -0
- package/editor/assets/shell-CX-rkNHf.js +6 -0
- package/editor/assets/solidity-Tw7wswEv.js +6 -0
- package/editor/assets/sophia-C5WLch3f.js +6 -0
- package/editor/assets/sparql-DHaeiCBh.js +6 -0
- package/editor/assets/sql-CCSDG5nI.js +6 -0
- package/editor/assets/st-pnP8ivHi.js +6 -0
- package/editor/assets/swift-DwJ7jVG9.js +8 -0
- package/editor/assets/systemverilog-B9Xyijhd.js +6 -0
- package/editor/assets/tcl-DnHyzjbg.js +6 -0
- package/editor/assets/ts.worker-BhkL8olL.js +51334 -0
- package/editor/assets/tsMode-CT2HUNtN.js +16 -0
- package/editor/assets/twig-CPajHgWi.js +6 -0
- package/editor/assets/typescript-CtMx97cn.js +6 -0
- package/editor/assets/typespec-D-MeaMDU.js +6 -0
- package/editor/assets/vb-DgyLZaXg.js +6 -0
- package/editor/assets/wgsl-BIv9DU6q.js +303 -0
- package/editor/assets/xml-CyfpINj_.js +6 -0
- package/editor/assets/yaml-BBWmgfMA.js +6 -0
- package/editor/config.json +3 -0
- package/editor/index.html +28 -0
- package/package.json +85 -0
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildAuthoringReference,
|
|
3
|
+
canEdit,
|
|
4
|
+
contentTypeFor,
|
|
5
|
+
deriveFields,
|
|
6
|
+
hasRole,
|
|
7
|
+
loadComponents,
|
|
8
|
+
loadTemplate,
|
|
9
|
+
renderReferenceMarkdown,
|
|
10
|
+
validateSite
|
|
11
|
+
} from "./chunk-G2UEZTYC.js";
|
|
12
|
+
|
|
13
|
+
// ../editor-api/src/api.ts
|
|
14
|
+
var MAX_LOGO_BYTES = 1024 * 1024;
|
|
15
|
+
function requiredWriteRole(path) {
|
|
16
|
+
const top = path.split("/")[0];
|
|
17
|
+
if (top === "content") return "author";
|
|
18
|
+
if (top === "templates" || top === "components" || top === "public") return "developer";
|
|
19
|
+
return "admin";
|
|
20
|
+
}
|
|
21
|
+
function denyWrite(principal, path) {
|
|
22
|
+
if (hasRole(principal, requiredWriteRole(path))) return null;
|
|
23
|
+
return json(403, { ok: false, error: "your role cannot modify this path" });
|
|
24
|
+
}
|
|
25
|
+
function json(status, data) {
|
|
26
|
+
return {
|
|
27
|
+
status,
|
|
28
|
+
headers: { "content-type": "application/json" },
|
|
29
|
+
body: JSON.stringify(data)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function methodNotAllowed() {
|
|
33
|
+
return json(405, { ok: false, error: "POST only" });
|
|
34
|
+
}
|
|
35
|
+
var BEARER_PREFIX = /^Bearer\s+/i;
|
|
36
|
+
function bearerToken(getHeader) {
|
|
37
|
+
const raw = getHeader("authorization") ?? getHeader("Authorization");
|
|
38
|
+
if (raw === void 0) return void 0;
|
|
39
|
+
const match = BEARER_PREFIX.exec(raw);
|
|
40
|
+
return match ? raw.slice(match[0].length).trim() : void 0;
|
|
41
|
+
}
|
|
42
|
+
async function parseJsonBody(req) {
|
|
43
|
+
const bytes = await req.body();
|
|
44
|
+
if (bytes.byteLength === 0) return null;
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function isLoginRequest(value) {
|
|
52
|
+
return typeof value === "object" && value !== null && typeof value["username"] === "string" && typeof value["password"] === "string";
|
|
53
|
+
}
|
|
54
|
+
function isChangePasswordRequest(value) {
|
|
55
|
+
return typeof value === "object" && value !== null && typeof value["currentPassword"] === "string" && typeof value["newPassword"] === "string";
|
|
56
|
+
}
|
|
57
|
+
async function handleAuthRoute(deps, req) {
|
|
58
|
+
const endpoints = deps.authEndpoints;
|
|
59
|
+
if (endpoints === void 0) return void 0;
|
|
60
|
+
switch (req.path) {
|
|
61
|
+
case "/api/auth/state":
|
|
62
|
+
return json(200, await endpoints.state());
|
|
63
|
+
case "/api/login": {
|
|
64
|
+
if (req.method !== "POST") return methodNotAllowed();
|
|
65
|
+
const body = await parseJsonBody(req);
|
|
66
|
+
if (!isLoginRequest(body)) {
|
|
67
|
+
return json(400, { ok: false, error: "JSON body with username + password required" });
|
|
68
|
+
}
|
|
69
|
+
const result = await endpoints.login(body);
|
|
70
|
+
if (result.ok) {
|
|
71
|
+
return json(200, {
|
|
72
|
+
ok: true,
|
|
73
|
+
token: result.value.token,
|
|
74
|
+
principal: result.value.principal,
|
|
75
|
+
firstRunBootstrap: result.value.firstRunBootstrap
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return json(result.status, { ok: false, error: result.error });
|
|
79
|
+
}
|
|
80
|
+
case "/api/refresh": {
|
|
81
|
+
if (req.method !== "POST") return methodNotAllowed();
|
|
82
|
+
const token = bearerToken(req.getHeader);
|
|
83
|
+
if (token === void 0) return json(401, { ok: false, error: "no token" });
|
|
84
|
+
const result = await endpoints.refresh(token);
|
|
85
|
+
if (result.ok) {
|
|
86
|
+
return json(200, {
|
|
87
|
+
ok: true,
|
|
88
|
+
token: result.value.token,
|
|
89
|
+
principal: result.value.principal
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return json(result.status, { ok: false, error: result.error });
|
|
93
|
+
}
|
|
94
|
+
case "/api/logout": {
|
|
95
|
+
if (req.method !== "POST") return methodNotAllowed();
|
|
96
|
+
const token = bearerToken(req.getHeader);
|
|
97
|
+
await endpoints.logout(token ?? "");
|
|
98
|
+
return json(200, { ok: true });
|
|
99
|
+
}
|
|
100
|
+
default:
|
|
101
|
+
return void 0;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function isObject(value) {
|
|
105
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
106
|
+
}
|
|
107
|
+
function fromAuthResult(result) {
|
|
108
|
+
return result.ok ? json(200, result.value) : json(result.status, { ok: false, error: result.error });
|
|
109
|
+
}
|
|
110
|
+
async function handleUserAdminRoute(deps, req, caller) {
|
|
111
|
+
const userAdmin = deps.userAdmin;
|
|
112
|
+
if (userAdmin === void 0) {
|
|
113
|
+
return json(404, { ok: false, error: "user management is not available on this host" });
|
|
114
|
+
}
|
|
115
|
+
if (!hasRole(caller, "admin")) {
|
|
116
|
+
return json(403, { ok: false, error: "admin role required" });
|
|
117
|
+
}
|
|
118
|
+
const segments = req.path.slice("/api/users".length).split("/").filter((s) => s !== "");
|
|
119
|
+
if (segments.length === 0) {
|
|
120
|
+
if (req.method === "GET") return fromAuthResult(await userAdmin.listUsers());
|
|
121
|
+
if (req.method === "POST") {
|
|
122
|
+
const body = await parseJsonBody(req);
|
|
123
|
+
if (!isObject(body)) return json(400, { ok: false, error: "JSON body required" });
|
|
124
|
+
return fromAuthResult(await userAdmin.createUser(body));
|
|
125
|
+
}
|
|
126
|
+
return json(405, { ok: false, error: "method not allowed" });
|
|
127
|
+
}
|
|
128
|
+
const targetId = segments[0];
|
|
129
|
+
if (segments.length === 2 && segments[1] === "password") {
|
|
130
|
+
if (req.method !== "POST") return json(405, { ok: false, error: "method not allowed" });
|
|
131
|
+
const body = await parseJsonBody(req);
|
|
132
|
+
if (!isObject(body)) return json(400, { ok: false, error: "JSON body required" });
|
|
133
|
+
return fromAuthResult(
|
|
134
|
+
await userAdmin.resetPassword(targetId, body)
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (segments.length === 1) {
|
|
138
|
+
if (req.method === "PATCH") {
|
|
139
|
+
const body = await parseJsonBody(req);
|
|
140
|
+
if (!isObject(body)) return json(400, { ok: false, error: "JSON body required" });
|
|
141
|
+
return fromAuthResult(
|
|
142
|
+
await userAdmin.updateUser(caller.userId, targetId, body)
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
if (req.method === "DELETE") {
|
|
146
|
+
return fromAuthResult(await userAdmin.deleteUser(caller.userId, targetId));
|
|
147
|
+
}
|
|
148
|
+
return json(405, { ok: false, error: "method not allowed" });
|
|
149
|
+
}
|
|
150
|
+
return json(404, { ok: false, error: `Unknown API route: ${req.path}` });
|
|
151
|
+
}
|
|
152
|
+
async function handleBrandingRoute(deps, req) {
|
|
153
|
+
if (req.path !== "/api/branding" && req.path !== "/api/branding/logo") return void 0;
|
|
154
|
+
const store = deps.branding;
|
|
155
|
+
if (req.method === "GET") {
|
|
156
|
+
if (req.path === "/api/branding") {
|
|
157
|
+
if (store === void 0) return json(200, { name: null, logoUrl: null });
|
|
158
|
+
const state = await store.get();
|
|
159
|
+
const logoUrl = state.logo === null ? null : `/api/branding/logo?v=${state.logo.updatedAt}`;
|
|
160
|
+
return json(200, { name: state.name, logoUrl });
|
|
161
|
+
}
|
|
162
|
+
const logo = store === void 0 ? null : await store.getLogo();
|
|
163
|
+
if (logo === null) return { status: 404, body: "" };
|
|
164
|
+
return {
|
|
165
|
+
status: 200,
|
|
166
|
+
headers: { "content-type": logo.contentType, "cache-control": "public, max-age=31536000" },
|
|
167
|
+
body: logo.bytes
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (store === void 0) {
|
|
171
|
+
return json(404, { ok: false, error: "branding is not available on this host" });
|
|
172
|
+
}
|
|
173
|
+
if (!hasRole(await deps.identity.authenticate(req.getHeader), "admin")) {
|
|
174
|
+
return json(403, { ok: false, error: "admin role required" });
|
|
175
|
+
}
|
|
176
|
+
if (req.path === "/api/branding") {
|
|
177
|
+
if (req.method !== "PUT") return json(405, { ok: false, error: "method not allowed" });
|
|
178
|
+
const body = await parseJsonBody(req);
|
|
179
|
+
if (!isObject(body)) return json(400, { ok: false, error: "JSON body required" });
|
|
180
|
+
const raw = body["name"];
|
|
181
|
+
const name = typeof raw === "string" && raw.trim() !== "" ? raw.trim() : null;
|
|
182
|
+
await store.setName(name);
|
|
183
|
+
return json(200, { ok: true });
|
|
184
|
+
}
|
|
185
|
+
if (req.method === "POST") {
|
|
186
|
+
const contentType = req.getHeader("content-type") ?? "";
|
|
187
|
+
if (!contentType.startsWith("image/")) {
|
|
188
|
+
return json(415, { ok: false, error: "logo must be an image" });
|
|
189
|
+
}
|
|
190
|
+
const bytes = await req.body();
|
|
191
|
+
if (bytes.byteLength === 0) return json(400, { ok: false, error: "empty body" });
|
|
192
|
+
if (bytes.byteLength > MAX_LOGO_BYTES) return json(413, { ok: false, error: "logo too large" });
|
|
193
|
+
await store.setLogo(bytes, contentType);
|
|
194
|
+
return json(200, { ok: true });
|
|
195
|
+
}
|
|
196
|
+
if (req.method === "DELETE") {
|
|
197
|
+
await store.clearLogo();
|
|
198
|
+
return json(200, { ok: true });
|
|
199
|
+
}
|
|
200
|
+
return json(405, { ok: false, error: "method not allowed" });
|
|
201
|
+
}
|
|
202
|
+
async function handleApi(deps, req) {
|
|
203
|
+
const authResponse = await handleAuthRoute(deps, req);
|
|
204
|
+
if (authResponse !== void 0) return authResponse;
|
|
205
|
+
const brandingResponse = await handleBrandingRoute(deps, req);
|
|
206
|
+
if (brandingResponse !== void 0) return brandingResponse;
|
|
207
|
+
const principal = await deps.identity.authenticate(req.getHeader);
|
|
208
|
+
if (!canEdit(principal)) {
|
|
209
|
+
return json(401, { ok: false, error: "Unauthorized" });
|
|
210
|
+
}
|
|
211
|
+
if (req.path === "/api/users" || req.path.startsWith("/api/users/")) {
|
|
212
|
+
return handleUserAdminRoute(deps, req, principal);
|
|
213
|
+
}
|
|
214
|
+
const get = (key) => req.query.get(key) ?? "";
|
|
215
|
+
switch (req.path) {
|
|
216
|
+
case "/api/list": {
|
|
217
|
+
try {
|
|
218
|
+
return json(200, await deps.store.list(get("dir")));
|
|
219
|
+
} catch {
|
|
220
|
+
return json(200, []);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
case "/api/read": {
|
|
224
|
+
const path = get("path");
|
|
225
|
+
try {
|
|
226
|
+
const bytes = await deps.store.readBytes(path);
|
|
227
|
+
return { status: 200, headers: { "content-type": contentTypeFor(path) }, body: bytes };
|
|
228
|
+
} catch {
|
|
229
|
+
return { status: 404, body: "" };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
case "/api/exists":
|
|
233
|
+
return json(200, { exists: await deps.store.exists(get("path")) });
|
|
234
|
+
case "/api/validate":
|
|
235
|
+
return json(200, await validateSite(deps.store));
|
|
236
|
+
case "/api/authors":
|
|
237
|
+
return json(200, deps.authors ? await deps.authors() : []);
|
|
238
|
+
case "/api/template-fields": {
|
|
239
|
+
try {
|
|
240
|
+
const [template, components] = await Promise.all([
|
|
241
|
+
loadTemplate(deps.store, get("name")),
|
|
242
|
+
loadComponents(deps.store)
|
|
243
|
+
]);
|
|
244
|
+
return json(200, deriveFields(template, components));
|
|
245
|
+
} catch {
|
|
246
|
+
return { status: 404, body: "" };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
case "/api/write": {
|
|
250
|
+
if (req.method !== "POST") return methodNotAllowed();
|
|
251
|
+
const denied = denyWrite(principal, get("path"));
|
|
252
|
+
if (denied) return denied;
|
|
253
|
+
try {
|
|
254
|
+
await deps.store.write(get("path"), await req.body());
|
|
255
|
+
return json(200, { ok: true });
|
|
256
|
+
} catch (error) {
|
|
257
|
+
return json(500, { ok: false, error: String(error) });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
case "/api/delete": {
|
|
261
|
+
if (req.method !== "POST") return methodNotAllowed();
|
|
262
|
+
const denied = denyWrite(principal, get("path"));
|
|
263
|
+
if (denied) return denied;
|
|
264
|
+
try {
|
|
265
|
+
await deps.store.delete(get("path"));
|
|
266
|
+
return json(200, { ok: true });
|
|
267
|
+
} catch (error) {
|
|
268
|
+
return json(500, { ok: false, error: String(error) });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
case "/api/rename": {
|
|
272
|
+
if (req.method !== "POST") return methodNotAllowed();
|
|
273
|
+
const denied = denyWrite(principal, get("from")) ?? denyWrite(principal, get("to"));
|
|
274
|
+
if (denied) return denied;
|
|
275
|
+
const result = await deps.store.rename(get("from"), get("to"));
|
|
276
|
+
if (result.ok) return json(200, { ok: true });
|
|
277
|
+
const status = result.reason === "exists" ? 409 : 404;
|
|
278
|
+
return json(status, { ok: false, error: result.reason });
|
|
279
|
+
}
|
|
280
|
+
case "/api/publish": {
|
|
281
|
+
if (req.method !== "POST") return methodNotAllowed();
|
|
282
|
+
const result = await deps.publish();
|
|
283
|
+
if (result.ok) return json(200, { ok: true, written: result.written.length });
|
|
284
|
+
return json(422, { ok: false, errors: result.validation.errors.map((e) => e.message) });
|
|
285
|
+
}
|
|
286
|
+
case "/api/me/password": {
|
|
287
|
+
if (req.method !== "POST") return methodNotAllowed();
|
|
288
|
+
if (deps.authEndpoints === void 0) {
|
|
289
|
+
return json(404, { ok: false, error: "no credential provider configured" });
|
|
290
|
+
}
|
|
291
|
+
const body = await parseJsonBody(req);
|
|
292
|
+
if (!isChangePasswordRequest(body)) {
|
|
293
|
+
return json(400, {
|
|
294
|
+
ok: false,
|
|
295
|
+
error: "JSON body with currentPassword + newPassword required"
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
const result = await deps.authEndpoints.changePassword(principal.userId, body);
|
|
299
|
+
if (result.ok) return json(200, { ok: true, token: result.value.token });
|
|
300
|
+
return json(result.status, { ok: false, error: result.error });
|
|
301
|
+
}
|
|
302
|
+
case "/api/me/token": {
|
|
303
|
+
const endpoints = deps.authEndpoints;
|
|
304
|
+
if (req.method === "POST") {
|
|
305
|
+
if (endpoints?.createToken === void 0) {
|
|
306
|
+
return json(404, { ok: false, error: "tokens are not available on this host" });
|
|
307
|
+
}
|
|
308
|
+
const result = await endpoints.createToken(principal.userId);
|
|
309
|
+
return result.ok ? json(200, { ok: true, token: result.value.token, expiresAt: result.value.expiresAt }) : json(result.status, { ok: false, error: result.error });
|
|
310
|
+
}
|
|
311
|
+
if (req.method === "DELETE") {
|
|
312
|
+
if (endpoints?.revokeTokens === void 0) {
|
|
313
|
+
return json(404, { ok: false, error: "token revocation is not available" });
|
|
314
|
+
}
|
|
315
|
+
const result = await endpoints.revokeTokens(principal.userId);
|
|
316
|
+
return result.ok ? json(200, { ok: true }) : json(result.status, { ok: false, error: result.error });
|
|
317
|
+
}
|
|
318
|
+
return json(405, { ok: false, error: "method not allowed" });
|
|
319
|
+
}
|
|
320
|
+
default:
|
|
321
|
+
return json(404, { ok: false, error: `Unknown API route: ${req.path}` });
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ../editor-api/src/authors.ts
|
|
326
|
+
function displayNameOf(user) {
|
|
327
|
+
const explicit = user.displayName?.trim();
|
|
328
|
+
return explicit !== void 0 && explicit !== "" ? explicit : user.username;
|
|
329
|
+
}
|
|
330
|
+
function authorOptions(users) {
|
|
331
|
+
return users.map((user) => ({ name: displayNameOf(user), user: user.username })).sort((a, b) => a.name.localeCompare(b.name));
|
|
332
|
+
}
|
|
333
|
+
function authorDirectory(users) {
|
|
334
|
+
const byUser = new Map(users.map((user) => [user.username, displayNameOf(user)]));
|
|
335
|
+
return (user) => {
|
|
336
|
+
const displayName = byUser.get(user);
|
|
337
|
+
return displayName === void 0 ? void 0 : { displayName };
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ../editor-api/src/mcp.ts
|
|
342
|
+
function str(args, key) {
|
|
343
|
+
const value = args[key];
|
|
344
|
+
return typeof value === "string" ? value : "";
|
|
345
|
+
}
|
|
346
|
+
function request(method, path, query, bodyText, token) {
|
|
347
|
+
const body = bodyText === void 0 ? new Uint8Array() : new TextEncoder().encode(bodyText);
|
|
348
|
+
return {
|
|
349
|
+
method,
|
|
350
|
+
path,
|
|
351
|
+
query: new URLSearchParams(query),
|
|
352
|
+
getHeader: (name) => name.toLowerCase() === "authorization" && token !== void 0 ? `Bearer ${token}` : void 0,
|
|
353
|
+
body: async () => body
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
var pathArg = {
|
|
357
|
+
type: "string",
|
|
358
|
+
description: 'Site-relative path with forward slashes, e.g. "content/blog/post.json".'
|
|
359
|
+
};
|
|
360
|
+
var TOOL_SPECS = [
|
|
361
|
+
{
|
|
362
|
+
name: "list_dir",
|
|
363
|
+
description: "List the immediate children (files and folders) of a site directory. Use '' or omit for the site root; the top-level folders are content/ (pages), templates/, components/, and public/.",
|
|
364
|
+
inputSchema: {
|
|
365
|
+
type: "object",
|
|
366
|
+
properties: {
|
|
367
|
+
path: { type: "string", description: "Directory path, '' for the site root." }
|
|
368
|
+
},
|
|
369
|
+
additionalProperties: false
|
|
370
|
+
},
|
|
371
|
+
toRequest: (args, token) => request("GET", "/api/list", { dir: str(args, "path") }, void 0, token)
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
name: "read_file",
|
|
375
|
+
description: "Read a file's text contents (content JSON, a template/component's HTML, CSS, etc.). Reports an error if the file does not exist.",
|
|
376
|
+
inputSchema: {
|
|
377
|
+
type: "object",
|
|
378
|
+
properties: { path: pathArg },
|
|
379
|
+
required: ["path"],
|
|
380
|
+
additionalProperties: false
|
|
381
|
+
},
|
|
382
|
+
toRequest: (args, token) => request("GET", "/api/read", { path: str(args, "path") }, void 0, token)
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
name: "describe_template",
|
|
386
|
+
description: "List the fields a template defines (name, type, label, whether required, length limits), so you know what a page using it needs. Pass the template name without its path or extension, e.g. 'article' for templates/article.html.",
|
|
387
|
+
inputSchema: {
|
|
388
|
+
type: "object",
|
|
389
|
+
properties: {
|
|
390
|
+
name: {
|
|
391
|
+
type: "string",
|
|
392
|
+
description: 'Template name without path or extension, e.g. "article".'
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
required: ["name"],
|
|
396
|
+
additionalProperties: false
|
|
397
|
+
},
|
|
398
|
+
toRequest: (args, token) => request("GET", "/api/template-fields", { name: str(args, "name") }, void 0, token)
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
name: "write_file",
|
|
402
|
+
description: "Create or overwrite a text file. content/ requires the author role; templates/, components/, and public/ require the developer role. Read the nanoesis://reference resource first for the authoring syntax.",
|
|
403
|
+
inputSchema: {
|
|
404
|
+
type: "object",
|
|
405
|
+
properties: {
|
|
406
|
+
path: pathArg,
|
|
407
|
+
contents: { type: "string", description: "The full new text contents of the file." }
|
|
408
|
+
},
|
|
409
|
+
required: ["path", "contents"],
|
|
410
|
+
additionalProperties: false
|
|
411
|
+
},
|
|
412
|
+
toRequest: (args, token) => request("POST", "/api/write", { path: str(args, "path") }, str(args, "contents"), token)
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
name: "delete_path",
|
|
416
|
+
description: "Delete a file or a whole folder subtree. Same role rules as write_file. Deleting a path that does not exist is a no-op.",
|
|
417
|
+
inputSchema: {
|
|
418
|
+
type: "object",
|
|
419
|
+
properties: { path: pathArg },
|
|
420
|
+
required: ["path"],
|
|
421
|
+
additionalProperties: false
|
|
422
|
+
},
|
|
423
|
+
toRequest: (args, token) => request("POST", "/api/delete", { path: str(args, "path") }, void 0, token)
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: "rename_path",
|
|
427
|
+
description: "Move or rename a file or folder subtree. Refuses to overwrite an existing destination. Requires write rights at both the source and the destination.",
|
|
428
|
+
inputSchema: {
|
|
429
|
+
type: "object",
|
|
430
|
+
properties: {
|
|
431
|
+
from: { type: "string", description: "The current path." },
|
|
432
|
+
to: { type: "string", description: "The new path." }
|
|
433
|
+
},
|
|
434
|
+
required: ["from", "to"],
|
|
435
|
+
additionalProperties: false
|
|
436
|
+
},
|
|
437
|
+
toRequest: (args, token) => request(
|
|
438
|
+
"POST",
|
|
439
|
+
"/api/rename",
|
|
440
|
+
{ from: str(args, "from"), to: str(args, "to") },
|
|
441
|
+
void 0,
|
|
442
|
+
token
|
|
443
|
+
)
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
name: "validate",
|
|
447
|
+
description: "Run the validation gate over the whole site and return its diagnostics, without publishing. Use it to check your work: errors would block a publish, warnings (e.g. length constraints) only inform.",
|
|
448
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
449
|
+
toRequest: (_args, token) => request("GET", "/api/validate", {}, void 0, token)
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
name: "publish",
|
|
453
|
+
description: "Compile and publish the whole site. Runs the validation gate first; if anything would break, it reports the problems and writes nothing.",
|
|
454
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
455
|
+
toRequest: (_args, token) => request("POST", "/api/publish", {}, void 0, token)
|
|
456
|
+
}
|
|
457
|
+
];
|
|
458
|
+
var MCP_TOOLS = TOOL_SPECS.map(
|
|
459
|
+
({ name, description, inputSchema }) => ({ name, description, inputSchema })
|
|
460
|
+
);
|
|
461
|
+
function toResult(response) {
|
|
462
|
+
const text = typeof response.body === "string" ? response.body : response.body === void 0 ? "" : new TextDecoder().decode(response.body);
|
|
463
|
+
return { text, isError: response.status >= 400 };
|
|
464
|
+
}
|
|
465
|
+
async function callMcpTool(deps, name, args = {}, options = {}) {
|
|
466
|
+
const spec = TOOL_SPECS.find((tool) => tool.name === name);
|
|
467
|
+
if (spec === void 0) return { text: `Unknown tool: ${name}`, isError: true };
|
|
468
|
+
return toResult(await handleApi(deps, spec.toRequest(args, options.token)));
|
|
469
|
+
}
|
|
470
|
+
var REFERENCE_URI = "nanoesis://reference";
|
|
471
|
+
var MCP_RESOURCES = [
|
|
472
|
+
{
|
|
473
|
+
uri: REFERENCE_URI,
|
|
474
|
+
name: "nanoesis authoring reference",
|
|
475
|
+
description: "The generated reference for nanoesis templates and content: tokens, field types, annotations, loops, components, and the document shell. Read this before writing templates or content.",
|
|
476
|
+
mimeType: "text/markdown"
|
|
477
|
+
}
|
|
478
|
+
];
|
|
479
|
+
function readMcpResource(uri) {
|
|
480
|
+
if (uri === REFERENCE_URI) {
|
|
481
|
+
return { text: renderReferenceMarkdown(buildAuthoringReference()), mimeType: "text/markdown" };
|
|
482
|
+
}
|
|
483
|
+
return void 0;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// ../editor-api/src/branding.ts
|
|
487
|
+
import { mkdir, readFile, rename, rm, writeFile } from "fs/promises";
|
|
488
|
+
import { dirname, join } from "path";
|
|
489
|
+
var EMPTY = { name: null, logo: null };
|
|
490
|
+
var InMemoryBrandingStore = class {
|
|
491
|
+
constructor(now = Date.now) {
|
|
492
|
+
this.now = now;
|
|
493
|
+
}
|
|
494
|
+
now;
|
|
495
|
+
name = null;
|
|
496
|
+
logo = null;
|
|
497
|
+
logoUpdatedAt = 0;
|
|
498
|
+
async get() {
|
|
499
|
+
return {
|
|
500
|
+
name: this.name,
|
|
501
|
+
logo: this.logo === null ? null : { contentType: this.logo.contentType, updatedAt: this.logoUpdatedAt }
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
async getLogo() {
|
|
505
|
+
return this.logo;
|
|
506
|
+
}
|
|
507
|
+
async setName(name) {
|
|
508
|
+
this.name = name;
|
|
509
|
+
}
|
|
510
|
+
async setLogo(bytes, contentType) {
|
|
511
|
+
this.logo = { bytes, contentType };
|
|
512
|
+
this.logoUpdatedAt = this.now();
|
|
513
|
+
}
|
|
514
|
+
async clearLogo() {
|
|
515
|
+
this.logo = null;
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
var FileBrandingStore = class {
|
|
519
|
+
constructor(metaPath, now = Date.now) {
|
|
520
|
+
this.metaPath = metaPath;
|
|
521
|
+
this.now = now;
|
|
522
|
+
this.logoPath = join(dirname(metaPath), "branding-logo");
|
|
523
|
+
}
|
|
524
|
+
metaPath;
|
|
525
|
+
now;
|
|
526
|
+
logoPath;
|
|
527
|
+
async loadMeta() {
|
|
528
|
+
try {
|
|
529
|
+
const raw = await readFile(this.metaPath, "utf8");
|
|
530
|
+
const text = raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
|
|
531
|
+
const parsed = JSON.parse(text);
|
|
532
|
+
return {
|
|
533
|
+
name: typeof parsed.name === "string" ? parsed.name : null,
|
|
534
|
+
logo: parsed.logo && typeof parsed.logo.contentType === "string" ? {
|
|
535
|
+
contentType: parsed.logo.contentType,
|
|
536
|
+
updatedAt: Number(parsed.logo.updatedAt) || 0
|
|
537
|
+
} : null
|
|
538
|
+
};
|
|
539
|
+
} catch (error) {
|
|
540
|
+
if (error.code === "ENOENT") return EMPTY;
|
|
541
|
+
throw error;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
async saveMeta(state) {
|
|
545
|
+
await mkdir(dirname(this.metaPath), { recursive: true });
|
|
546
|
+
const tmp = `${this.metaPath}.tmp`;
|
|
547
|
+
await writeFile(tmp, JSON.stringify(state, null, 2), "utf8");
|
|
548
|
+
await rename(tmp, this.metaPath);
|
|
549
|
+
}
|
|
550
|
+
async get() {
|
|
551
|
+
return this.loadMeta();
|
|
552
|
+
}
|
|
553
|
+
async getLogo() {
|
|
554
|
+
const meta = await this.loadMeta();
|
|
555
|
+
if (meta.logo === null) return null;
|
|
556
|
+
try {
|
|
557
|
+
const bytes = await readFile(this.logoPath);
|
|
558
|
+
return { bytes: new Uint8Array(bytes), contentType: meta.logo.contentType };
|
|
559
|
+
} catch (error) {
|
|
560
|
+
if (error.code === "ENOENT") return null;
|
|
561
|
+
throw error;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
async setName(name) {
|
|
565
|
+
const meta = await this.loadMeta();
|
|
566
|
+
await this.saveMeta({ name, logo: meta.logo });
|
|
567
|
+
}
|
|
568
|
+
async setLogo(bytes, contentType) {
|
|
569
|
+
const meta = await this.loadMeta();
|
|
570
|
+
await mkdir(dirname(this.logoPath), { recursive: true });
|
|
571
|
+
const tmp = `${this.logoPath}.tmp`;
|
|
572
|
+
await writeFile(tmp, bytes);
|
|
573
|
+
await rename(tmp, this.logoPath);
|
|
574
|
+
await this.saveMeta({ name: meta.name, logo: { contentType, updatedAt: this.now() } });
|
|
575
|
+
}
|
|
576
|
+
async clearLogo() {
|
|
577
|
+
const meta = await this.loadMeta();
|
|
578
|
+
await rm(this.logoPath, { force: true });
|
|
579
|
+
await this.saveMeta({ name: meta.name, logo: null });
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
export {
|
|
583
|
+
FileBrandingStore,
|
|
584
|
+
InMemoryBrandingStore,
|
|
585
|
+
MCP_RESOURCES,
|
|
586
|
+
MCP_TOOLS,
|
|
587
|
+
authorDirectory,
|
|
588
|
+
authorOptions,
|
|
589
|
+
callMcpTool,
|
|
590
|
+
handleApi,
|
|
591
|
+
readMcpResource
|
|
592
|
+
};
|
package/dist/editor.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Absolute path to the bundled editor SPA (static files) shipped inside this package
|
|
3
|
+
* (DESIGN §11c). Point your host's static file server at it; it contains `index.html`
|
|
4
|
+
* and the editor's assets. Example:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { editorDist } from '@xtrable-ltd/nanoesis/editor';
|
|
8
|
+
* // serve files from `editorDist`, falling back to index.html for client-side routing.
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
declare const editorDist: string;
|
|
12
|
+
|
|
13
|
+
export { editorDist };
|