@xtrable-ltd/nanoesis 0.1.21 → 0.1.22
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/dist/adapter-azure-blob.d.ts +12 -3
- package/dist/adapter-azure-blob.js +16 -1
- package/dist/adapter-fs.d.ts +11 -2
- package/dist/adapter-fs.js +14 -1
- package/dist/{chunk-26VCV3IE.js → chunk-SHGYQTZ7.js} +317 -203
- package/dist/editor-api.d.ts +102 -2
- package/dist/editor-api.js +7 -1
- package/dist/index.d.ts +17 -1
- package/dist/mcp.js +2 -2
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ContainerClient } from '@azure/storage-blob';
|
|
2
|
-
import { BlobStore, ArtifactSink } from '@nanoesis/engine';
|
|
2
|
+
import { BlobStore, ArtifactSink, Storage } from '@nanoesis/engine';
|
|
3
3
|
export { contentTypeFor } from '@nanoesis/engine';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -34,7 +34,7 @@ declare class InMemoryBlobContainer implements BlobContainer {
|
|
|
34
34
|
constructor(seed?: Readonly<Record<string, Uint8Array | string>>);
|
|
35
35
|
list(prefix: string): Promise<readonly string[]>;
|
|
36
36
|
read(name: string): Promise<Uint8Array | null>;
|
|
37
|
-
write(name: string, data: Uint8Array): Promise<void>;
|
|
37
|
+
write(name: string, data: Uint8Array, contentType?: string): Promise<void>;
|
|
38
38
|
remove(name: string): Promise<void>;
|
|
39
39
|
/** Test helper: the current blob names. */
|
|
40
40
|
get names(): readonly string[];
|
|
@@ -90,6 +90,15 @@ declare class BlobArtifactSink implements ArtifactSink {
|
|
|
90
90
|
write(path: string, contents: string | Uint8Array): Promise<void>;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* A ready-made {@link Storage} over an Azure Blob container: get/put/delete (via
|
|
95
|
+
* {@link BlobContainerStore}) plus `wipe` (delete every blob). Pass it to `createEditor`
|
|
96
|
+
* as `editorFiles` (the working container) and `website` (the published `$web` container).
|
|
97
|
+
* For the working container, also pass `enumerate: () => container.list('')` so the editor
|
|
98
|
+
* can reconcile its content index.
|
|
99
|
+
*/
|
|
100
|
+
declare function azureStorage(container: BlobContainer): Storage;
|
|
101
|
+
|
|
93
102
|
/**
|
|
94
103
|
* Pure path/name helper for the blob adapters. Blob storage is a *flat* namespace, a blob
|
|
95
104
|
* is just a name like "content/blog/post.json", so the only mapping the SDK glue needs is
|
|
@@ -100,4 +109,4 @@ declare class BlobArtifactSink implements ArtifactSink {
|
|
|
100
109
|
/** Normalise a site path to a blob name: forward slashes, no leading/trailing slash. */
|
|
101
110
|
declare function normalizeBlobName(path: string): string;
|
|
102
111
|
|
|
103
|
-
export { AzureBlobContainer, BlobArtifactSink, type BlobContainer, BlobContainerStore, InMemoryBlobContainer, normalizeBlobName };
|
|
112
|
+
export { AzureBlobContainer, BlobArtifactSink, type BlobContainer, BlobContainerStore, InMemoryBlobContainer, azureStorage, normalizeBlobName };
|
|
@@ -20,7 +20,8 @@ var InMemoryBlobContainer = class {
|
|
|
20
20
|
async read(name) {
|
|
21
21
|
return this.blobs.get(name) ?? null;
|
|
22
22
|
}
|
|
23
|
-
async write(name, data) {
|
|
23
|
+
async write(name, data, contentType) {
|
|
24
|
+
void contentType;
|
|
24
25
|
this.blobs.set(name, data);
|
|
25
26
|
}
|
|
26
27
|
async remove(name) {
|
|
@@ -117,11 +118,25 @@ var BlobArtifactSink = class {
|
|
|
117
118
|
await this.container.write(name, data, contentTypeFor(name));
|
|
118
119
|
}
|
|
119
120
|
};
|
|
121
|
+
|
|
122
|
+
// ../../adapters/azure-blob/src/azure-storage.ts
|
|
123
|
+
function azureStorage(container) {
|
|
124
|
+
const store = new BlobContainerStore(container);
|
|
125
|
+
return {
|
|
126
|
+
get: (key) => store.get(key),
|
|
127
|
+
put: (key, bytes) => store.put(key, bytes),
|
|
128
|
+
delete: (key) => store.delete(key),
|
|
129
|
+
wipe: async () => {
|
|
130
|
+
for (const name of await container.list("")) await container.remove(name);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
120
134
|
export {
|
|
121
135
|
AzureBlobContainer,
|
|
122
136
|
BlobArtifactSink,
|
|
123
137
|
BlobContainerStore,
|
|
124
138
|
InMemoryBlobContainer,
|
|
139
|
+
azureStorage,
|
|
125
140
|
contentTypeFor,
|
|
126
141
|
normalizeBlobName
|
|
127
142
|
};
|
package/dist/adapter-fs.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BlobStore, ArtifactSink } from '@nanoesis/engine';
|
|
1
|
+
import { BlobStore, ArtifactSink, Storage } from '@nanoesis/engine';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Filesystem implementation of the engine's {@link BlobStore} port (DESIGN §11c): get /
|
|
@@ -35,4 +35,13 @@ declare class FsArtifactSink implements ArtifactSink {
|
|
|
35
35
|
wipe(): Promise<void>;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
/**
|
|
39
|
+
* A ready-made {@link Storage} over a local directory: get/put/delete (via
|
|
40
|
+
* {@link FsBlobStore}) plus `wipe` (remove the directory). Pass it straight to
|
|
41
|
+
* `createEditor` as `editorFiles` and/or `website` for a zero-implementation local host
|
|
42
|
+
* (DESIGN §11c): `createEditor({ editorFiles: folderStorage('./site'), website:
|
|
43
|
+
* folderStorage('./public'), login: devNoAuth() })`.
|
|
44
|
+
*/
|
|
45
|
+
declare function folderStorage(root: string): Storage;
|
|
46
|
+
|
|
47
|
+
export { FsArtifactSink, FsBlobStore, folderStorage };
|
package/dist/adapter-fs.js
CHANGED
|
@@ -48,7 +48,20 @@ var FsArtifactSink = class {
|
|
|
48
48
|
await rm2(this.root, { recursive: true, force: true });
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
|
+
|
|
52
|
+
// ../../adapters/fs/src/folder-storage.ts
|
|
53
|
+
import { rm as rm3 } from "fs/promises";
|
|
54
|
+
function folderStorage(root) {
|
|
55
|
+
const blobs = new FsBlobStore(root);
|
|
56
|
+
return {
|
|
57
|
+
get: (key) => blobs.get(key),
|
|
58
|
+
put: (key, bytes) => blobs.put(key, bytes),
|
|
59
|
+
delete: (key) => blobs.delete(key),
|
|
60
|
+
wipe: () => rm3(root, { recursive: true, force: true })
|
|
61
|
+
};
|
|
62
|
+
}
|
|
51
63
|
export {
|
|
52
64
|
FsArtifactSink,
|
|
53
|
-
FsBlobStore
|
|
65
|
+
FsBlobStore,
|
|
66
|
+
folderStorage
|
|
54
67
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
IndexedStore,
|
|
2
3
|
analyzeTemplate,
|
|
3
4
|
applyMigration,
|
|
4
5
|
baseTemplateName,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
loadComponents,
|
|
13
14
|
loadTemplate,
|
|
14
15
|
pendingMigrations,
|
|
16
|
+
publishSite,
|
|
15
17
|
renderReferenceMarkdown,
|
|
16
18
|
validateSite,
|
|
17
19
|
workingStoreRoundTripDiagnostic
|
|
@@ -633,202 +635,6 @@ function authorDirectory(users) {
|
|
|
633
635
|
};
|
|
634
636
|
}
|
|
635
637
|
|
|
636
|
-
// ../editor-api/src/mcp.ts
|
|
637
|
-
function str(args, key) {
|
|
638
|
-
const value = args[key];
|
|
639
|
-
return typeof value === "string" ? value : "";
|
|
640
|
-
}
|
|
641
|
-
function request(method, path, query, bodyText, token) {
|
|
642
|
-
const body = bodyText === void 0 ? new Uint8Array() : new TextEncoder().encode(bodyText);
|
|
643
|
-
return {
|
|
644
|
-
method,
|
|
645
|
-
path,
|
|
646
|
-
query: new URLSearchParams(query),
|
|
647
|
-
getHeader: (name) => name.toLowerCase() === "authorization" && token !== void 0 ? `Bearer ${token}` : void 0,
|
|
648
|
-
body: async () => body
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
var pathArg = {
|
|
652
|
-
type: "string",
|
|
653
|
-
description: 'Site-relative path with forward slashes, e.g. "content/blog/post.json".'
|
|
654
|
-
};
|
|
655
|
-
var TOOL_SPECS = [
|
|
656
|
-
{
|
|
657
|
-
name: "list_dir",
|
|
658
|
-
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/.",
|
|
659
|
-
inputSchema: {
|
|
660
|
-
type: "object",
|
|
661
|
-
properties: {
|
|
662
|
-
path: { type: "string", description: "Directory path, '' for the site root." }
|
|
663
|
-
},
|
|
664
|
-
additionalProperties: false
|
|
665
|
-
},
|
|
666
|
-
toRequest: (args, token) => request("GET", "/api/list", { dir: str(args, "path") }, void 0, token)
|
|
667
|
-
},
|
|
668
|
-
{
|
|
669
|
-
name: "read_file",
|
|
670
|
-
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.",
|
|
671
|
-
inputSchema: {
|
|
672
|
-
type: "object",
|
|
673
|
-
properties: { path: pathArg },
|
|
674
|
-
required: ["path"],
|
|
675
|
-
additionalProperties: false
|
|
676
|
-
},
|
|
677
|
-
toRequest: (args, token) => request("GET", "/api/read", { path: str(args, "path") }, void 0, token)
|
|
678
|
-
},
|
|
679
|
-
{
|
|
680
|
-
name: "describe_template",
|
|
681
|
-
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.",
|
|
682
|
-
inputSchema: {
|
|
683
|
-
type: "object",
|
|
684
|
-
properties: {
|
|
685
|
-
name: {
|
|
686
|
-
type: "string",
|
|
687
|
-
description: 'Template name without path or extension, e.g. "article".'
|
|
688
|
-
}
|
|
689
|
-
},
|
|
690
|
-
required: ["name"],
|
|
691
|
-
additionalProperties: false
|
|
692
|
-
},
|
|
693
|
-
toRequest: (args, token) => request("GET", "/api/template-fields", { name: str(args, "name") }, void 0, token)
|
|
694
|
-
},
|
|
695
|
-
{
|
|
696
|
-
name: "write_file",
|
|
697
|
-
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. For template/component paths the response includes `schemaDelta` (added/removed/typeChanged fields) \u2014 if `typeChanged` contains `destructive: true` or `removed` is non-empty, an auto-stamp has been written and authored content may need migration, so surface to the user before further edits. For content/*.json writes the response may include `parseDiagnostics.unknownTopLevelKeys` \u2014 top-level keys the parser dropped because they are not system keys; content values belong under `fields` (system keys are case-sensitive: `isPublished`, not `IsPublished`). See the reference's 'Authoring guardrails for LLMs' section for the common pitfalls (notably: `data-*` annotations bind to the element, not the token inside it \u2014 moving a token out of its annotated container silently strips them).",
|
|
698
|
-
inputSchema: {
|
|
699
|
-
type: "object",
|
|
700
|
-
properties: {
|
|
701
|
-
path: pathArg,
|
|
702
|
-
contents: { type: "string", description: "The full new text contents of the file." }
|
|
703
|
-
},
|
|
704
|
-
required: ["path", "contents"],
|
|
705
|
-
additionalProperties: false
|
|
706
|
-
},
|
|
707
|
-
toRequest: (args, token) => request("POST", "/api/write", { path: str(args, "path") }, str(args, "contents"), token)
|
|
708
|
-
},
|
|
709
|
-
{
|
|
710
|
-
name: "delete_path",
|
|
711
|
-
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.",
|
|
712
|
-
inputSchema: {
|
|
713
|
-
type: "object",
|
|
714
|
-
properties: { path: pathArg },
|
|
715
|
-
required: ["path"],
|
|
716
|
-
additionalProperties: false
|
|
717
|
-
},
|
|
718
|
-
toRequest: (args, token) => request("POST", "/api/delete", { path: str(args, "path") }, void 0, token)
|
|
719
|
-
},
|
|
720
|
-
{
|
|
721
|
-
name: "rename_path",
|
|
722
|
-
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. Renaming a template also requires updating every content item bound to the old name (the validation gate will catch unbound items on publish, but `list_pending_migrations` is the cheaper check).",
|
|
723
|
-
inputSchema: {
|
|
724
|
-
type: "object",
|
|
725
|
-
properties: {
|
|
726
|
-
from: { type: "string", description: "The current path." },
|
|
727
|
-
to: { type: "string", description: "The new path." }
|
|
728
|
-
},
|
|
729
|
-
required: ["from", "to"],
|
|
730
|
-
additionalProperties: false
|
|
731
|
-
},
|
|
732
|
-
toRequest: (args, token) => request(
|
|
733
|
-
"POST",
|
|
734
|
-
"/api/rename",
|
|
735
|
-
{ from: str(args, "from"), to: str(args, "to") },
|
|
736
|
-
void 0,
|
|
737
|
-
token
|
|
738
|
-
)
|
|
739
|
-
},
|
|
740
|
-
{
|
|
741
|
-
name: "validate",
|
|
742
|
-
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.",
|
|
743
|
-
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
744
|
-
toRequest: (_args, token) => request("GET", "/api/validate", {}, void 0, token)
|
|
745
|
-
},
|
|
746
|
-
{
|
|
747
|
-
name: "publish",
|
|
748
|
-
description: "Compile and publish the whole site. Runs the validation gate first; if anything would break, it reports the problems and writes nothing.",
|
|
749
|
-
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
750
|
-
toRequest: (_args, token) => request("POST", "/api/publish", {}, void 0, token)
|
|
751
|
-
},
|
|
752
|
-
{
|
|
753
|
-
name: "list_pending_migrations",
|
|
754
|
-
description: "List content items whose JSON fields don't match their bound template's schema (orphan fields or missing required fields). Use this after a destructive template edit (you'll see a `stamped` record on write_file) to see what needs migrating, or any time to check site-wide drift.",
|
|
755
|
-
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
756
|
-
toRequest: (_args, token) => request("GET", "/api/migrations", {}, void 0, token)
|
|
757
|
-
},
|
|
758
|
-
{
|
|
759
|
-
name: "preview_migration",
|
|
760
|
-
description: 'For one content item, return the current template HTML, the best-fit snapshot HTML (the "before"), the list of orphan fields with their values, and the names of fields the current template defines. Use this to plan how to migrate (drop, rename, or keep each orphan).',
|
|
761
|
-
inputSchema: {
|
|
762
|
-
type: "object",
|
|
763
|
-
properties: { path: pathArg },
|
|
764
|
-
required: ["path"],
|
|
765
|
-
additionalProperties: false
|
|
766
|
-
},
|
|
767
|
-
toRequest: (args, token) => request("GET", "/api/migrations/preview", { path: str(args, "path") }, void 0, token)
|
|
768
|
-
},
|
|
769
|
-
{
|
|
770
|
-
name: "apply_migration",
|
|
771
|
-
description: "Resolve a content item's schema drift in one go. `resolution` is a JSON object: `drop` (array of orphan field names to delete), `rename` (object mapping orphan field name \u2192 current template field name to copy the value into), `keep` (array of orphan field names to leave untouched), and `fill` (object mapping current template field name \u2192 value, for missing required fields). Returns the refreshed pending list.",
|
|
772
|
-
inputSchema: {
|
|
773
|
-
type: "object",
|
|
774
|
-
properties: {
|
|
775
|
-
path: pathArg,
|
|
776
|
-
resolution: {
|
|
777
|
-
type: "string",
|
|
778
|
-
description: 'A JSON object encoded as a string: { "drop": string[], "rename": { from: to }, "keep": string[], "fill": { name: value } }.'
|
|
779
|
-
}
|
|
780
|
-
},
|
|
781
|
-
required: ["path", "resolution"],
|
|
782
|
-
additionalProperties: false
|
|
783
|
-
},
|
|
784
|
-
toRequest: (args, token) => request(
|
|
785
|
-
"POST",
|
|
786
|
-
"/api/migrations/apply",
|
|
787
|
-
{},
|
|
788
|
-
JSON.stringify({
|
|
789
|
-
path: str(args, "path"),
|
|
790
|
-
resolution: parseResolutionArg(str(args, "resolution"))
|
|
791
|
-
}),
|
|
792
|
-
token
|
|
793
|
-
)
|
|
794
|
-
}
|
|
795
|
-
];
|
|
796
|
-
function parseResolutionArg(raw) {
|
|
797
|
-
if (raw === "") return {};
|
|
798
|
-
try {
|
|
799
|
-
return JSON.parse(raw);
|
|
800
|
-
} catch {
|
|
801
|
-
return {};
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
var MCP_TOOLS = TOOL_SPECS.map(
|
|
805
|
-
({ name, description, inputSchema }) => ({ name, description, inputSchema })
|
|
806
|
-
);
|
|
807
|
-
function toResult(response) {
|
|
808
|
-
const text = typeof response.body === "string" ? response.body : response.body === void 0 ? "" : new TextDecoder().decode(response.body);
|
|
809
|
-
return { text, isError: response.status >= 400 };
|
|
810
|
-
}
|
|
811
|
-
async function callMcpTool(deps, name, args = {}, options = {}) {
|
|
812
|
-
const spec = TOOL_SPECS.find((tool) => tool.name === name);
|
|
813
|
-
if (spec === void 0) return { text: `Unknown tool: ${name}`, isError: true };
|
|
814
|
-
return toResult(await handleApi(deps, spec.toRequest(args, options.token)));
|
|
815
|
-
}
|
|
816
|
-
var REFERENCE_URI = "nanoesis://reference";
|
|
817
|
-
var MCP_RESOURCES = [
|
|
818
|
-
{
|
|
819
|
-
uri: REFERENCE_URI,
|
|
820
|
-
name: "nanoesis authoring reference",
|
|
821
|
-
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.",
|
|
822
|
-
mimeType: "text/markdown"
|
|
823
|
-
}
|
|
824
|
-
];
|
|
825
|
-
function readMcpResource(uri) {
|
|
826
|
-
if (uri === REFERENCE_URI) {
|
|
827
|
-
return { text: renderReferenceMarkdown(buildAuthoringReference()), mimeType: "text/markdown" };
|
|
828
|
-
}
|
|
829
|
-
return void 0;
|
|
830
|
-
}
|
|
831
|
-
|
|
832
638
|
// ../editor-api/src/diagnose.ts
|
|
833
639
|
function buildDefaultDiagnostics() {
|
|
834
640
|
const registry = createDiagnosticRegistry();
|
|
@@ -1101,8 +907,313 @@ async function anyPublishedContentItem(store, dir = "content") {
|
|
|
1101
907
|
return false;
|
|
1102
908
|
}
|
|
1103
909
|
|
|
910
|
+
// ../editor-api/src/create-editor.ts
|
|
911
|
+
function asSink(storage) {
|
|
912
|
+
const encoder = new TextEncoder();
|
|
913
|
+
return {
|
|
914
|
+
write: (path, contents) => storage.put(path, typeof contents === "string" ? encoder.encode(contents) : contents)
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
function createEditor(config) {
|
|
918
|
+
const working = new IndexedStore(config.editorFiles);
|
|
919
|
+
const sink = asSink(config.website);
|
|
920
|
+
const wipeBeforePublish = config.wipeBeforePublish ?? true;
|
|
921
|
+
const reconcile = config.enumerate === void 0 ? void 0 : async () => working.reconcile([...await config.enumerate()]);
|
|
922
|
+
const publish = async () => {
|
|
923
|
+
const validation = await validateSite(working);
|
|
924
|
+
if (validation.ok && wipeBeforePublish && config.website.wipe !== void 0) {
|
|
925
|
+
await config.website.wipe();
|
|
926
|
+
}
|
|
927
|
+
const dir = config.users === void 0 ? void 0 : authorDirectory(await config.users());
|
|
928
|
+
const prebuild = typeof config.prebuild === "function" ? await config.prebuild() : config.prebuild;
|
|
929
|
+
return publishSite(working, sink, {
|
|
930
|
+
...config.images !== void 0 && { imageEncoder: config.images },
|
|
931
|
+
...config.purge !== void 0 && { purge: config.purge },
|
|
932
|
+
...config.baseUrl !== void 0 && { baseUrl: config.baseUrl },
|
|
933
|
+
...dir !== void 0 && { authorDirectory: dir },
|
|
934
|
+
...prebuild !== void 0 && { prebuild }
|
|
935
|
+
});
|
|
936
|
+
};
|
|
937
|
+
const users = config.users;
|
|
938
|
+
const deps = {
|
|
939
|
+
store: working,
|
|
940
|
+
identity: config.login,
|
|
941
|
+
publish,
|
|
942
|
+
diagnostics: config.diagnostics ?? buildDefaultDiagnostics(),
|
|
943
|
+
...reconcile !== void 0 && { reconcile },
|
|
944
|
+
...config.authEndpoints !== void 0 && { authEndpoints: config.authEndpoints },
|
|
945
|
+
...config.userAdmin !== void 0 && { userAdmin: config.userAdmin },
|
|
946
|
+
...config.branding !== void 0 && { branding: config.branding },
|
|
947
|
+
...users !== void 0 && { authors: async () => authorOptions(await users()) }
|
|
948
|
+
};
|
|
949
|
+
return {
|
|
950
|
+
handleApi: (req) => handleApi(deps, req),
|
|
951
|
+
publish,
|
|
952
|
+
store: working,
|
|
953
|
+
deps
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
function devNoAuth() {
|
|
957
|
+
console.warn(
|
|
958
|
+
"[nanoesis] devNoAuth(): every request is treated as an admin. Do not use in production."
|
|
959
|
+
);
|
|
960
|
+
return {
|
|
961
|
+
authenticate: async () => ({
|
|
962
|
+
userId: "dev",
|
|
963
|
+
username: "dev",
|
|
964
|
+
roles: ["author", "developer", "admin"]
|
|
965
|
+
})
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// ../editor-api/src/serve-asset.ts
|
|
970
|
+
import { readFile } from "fs/promises";
|
|
971
|
+
import { resolve, sep } from "path";
|
|
972
|
+
var MIME = {
|
|
973
|
+
".html": "text/html; charset=utf-8",
|
|
974
|
+
".js": "application/javascript; charset=utf-8",
|
|
975
|
+
".mjs": "application/javascript; charset=utf-8",
|
|
976
|
+
".css": "text/css; charset=utf-8",
|
|
977
|
+
".json": "application/json; charset=utf-8",
|
|
978
|
+
".svg": "image/svg+xml",
|
|
979
|
+
".png": "image/png",
|
|
980
|
+
".jpg": "image/jpeg",
|
|
981
|
+
".jpeg": "image/jpeg",
|
|
982
|
+
".ico": "image/x-icon",
|
|
983
|
+
".woff": "font/woff",
|
|
984
|
+
".woff2": "font/woff2",
|
|
985
|
+
".map": "application/json; charset=utf-8"
|
|
986
|
+
};
|
|
987
|
+
function mimeFor(path) {
|
|
988
|
+
const dot = path.lastIndexOf(".");
|
|
989
|
+
return dot < 0 ? "application/octet-stream" : MIME[path.slice(dot).toLowerCase()] ?? "application/octet-stream";
|
|
990
|
+
}
|
|
991
|
+
async function serveEditorAsset(distDir, pathname, options = {}) {
|
|
992
|
+
if (pathname === "/config.json" && options.config !== void 0) {
|
|
993
|
+
return {
|
|
994
|
+
status: 200,
|
|
995
|
+
headers: { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" },
|
|
996
|
+
body: JSON.stringify(options.config)
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
const base = resolve(distDir);
|
|
1000
|
+
const requested = pathname === "/" || pathname === "" ? "/index.html" : pathname;
|
|
1001
|
+
const filePath = resolve(base, requested.replace(/^\/+/, ""));
|
|
1002
|
+
if (filePath !== base && !filePath.startsWith(base + sep)) {
|
|
1003
|
+
return { status: 403, headers: { "content-type": "text/plain" }, body: "Forbidden" };
|
|
1004
|
+
}
|
|
1005
|
+
try {
|
|
1006
|
+
const bytes = new Uint8Array(await readFile(filePath));
|
|
1007
|
+
return { status: 200, headers: { "content-type": mimeFor(filePath) }, body: bytes };
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
if (error.code !== "ENOENT") throw error;
|
|
1010
|
+
const indexBytes = new Uint8Array(await readFile(resolve(base, "index.html")));
|
|
1011
|
+
return {
|
|
1012
|
+
status: 200,
|
|
1013
|
+
headers: { "content-type": "text/html; charset=utf-8" },
|
|
1014
|
+
body: indexBytes
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// ../editor-api/src/mcp.ts
|
|
1020
|
+
function str(args, key) {
|
|
1021
|
+
const value = args[key];
|
|
1022
|
+
return typeof value === "string" ? value : "";
|
|
1023
|
+
}
|
|
1024
|
+
function request(method, path, query, bodyText, token) {
|
|
1025
|
+
const body = bodyText === void 0 ? new Uint8Array() : new TextEncoder().encode(bodyText);
|
|
1026
|
+
return {
|
|
1027
|
+
method,
|
|
1028
|
+
path,
|
|
1029
|
+
query: new URLSearchParams(query),
|
|
1030
|
+
getHeader: (name) => name.toLowerCase() === "authorization" && token !== void 0 ? `Bearer ${token}` : void 0,
|
|
1031
|
+
body: async () => body
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
var pathArg = {
|
|
1035
|
+
type: "string",
|
|
1036
|
+
description: 'Site-relative path with forward slashes, e.g. "content/blog/post.json".'
|
|
1037
|
+
};
|
|
1038
|
+
var TOOL_SPECS = [
|
|
1039
|
+
{
|
|
1040
|
+
name: "list_dir",
|
|
1041
|
+
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/.",
|
|
1042
|
+
inputSchema: {
|
|
1043
|
+
type: "object",
|
|
1044
|
+
properties: {
|
|
1045
|
+
path: { type: "string", description: "Directory path, '' for the site root." }
|
|
1046
|
+
},
|
|
1047
|
+
additionalProperties: false
|
|
1048
|
+
},
|
|
1049
|
+
toRequest: (args, token) => request("GET", "/api/list", { dir: str(args, "path") }, void 0, token)
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
name: "read_file",
|
|
1053
|
+
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.",
|
|
1054
|
+
inputSchema: {
|
|
1055
|
+
type: "object",
|
|
1056
|
+
properties: { path: pathArg },
|
|
1057
|
+
required: ["path"],
|
|
1058
|
+
additionalProperties: false
|
|
1059
|
+
},
|
|
1060
|
+
toRequest: (args, token) => request("GET", "/api/read", { path: str(args, "path") }, void 0, token)
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
name: "describe_template",
|
|
1064
|
+
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.",
|
|
1065
|
+
inputSchema: {
|
|
1066
|
+
type: "object",
|
|
1067
|
+
properties: {
|
|
1068
|
+
name: {
|
|
1069
|
+
type: "string",
|
|
1070
|
+
description: 'Template name without path or extension, e.g. "article".'
|
|
1071
|
+
}
|
|
1072
|
+
},
|
|
1073
|
+
required: ["name"],
|
|
1074
|
+
additionalProperties: false
|
|
1075
|
+
},
|
|
1076
|
+
toRequest: (args, token) => request("GET", "/api/template-fields", { name: str(args, "name") }, void 0, token)
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
name: "write_file",
|
|
1080
|
+
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. For template/component paths the response includes `schemaDelta` (added/removed/typeChanged fields) \u2014 if `typeChanged` contains `destructive: true` or `removed` is non-empty, an auto-stamp has been written and authored content may need migration, so surface to the user before further edits. For content/*.json writes the response may include `parseDiagnostics.unknownTopLevelKeys` \u2014 top-level keys the parser dropped because they are not system keys; content values belong under `fields` (system keys are case-sensitive: `isPublished`, not `IsPublished`). See the reference's 'Authoring guardrails for LLMs' section for the common pitfalls (notably: `data-*` annotations bind to the element, not the token inside it \u2014 moving a token out of its annotated container silently strips them).",
|
|
1081
|
+
inputSchema: {
|
|
1082
|
+
type: "object",
|
|
1083
|
+
properties: {
|
|
1084
|
+
path: pathArg,
|
|
1085
|
+
contents: { type: "string", description: "The full new text contents of the file." }
|
|
1086
|
+
},
|
|
1087
|
+
required: ["path", "contents"],
|
|
1088
|
+
additionalProperties: false
|
|
1089
|
+
},
|
|
1090
|
+
toRequest: (args, token) => request("POST", "/api/write", { path: str(args, "path") }, str(args, "contents"), token)
|
|
1091
|
+
},
|
|
1092
|
+
{
|
|
1093
|
+
name: "delete_path",
|
|
1094
|
+
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.",
|
|
1095
|
+
inputSchema: {
|
|
1096
|
+
type: "object",
|
|
1097
|
+
properties: { path: pathArg },
|
|
1098
|
+
required: ["path"],
|
|
1099
|
+
additionalProperties: false
|
|
1100
|
+
},
|
|
1101
|
+
toRequest: (args, token) => request("POST", "/api/delete", { path: str(args, "path") }, void 0, token)
|
|
1102
|
+
},
|
|
1103
|
+
{
|
|
1104
|
+
name: "rename_path",
|
|
1105
|
+
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. Renaming a template also requires updating every content item bound to the old name (the validation gate will catch unbound items on publish, but `list_pending_migrations` is the cheaper check).",
|
|
1106
|
+
inputSchema: {
|
|
1107
|
+
type: "object",
|
|
1108
|
+
properties: {
|
|
1109
|
+
from: { type: "string", description: "The current path." },
|
|
1110
|
+
to: { type: "string", description: "The new path." }
|
|
1111
|
+
},
|
|
1112
|
+
required: ["from", "to"],
|
|
1113
|
+
additionalProperties: false
|
|
1114
|
+
},
|
|
1115
|
+
toRequest: (args, token) => request(
|
|
1116
|
+
"POST",
|
|
1117
|
+
"/api/rename",
|
|
1118
|
+
{ from: str(args, "from"), to: str(args, "to") },
|
|
1119
|
+
void 0,
|
|
1120
|
+
token
|
|
1121
|
+
)
|
|
1122
|
+
},
|
|
1123
|
+
{
|
|
1124
|
+
name: "validate",
|
|
1125
|
+
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.",
|
|
1126
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
1127
|
+
toRequest: (_args, token) => request("GET", "/api/validate", {}, void 0, token)
|
|
1128
|
+
},
|
|
1129
|
+
{
|
|
1130
|
+
name: "publish",
|
|
1131
|
+
description: "Compile and publish the whole site. Runs the validation gate first; if anything would break, it reports the problems and writes nothing.",
|
|
1132
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
1133
|
+
toRequest: (_args, token) => request("POST", "/api/publish", {}, void 0, token)
|
|
1134
|
+
},
|
|
1135
|
+
{
|
|
1136
|
+
name: "list_pending_migrations",
|
|
1137
|
+
description: "List content items whose JSON fields don't match their bound template's schema (orphan fields or missing required fields). Use this after a destructive template edit (you'll see a `stamped` record on write_file) to see what needs migrating, or any time to check site-wide drift.",
|
|
1138
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
1139
|
+
toRequest: (_args, token) => request("GET", "/api/migrations", {}, void 0, token)
|
|
1140
|
+
},
|
|
1141
|
+
{
|
|
1142
|
+
name: "preview_migration",
|
|
1143
|
+
description: 'For one content item, return the current template HTML, the best-fit snapshot HTML (the "before"), the list of orphan fields with their values, and the names of fields the current template defines. Use this to plan how to migrate (drop, rename, or keep each orphan).',
|
|
1144
|
+
inputSchema: {
|
|
1145
|
+
type: "object",
|
|
1146
|
+
properties: { path: pathArg },
|
|
1147
|
+
required: ["path"],
|
|
1148
|
+
additionalProperties: false
|
|
1149
|
+
},
|
|
1150
|
+
toRequest: (args, token) => request("GET", "/api/migrations/preview", { path: str(args, "path") }, void 0, token)
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
name: "apply_migration",
|
|
1154
|
+
description: "Resolve a content item's schema drift in one go. `resolution` is a JSON object: `drop` (array of orphan field names to delete), `rename` (object mapping orphan field name \u2192 current template field name to copy the value into), `keep` (array of orphan field names to leave untouched), and `fill` (object mapping current template field name \u2192 value, for missing required fields). Returns the refreshed pending list.",
|
|
1155
|
+
inputSchema: {
|
|
1156
|
+
type: "object",
|
|
1157
|
+
properties: {
|
|
1158
|
+
path: pathArg,
|
|
1159
|
+
resolution: {
|
|
1160
|
+
type: "string",
|
|
1161
|
+
description: 'A JSON object encoded as a string: { "drop": string[], "rename": { from: to }, "keep": string[], "fill": { name: value } }.'
|
|
1162
|
+
}
|
|
1163
|
+
},
|
|
1164
|
+
required: ["path", "resolution"],
|
|
1165
|
+
additionalProperties: false
|
|
1166
|
+
},
|
|
1167
|
+
toRequest: (args, token) => request(
|
|
1168
|
+
"POST",
|
|
1169
|
+
"/api/migrations/apply",
|
|
1170
|
+
{},
|
|
1171
|
+
JSON.stringify({
|
|
1172
|
+
path: str(args, "path"),
|
|
1173
|
+
resolution: parseResolutionArg(str(args, "resolution"))
|
|
1174
|
+
}),
|
|
1175
|
+
token
|
|
1176
|
+
)
|
|
1177
|
+
}
|
|
1178
|
+
];
|
|
1179
|
+
function parseResolutionArg(raw) {
|
|
1180
|
+
if (raw === "") return {};
|
|
1181
|
+
try {
|
|
1182
|
+
return JSON.parse(raw);
|
|
1183
|
+
} catch {
|
|
1184
|
+
return {};
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
var MCP_TOOLS = TOOL_SPECS.map(
|
|
1188
|
+
({ name, description, inputSchema }) => ({ name, description, inputSchema })
|
|
1189
|
+
);
|
|
1190
|
+
function toResult(response) {
|
|
1191
|
+
const text = typeof response.body === "string" ? response.body : response.body === void 0 ? "" : new TextDecoder().decode(response.body);
|
|
1192
|
+
return { text, isError: response.status >= 400 };
|
|
1193
|
+
}
|
|
1194
|
+
async function callMcpTool(deps, name, args = {}, options = {}) {
|
|
1195
|
+
const spec = TOOL_SPECS.find((tool) => tool.name === name);
|
|
1196
|
+
if (spec === void 0) return { text: `Unknown tool: ${name}`, isError: true };
|
|
1197
|
+
return toResult(await handleApi(deps, spec.toRequest(args, options.token)));
|
|
1198
|
+
}
|
|
1199
|
+
var REFERENCE_URI = "nanoesis://reference";
|
|
1200
|
+
var MCP_RESOURCES = [
|
|
1201
|
+
{
|
|
1202
|
+
uri: REFERENCE_URI,
|
|
1203
|
+
name: "nanoesis authoring reference",
|
|
1204
|
+
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.",
|
|
1205
|
+
mimeType: "text/markdown"
|
|
1206
|
+
}
|
|
1207
|
+
];
|
|
1208
|
+
function readMcpResource(uri) {
|
|
1209
|
+
if (uri === REFERENCE_URI) {
|
|
1210
|
+
return { text: renderReferenceMarkdown(buildAuthoringReference()), mimeType: "text/markdown" };
|
|
1211
|
+
}
|
|
1212
|
+
return void 0;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1104
1215
|
// ../editor-api/src/branding.ts
|
|
1105
|
-
import { mkdir, readFile, rename, rm, writeFile } from "fs/promises";
|
|
1216
|
+
import { mkdir, readFile as readFile2, rename, rm, writeFile } from "fs/promises";
|
|
1106
1217
|
import { dirname, join } from "path";
|
|
1107
1218
|
var EMPTY = { name: null, logo: null };
|
|
1108
1219
|
var InMemoryBrandingStore = class {
|
|
@@ -1144,7 +1255,7 @@ var FileBrandingStore = class {
|
|
|
1144
1255
|
logoPath;
|
|
1145
1256
|
async loadMeta() {
|
|
1146
1257
|
try {
|
|
1147
|
-
const raw = await
|
|
1258
|
+
const raw = await readFile2(this.metaPath, "utf8");
|
|
1148
1259
|
const text = raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
|
|
1149
1260
|
const parsed = JSON.parse(text);
|
|
1150
1261
|
return {
|
|
@@ -1172,7 +1283,7 @@ var FileBrandingStore = class {
|
|
|
1172
1283
|
const meta = await this.loadMeta();
|
|
1173
1284
|
if (meta.logo === null) return null;
|
|
1174
1285
|
try {
|
|
1175
|
-
const bytes = await
|
|
1286
|
+
const bytes = await readFile2(this.logoPath);
|
|
1176
1287
|
return { bytes: new Uint8Array(bytes), contentType: meta.logo.contentType };
|
|
1177
1288
|
} catch (error) {
|
|
1178
1289
|
if (error.code === "ENOENT") return null;
|
|
@@ -1203,10 +1314,6 @@ export {
|
|
|
1203
1314
|
handleApi,
|
|
1204
1315
|
authorOptions,
|
|
1205
1316
|
authorDirectory,
|
|
1206
|
-
MCP_TOOLS,
|
|
1207
|
-
callMcpTool,
|
|
1208
|
-
MCP_RESOURCES,
|
|
1209
|
-
readMcpResource,
|
|
1210
1317
|
buildDefaultDiagnostics,
|
|
1211
1318
|
homeTemplateMissingDiagnostic,
|
|
1212
1319
|
recreateHomeTemplateRepair,
|
|
@@ -1216,6 +1323,13 @@ export {
|
|
|
1216
1323
|
templateSuffixConflictDiagnostic,
|
|
1217
1324
|
copySnapshotToCurrentRepair,
|
|
1218
1325
|
pendingMigrationsDiagnostic,
|
|
1326
|
+
createEditor,
|
|
1327
|
+
devNoAuth,
|
|
1328
|
+
serveEditorAsset,
|
|
1329
|
+
MCP_TOOLS,
|
|
1330
|
+
callMcpTool,
|
|
1331
|
+
MCP_RESOURCES,
|
|
1332
|
+
readMcpResource,
|
|
1219
1333
|
InMemoryBrandingStore,
|
|
1220
1334
|
FileBrandingStore
|
|
1221
1335
|
};
|
package/dist/editor-api.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { WorkingStore, IdentityProvider, PublishResult, ReconcileResult, AuthEndpoints, UserAdminEndpoints, AuthorOption, DiagnosticRegistry, UserSummary, AuthorDirectory, Repair, DiagnosticCheck } from '@nanoesis/engine';
|
|
1
|
+
import { WorkingStore, IdentityProvider, PublishResult, ReconcileResult, AuthEndpoints, UserAdminEndpoints, AuthorOption, DiagnosticRegistry, Storage, ImageEncoder, PurgeService, UserSummary, PreBuildHook, AuthorDirectory, Repair, DiagnosticCheck } from '@nanoesis/engine';
|
|
2
|
+
export { Storage } from '@nanoesis/engine';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Editor white-labelling storage (DESIGN §11, Phase B). This is the *editor app's*
|
|
@@ -158,6 +159,105 @@ interface ApiDeps {
|
|
|
158
159
|
*/
|
|
159
160
|
declare function handleApi(deps: ApiDeps, req: ApiRequest): Promise<ApiResponse>;
|
|
160
161
|
|
|
162
|
+
/** Everything {@link createEditor} needs. Three things are required; the rest light up features. */
|
|
163
|
+
interface EditorConfig {
|
|
164
|
+
/** Where the editor reads and writes your editable files (content, templates, assets). */
|
|
165
|
+
readonly editorFiles: Storage;
|
|
166
|
+
/** Where the built website is written on publish (your live site, a folder, a CDN origin). */
|
|
167
|
+
readonly website: Storage;
|
|
168
|
+
/** Who may edit. Use {@link devNoAuth} locally; swap for a real provider in production. */
|
|
169
|
+
readonly login: IdentityProvider;
|
|
170
|
+
/**
|
|
171
|
+
* Optional full key scan of `editorFiles`. When supplied, the editor can self-heal its
|
|
172
|
+
* content index (`POST /api/reconcile`) and warn about files that bypassed it. Pass the
|
|
173
|
+
* adapter's native listing (e.g. an Azure container's `list`); omit on a store that
|
|
174
|
+
* cannot enumerate.
|
|
175
|
+
*/
|
|
176
|
+
readonly enumerate?: () => Promise<readonly string[]>;
|
|
177
|
+
/** Turns `<img>` into responsive `<picture>` (AVIF/WebP/JPG). Pass the sharp adapter. */
|
|
178
|
+
readonly images?: ImageEncoder;
|
|
179
|
+
/** Clears a CDN cache after a successful publish. */
|
|
180
|
+
readonly purge?: PurgeService;
|
|
181
|
+
/** Absolute site URL; enables `sitemap.xml` and absolute links. */
|
|
182
|
+
readonly baseUrl?: string;
|
|
183
|
+
/** The users a byline may name (DESIGN §6.11): powers the authors picker and bylines. */
|
|
184
|
+
readonly users?: () => Promise<readonly UserSummary[]>;
|
|
185
|
+
/** Editor white-labelling (name + logo). */
|
|
186
|
+
readonly branding?: BrandingStore;
|
|
187
|
+
/** Credential routes (login/refresh/logout); omit for trusted-header / gateway auth. */
|
|
188
|
+
readonly authEndpoints?: AuthEndpoints;
|
|
189
|
+
/** Admin user-management routes; omit when users are managed upstream. */
|
|
190
|
+
readonly userAdmin?: UserAdminEndpoints;
|
|
191
|
+
/**
|
|
192
|
+
* A build step (Tailwind, esbuild, …) run before each publish. Pass a {@link PreBuildHook}
|
|
193
|
+
* for a fixed command, or a function resolved on every publish so a host can pick up a
|
|
194
|
+
* site's build-command change without a restart.
|
|
195
|
+
*/
|
|
196
|
+
readonly prebuild?: PreBuildHook | (() => Promise<PreBuildHook | undefined>);
|
|
197
|
+
/** Admin diagnostics/self-heal; defaults to {@link buildDefaultDiagnostics}. */
|
|
198
|
+
readonly diagnostics?: DiagnosticRegistry;
|
|
199
|
+
/** Clear the website before republishing (default true). Needs `website.wipe`. */
|
|
200
|
+
readonly wipeBeforePublish?: boolean;
|
|
201
|
+
}
|
|
202
|
+
/** A wired editor: mount {@link handleApi} at `/api/*` and call {@link publish} to go live. */
|
|
203
|
+
interface Editor {
|
|
204
|
+
/** Handle one editor API request. Mount at `/api/*` in your HTTP server. */
|
|
205
|
+
handleApi(req: ApiRequest): Promise<ApiResponse>;
|
|
206
|
+
/** Validate, then (optionally wipe and) build the site into the website store. */
|
|
207
|
+
publish(): Promise<PublishResult>;
|
|
208
|
+
/** The underlying working store, for advanced hosts that need direct access. */
|
|
209
|
+
readonly store: WorkingStore;
|
|
210
|
+
/**
|
|
211
|
+
* The assembled {@link ApiDeps}, an escape hatch for advanced transports (e.g. the MCP
|
|
212
|
+
* server) that dispatch through the dependencies directly rather than via `handleApi`.
|
|
213
|
+
* Most hosts ignore this.
|
|
214
|
+
*/
|
|
215
|
+
readonly deps: ApiDeps;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Wire a complete nanoesis editor from a storage pair, a login, and a few optional
|
|
219
|
+
* capabilities. This is the whole integration surface (DESIGN §11c): everything that used
|
|
220
|
+
* to be hand-assembled per host, the content index over the store, the publish source/sink,
|
|
221
|
+
* index reconcile, the wipe-before-publish, and the {@link ApiDeps} bag, is built here, so
|
|
222
|
+
* internals (`IndexedStore`, `ArtifactSink`, reconcile plumbing) never reach the adopter.
|
|
223
|
+
*
|
|
224
|
+
* `publish()` validates **before** it wipes: an invalid site returns its errors and never
|
|
225
|
+
* touches the live files, so a failed publish can never blank the website (the footgun the
|
|
226
|
+
* old "host wipes, then publishes" wiring carried).
|
|
227
|
+
*/
|
|
228
|
+
declare function createEditor(config: EditorConfig): Editor;
|
|
229
|
+
/**
|
|
230
|
+
* An {@link IdentityProvider} that treats every request as a full admin, no login. For
|
|
231
|
+
* **local development only**, it prints a warning on use. Swap for a real provider (e.g.
|
|
232
|
+
* `@nanoesis/adapter-local-jwt`) before exposing the editor to anyone.
|
|
233
|
+
*/
|
|
234
|
+
declare function devNoAuth(): IdentityProvider;
|
|
235
|
+
|
|
236
|
+
/** A transport-agnostic response: a host maps this onto its own framework's response. */
|
|
237
|
+
interface AssetResponse {
|
|
238
|
+
readonly status: number;
|
|
239
|
+
readonly headers: Record<string, string>;
|
|
240
|
+
readonly body: Uint8Array | string;
|
|
241
|
+
}
|
|
242
|
+
/** Options for {@link serveEditorAsset}. */
|
|
243
|
+
interface ServeEditorOptions {
|
|
244
|
+
/**
|
|
245
|
+
* Served verbatim as `/config.json` (JSON, never cached). The editor SPA fetches this at
|
|
246
|
+
* boot to learn its API base URL, e.g. `{ apiUrl: 'https://editor.example.com' }`. Omit
|
|
247
|
+
* for a same-origin host where the SPA and API share a domain.
|
|
248
|
+
*/
|
|
249
|
+
readonly config?: Record<string, unknown>;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Serve one request for the bundled editor SPA from `distDir` (the `editorDist` shipped in
|
|
253
|
+
* `@xtrable-ltd/nanoesis`). Returns the requested file, or falls back to `index.html` for
|
|
254
|
+
* any unknown path so the editor's client-side router takes over (DESIGN §11c). Mount it on
|
|
255
|
+
* every non-`/api/*` GET. A path that escapes `distDir` is refused (403).
|
|
256
|
+
*
|
|
257
|
+
* This is the SPA-serving glue every host used to hand-write; one helper replaces it.
|
|
258
|
+
*/
|
|
259
|
+
declare function serveEditorAsset(distDir: string, pathname: string, options?: ServeEditorOptions): Promise<AssetResponse>;
|
|
260
|
+
|
|
161
261
|
/** The byline picker's options, display name + stable handle, sorted by display name. */
|
|
162
262
|
declare function authorOptions(users: readonly UserSummary[]): AuthorOption[];
|
|
163
263
|
/**
|
|
@@ -323,4 +423,4 @@ declare const copySnapshotToCurrentRepair: Repair;
|
|
|
323
423
|
*/
|
|
324
424
|
declare const pendingMigrationsDiagnostic: DiagnosticCheck;
|
|
325
425
|
|
|
326
|
-
export { type ApiDeps, type ApiRequest, type ApiResponse, type BrandingLogo, type BrandingLogoMeta, type BrandingState, type BrandingStore, FileBrandingStore, InMemoryBrandingStore, MCP_RESOURCES, MCP_TOOLS, type McpCallOptions, type McpResourceDef, type McpToolDef, type McpToolResult, SCAFFOLD_FILES, authorDirectory, authorOptions, buildDefaultDiagnostics, callMcpTool, copySnapshotToCurrentRepair, handleApi, homeTemplateMissingDiagnostic, noPublishedContentDiagnostic, pendingMigrationsDiagnostic, readMcpResource, rebindItemToCurrentRepair, recreateHomeTemplateRepair, templateSnapshotIntegrityDiagnostic, templateSuffixConflictDiagnostic };
|
|
426
|
+
export { type ApiDeps, type ApiRequest, type ApiResponse, type AssetResponse, type BrandingLogo, type BrandingLogoMeta, type BrandingState, type BrandingStore, type Editor, type EditorConfig, FileBrandingStore, InMemoryBrandingStore, MCP_RESOURCES, MCP_TOOLS, type McpCallOptions, type McpResourceDef, type McpToolDef, type McpToolResult, SCAFFOLD_FILES, type ServeEditorOptions, authorDirectory, authorOptions, buildDefaultDiagnostics, callMcpTool, copySnapshotToCurrentRepair, createEditor, devNoAuth, handleApi, homeTemplateMissingDiagnostic, noPublishedContentDiagnostic, pendingMigrationsDiagnostic, readMcpResource, rebindItemToCurrentRepair, recreateHomeTemplateRepair, serveEditorAsset, templateSnapshotIntegrityDiagnostic, templateSuffixConflictDiagnostic };
|
package/dist/editor-api.js
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
buildDefaultDiagnostics,
|
|
10
10
|
callMcpTool,
|
|
11
11
|
copySnapshotToCurrentRepair,
|
|
12
|
+
createEditor,
|
|
13
|
+
devNoAuth,
|
|
12
14
|
handleApi,
|
|
13
15
|
homeTemplateMissingDiagnostic,
|
|
14
16
|
noPublishedContentDiagnostic,
|
|
@@ -16,9 +18,10 @@ import {
|
|
|
16
18
|
readMcpResource,
|
|
17
19
|
rebindItemToCurrentRepair,
|
|
18
20
|
recreateHomeTemplateRepair,
|
|
21
|
+
serveEditorAsset,
|
|
19
22
|
templateSnapshotIntegrityDiagnostic,
|
|
20
23
|
templateSuffixConflictDiagnostic
|
|
21
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-SHGYQTZ7.js";
|
|
22
25
|
import "./chunk-WHK3SBV7.js";
|
|
23
26
|
export {
|
|
24
27
|
FileBrandingStore,
|
|
@@ -31,6 +34,8 @@ export {
|
|
|
31
34
|
buildDefaultDiagnostics,
|
|
32
35
|
callMcpTool,
|
|
33
36
|
copySnapshotToCurrentRepair,
|
|
37
|
+
createEditor,
|
|
38
|
+
devNoAuth,
|
|
34
39
|
handleApi,
|
|
35
40
|
homeTemplateMissingDiagnostic,
|
|
36
41
|
noPublishedContentDiagnostic,
|
|
@@ -38,6 +43,7 @@ export {
|
|
|
38
43
|
readMcpResource,
|
|
39
44
|
rebindItemToCurrentRepair,
|
|
40
45
|
recreateHomeTemplateRepair,
|
|
46
|
+
serveEditorAsset,
|
|
41
47
|
templateSnapshotIntegrityDiagnostic,
|
|
42
48
|
templateSuffixConflictDiagnostic
|
|
43
49
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -154,6 +154,22 @@ interface BlobStore {
|
|
|
154
154
|
/** Remove `key`. Deleting an absent key is a no-op (idempotent). */
|
|
155
155
|
delete(key: string): Promise<void>;
|
|
156
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* The single storage contract an adopter implements (DESIGN §11c): a {@link BlobStore}
|
|
159
|
+
* (get/put/delete) plus an optional {@link wipe}. The same shape backs both the editor's
|
|
160
|
+
* working files and the published website, so there is one interface to learn, not a
|
|
161
|
+
* separate read port and write sink. `createEditor` (in `@nanoesis/editor-api`) consumes
|
|
162
|
+
* this; the ready-made adapters (e.g. `folderStorage`) implement it, so most adopters write
|
|
163
|
+
* none of it.
|
|
164
|
+
*/
|
|
165
|
+
interface Storage extends BlobStore {
|
|
166
|
+
/**
|
|
167
|
+
* Optional: remove everything in the store. Used to clear the previous publish before
|
|
168
|
+
* re-emitting the website, so deleted pages disappear. A store that cannot enumerate
|
|
169
|
+
* (and so cannot clear itself) omits it; the publish then only overwrites.
|
|
170
|
+
*/
|
|
171
|
+
wipe?(): Promise<void>;
|
|
172
|
+
}
|
|
157
173
|
/**
|
|
158
174
|
* In-memory {@link BlobStore} backed by a plain map, the test double the engine's
|
|
159
175
|
* store and index tests run against (the LSP guarantee that real adapters are
|
|
@@ -1698,4 +1714,4 @@ declare function createDiagnosticRegistry(): DiagnosticRegistry;
|
|
|
1698
1714
|
|
|
1699
1715
|
declare const workingStoreRoundTripDiagnostic: Diagnostic;
|
|
1700
1716
|
|
|
1701
|
-
export { type Artifact, type ArtifactSink, type AuthEndpoints, type AuthResult, type AuthorDirectory, type AuthorEntry, type AuthorOption, type AuthorRef, type AuthoringReference, type BlobStore, type BoundItem, type ChangePasswordRequest, type ChangePasswordSuccess, type CollectionConfig, type CollectionQuery, type CompileInput, type CompilePageOptions, type CompileSiteOptions, type CompiledPage, type ComponentMap, type ContentIndex, type ContentItem, ContentParseError, type ContentSource, type CreateTokenSuccess, type CreateUserRequest, DEFAULT_DIRS, DOCUMENT_SHELL, type DerivedField, type DiagnoseDeps, type Severity as DiagnoseSeverity, type Source as DiagnoseSource, type Diagnostic$1 as Diagnostic, type Diagnostic as DiagnosticCheck, type DiagnosticRegistry, type DirEntry, type DirNode, type EncodeRequest, type EncodedImage, type EncodedVariant, type EntryKind, FIELD_TYPES, type FieldPrimitive, type FieldRecord, type FieldType, type FieldTypeDef, type FieldValue, type Finding, type IdentityProvider, type ImageEncoder, type ImageFormat, type ImageInfo, InMemoryArtifactSink, InMemoryBlobStore, InMemoryContentSource, IndexedStore, type ItemNode, type LengthConstraints, type LoginRequest, type LoginSuccess, type MediaResolver, type MigrationResolution, type PageEntry, type PendingMigrationItem, type PendingMigrations, type PreBuildHook, type Principal, type PublishOptions, type PublishResult, type PublishSummary, type PurgeService, RESERVED_PREFIX, type ReconcileResult, type RedirectRule, type ReferenceContext, type ReferenceEntry, type ReferenceSection, type RefreshSuccess, type RenameResult, type Repair, type RepairArgs, type ResetPasswordRequest, type ResolveContext, type Role, type RssOptions, type SchemaDelta, type SchemaFieldRef, type Scope, type Severity$1 as Severity, type SiteConfig, type SortFile, type StampDecision, type StampRecord, type TemplateAnalysis, type TemplateKind, type TokenContext, type TokenRef, type TreeNode, type TypeChange, type UpdateUserRequest, type UserAdminEndpoints, type UserSummary, type ValidationResult, type ValueKind, type WorkingStore, analyzeTemplate, applyMigration, baseTemplateName, bestFitSnapshot, buildAuthoringReference, buildContentIndex, buildPictureMarkup, buildRedirects, buildResolveContext, buildRss, buildSitemap, canEdit, compilePage, compileSite, compileTemplate, computeSchemaDelta, contentHash, contentTypeFor, createDiagnosticRegistry, deriveFields, detectStamp, emptyIndex, escapeHtmlAttribute, escapeHtmlText, escapeJsonStringContent, findTokens, hasRole, humanize, inferControl, isDestructiveTypeChange, isFieldType, isReservedVersionedPath, isVersionedTemplateName, joinAuthors, loadComponentScripts, loadComponentStyles, loadComponents, loadContentTree, loadDocumentShell, loadIndex, loadRedirects, loadSiteConfig, loadTemplate, nextVersionNumber, noopPurgeService, outputPathForItem, parseContentItem, parseRedirects, parseSortFile, pendingMigrations, processImage, publishSite, reconcileIndex, renderAuthors, renderReferenceMarkdown, sanitizeUrl, saveIndex, slugify, snapshotName, textContent, toAuthorRefs, urlForItem, validateSite, valueKindOf, versionNumber, wholeValueToken, workingStoreRoundTripDiagnostic };
|
|
1717
|
+
export { type Artifact, type ArtifactSink, type AuthEndpoints, type AuthResult, type AuthorDirectory, type AuthorEntry, type AuthorOption, type AuthorRef, type AuthoringReference, type BlobStore, type BoundItem, type ChangePasswordRequest, type ChangePasswordSuccess, type CollectionConfig, type CollectionQuery, type CompileInput, type CompilePageOptions, type CompileSiteOptions, type CompiledPage, type ComponentMap, type ContentIndex, type ContentItem, ContentParseError, type ContentSource, type CreateTokenSuccess, type CreateUserRequest, DEFAULT_DIRS, DOCUMENT_SHELL, type DerivedField, type DiagnoseDeps, type Severity as DiagnoseSeverity, type Source as DiagnoseSource, type Diagnostic$1 as Diagnostic, type Diagnostic as DiagnosticCheck, type DiagnosticRegistry, type DirEntry, type DirNode, type EncodeRequest, type EncodedImage, type EncodedVariant, type EntryKind, FIELD_TYPES, type FieldPrimitive, type FieldRecord, type FieldType, type FieldTypeDef, type FieldValue, type Finding, type IdentityProvider, type ImageEncoder, type ImageFormat, type ImageInfo, InMemoryArtifactSink, InMemoryBlobStore, InMemoryContentSource, IndexedStore, type ItemNode, type LengthConstraints, type LoginRequest, type LoginSuccess, type MediaResolver, type MigrationResolution, type PageEntry, type PendingMigrationItem, type PendingMigrations, type PreBuildHook, type Principal, type PublishOptions, type PublishResult, type PublishSummary, type PurgeService, RESERVED_PREFIX, type ReconcileResult, type RedirectRule, type ReferenceContext, type ReferenceEntry, type ReferenceSection, type RefreshSuccess, type RenameResult, type Repair, type RepairArgs, type ResetPasswordRequest, type ResolveContext, type Role, type RssOptions, type SchemaDelta, type SchemaFieldRef, type Scope, type Severity$1 as Severity, type SiteConfig, type SortFile, type StampDecision, type StampRecord, type Storage, type TemplateAnalysis, type TemplateKind, type TokenContext, type TokenRef, type TreeNode, type TypeChange, type UpdateUserRequest, type UserAdminEndpoints, type UserSummary, type ValidationResult, type ValueKind, type WorkingStore, analyzeTemplate, applyMigration, baseTemplateName, bestFitSnapshot, buildAuthoringReference, buildContentIndex, buildPictureMarkup, buildRedirects, buildResolveContext, buildRss, buildSitemap, canEdit, compilePage, compileSite, compileTemplate, computeSchemaDelta, contentHash, contentTypeFor, createDiagnosticRegistry, deriveFields, detectStamp, emptyIndex, escapeHtmlAttribute, escapeHtmlText, escapeJsonStringContent, findTokens, hasRole, humanize, inferControl, isDestructiveTypeChange, isFieldType, isReservedVersionedPath, isVersionedTemplateName, joinAuthors, loadComponentScripts, loadComponentStyles, loadComponents, loadContentTree, loadDocumentShell, loadIndex, loadRedirects, loadSiteConfig, loadTemplate, nextVersionNumber, noopPurgeService, outputPathForItem, parseContentItem, parseRedirects, parseSortFile, pendingMigrations, processImage, publishSite, reconcileIndex, renderAuthors, renderReferenceMarkdown, sanitizeUrl, saveIndex, slugify, snapshotName, textContent, toAuthorRefs, urlForItem, validateSite, valueKindOf, versionNumber, wholeValueToken, workingStoreRoundTripDiagnostic };
|
package/dist/mcp.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
MCP_TOOLS,
|
|
4
4
|
callMcpTool,
|
|
5
5
|
readMcpResource
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SHGYQTZ7.js";
|
|
7
7
|
import "./chunk-WHK3SBV7.js";
|
|
8
8
|
|
|
9
9
|
// ../../hosts/host-mcp/src/http.ts
|
|
@@ -56,7 +56,7 @@ function createMcpServer(deps, identity) {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// ../../hosts/host-mcp/src/http.ts
|
|
59
|
-
var DEFAULT_VERSION = true ? "0.1.
|
|
59
|
+
var DEFAULT_VERSION = true ? "0.1.22" : "0.0.0-workspace";
|
|
60
60
|
async function handleMcpRequest(deps, request, opts) {
|
|
61
61
|
const server = createMcpServer(deps, {
|
|
62
62
|
name: opts?.name ?? "nanoesis",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xtrable-ltd/nanoesis",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "nanoesis: a static-first publishing compiler. The engine, the mountable editor API, the storage/image/auth adapters, and the editor UI bundle, in one package (DESIGN 11c).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Xtrable Ltd",
|