@xtrable-ltd/nanoesis 0.1.31 → 0.1.33
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 +41 -4
- package/dist/adapter-azure-blob.js +90 -4
- package/dist/{chunk-BCWZRKMF.js → chunk-C3QPGXG5.js} +14 -4
- package/dist/{chunk-GFQT7BYP.js → chunk-P6NDWIKK.js} +201 -83
- package/dist/editor-api.d.ts +11 -5
- package/dist/editor-api.js +2 -2
- package/dist/index.d.ts +102 -2
- package/dist/index.js +9 -1
- package/dist/mcp.js +3 -3
- package/editor/assets/{MigrationsPane-Drxje7Nq.js → MigrationsPane-BuaKhsie.js} +1 -1
- package/editor/assets/{TemplatesPane-CxO1IknP.js → TemplatesPane-Bg_b0htA.js} +7 -7
- package/editor/assets/{cssMode-BPla51Av.js → cssMode-Bcwjp8T3.js} +1 -1
- package/editor/assets/{freemarker2-Bob6Bse-.js → freemarker2-Cgqj3dEx.js} +1 -1
- package/editor/assets/{handlebars-DsQdZgzp.js → handlebars-CLf2-RZf.js} +1 -1
- package/editor/assets/{html-BNBtLdPe.js → html-BrWGWRh6.js} +1 -1
- package/editor/assets/{htmlMode-6kyy4G1O.js → htmlMode-D9skx8gp.js} +1 -1
- package/editor/assets/index-CY5Kehuu.js +142 -0
- package/editor/assets/{javascript-Dx8xuybD.js → javascript-KaABDboM.js} +1 -1
- package/editor/assets/{jsonMode-DWRGUqMk.js → jsonMode-0usLs2ME.js} +1 -1
- package/editor/assets/{liquid-DlIa11aQ.js → liquid-DYna-Clp.js} +1 -1
- package/editor/assets/{mdx-DR0q2TJm.js → mdx-f7LAIls6.js} +1 -1
- package/editor/assets/{python-Cbswpzux.js → python-Bh21_mvq.js} +1 -1
- package/editor/assets/{razor-IYEYh5Ox.js → razor-D_530Ymg.js} +1 -1
- package/editor/assets/{tsMode-DQrbmWSC.js → tsMode-CTRVjt9n.js} +1 -1
- package/editor/assets/{typescript-DiBU2jVJ.js → typescript-D084tkOf.js} +1 -1
- package/editor/assets/{xml-CCxs0QRL.js → xml-BFulX1C6.js} +1 -1
- package/editor/assets/{yaml-C1tH0_iR.js → yaml-CgZsYmUt.js} +1 -1
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/editor/assets/index-BG-8SSzq.js +0 -142
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ContainerClient } from '@azure/storage-blob';
|
|
2
|
-
import { BlobStore, ArtifactSink, Storage } from '@nanoesis/engine';
|
|
2
|
+
import { BlobStore, ConditionalBlobStore, ArtifactSink, Storage } from '@nanoesis/engine';
|
|
3
3
|
export { contentTypeFor } from '@nanoesis/engine';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -28,6 +28,23 @@ interface BlobContainer {
|
|
|
28
28
|
write(name: string, data: Uint8Array, contentType: string, cacheControl?: string): Promise<void>;
|
|
29
29
|
/** Delete a blob; a missing blob is a no-op (idempotent). */
|
|
30
30
|
remove(name: string): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* A blob's bytes plus its current version tag (the Azure ETag), or null if absent. Pairs
|
|
33
|
+
* with {@link writeConditional} for the optimistic-concurrency the content index needs
|
|
34
|
+
* (DESIGN §11d): the bytes and the tag are captured together so the tag always describes
|
|
35
|
+
* the bytes returned.
|
|
36
|
+
*/
|
|
37
|
+
readVersioned(name: string): Promise<{
|
|
38
|
+
bytes: Uint8Array;
|
|
39
|
+
version: string;
|
|
40
|
+
} | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Write a blob only if its current version tag matches `expected` (or, when `expected` is
|
|
43
|
+
* null, only if it does not yet exist). Resolves to the new version tag on success, or
|
|
44
|
+
* `undefined` when the precondition failed because another writer got there first. A
|
|
45
|
+
* non-precondition failure still rejects.
|
|
46
|
+
*/
|
|
47
|
+
writeConditional(name: string, data: Uint8Array, contentType: string, expected: string | null): Promise<string | undefined>;
|
|
31
48
|
}
|
|
32
49
|
/**
|
|
33
50
|
* An in-memory {@link BlobContainer} backed by a name→bytes map. The test double for
|
|
@@ -36,11 +53,20 @@ interface BlobContainer {
|
|
|
36
53
|
declare class InMemoryBlobContainer implements BlobContainer {
|
|
37
54
|
private readonly blobs;
|
|
38
55
|
private readonly cacheControls;
|
|
56
|
+
/** Per-blob version, bumped on every write, the in-memory stand-in for an ETag. */
|
|
57
|
+
private readonly versions;
|
|
58
|
+
private nextVersion;
|
|
39
59
|
constructor(seed?: Readonly<Record<string, Uint8Array | string>>);
|
|
60
|
+
private store;
|
|
40
61
|
list(prefix: string): Promise<readonly string[]>;
|
|
41
62
|
read(name: string): Promise<Uint8Array | null>;
|
|
42
63
|
write(name: string, data: Uint8Array, contentType?: string, cacheControl?: string): Promise<void>;
|
|
43
64
|
remove(name: string): Promise<void>;
|
|
65
|
+
readVersioned(name: string): Promise<{
|
|
66
|
+
bytes: Uint8Array;
|
|
67
|
+
version: string;
|
|
68
|
+
} | null>;
|
|
69
|
+
writeConditional(name: string, data: Uint8Array, contentType: string, expected: string | null): Promise<string | undefined>;
|
|
44
70
|
/** Test helper: the current blob names. */
|
|
45
71
|
get names(): readonly string[];
|
|
46
72
|
/** Test helper: the `Cache-Control` a blob was written with, if any. */
|
|
@@ -65,6 +91,11 @@ declare class AzureBlobContainer implements BlobContainer {
|
|
|
65
91
|
read(name: string): Promise<Uint8Array | null>;
|
|
66
92
|
write(name: string, data: Uint8Array, contentType: string, cacheControl?: string): Promise<void>;
|
|
67
93
|
remove(name: string): Promise<void>;
|
|
94
|
+
readVersioned(name: string): Promise<{
|
|
95
|
+
bytes: Uint8Array;
|
|
96
|
+
version: string;
|
|
97
|
+
} | null>;
|
|
98
|
+
writeConditional(name: string, data: Uint8Array, contentType: string, expected: string | null): Promise<string | undefined>;
|
|
68
99
|
}
|
|
69
100
|
|
|
70
101
|
/**
|
|
@@ -74,12 +105,17 @@ declare class AzureBlobContainer implements BlobContainer {
|
|
|
74
105
|
* in-memory container in tests (LSP). Keys are POSIX-style; `put` stamps a content type
|
|
75
106
|
* from the key's extension so a published blob serves correctly as a static-website file.
|
|
76
107
|
*/
|
|
77
|
-
declare class BlobContainerStore implements BlobStore {
|
|
108
|
+
declare class BlobContainerStore implements BlobStore, ConditionalBlobStore {
|
|
78
109
|
private readonly container;
|
|
79
110
|
constructor(container: BlobContainer);
|
|
80
111
|
get(key: string): Promise<Uint8Array | undefined>;
|
|
81
112
|
put(key: string, bytes: Uint8Array, cacheControl?: string): Promise<void>;
|
|
82
113
|
delete(key: string): Promise<void>;
|
|
114
|
+
getVersioned(key: string): Promise<{
|
|
115
|
+
bytes: Uint8Array;
|
|
116
|
+
version: string;
|
|
117
|
+
} | undefined>;
|
|
118
|
+
putConditional(key: string, bytes: Uint8Array, expected: string | null): Promise<string | undefined>;
|
|
83
119
|
}
|
|
84
120
|
|
|
85
121
|
/**
|
|
@@ -99,12 +135,13 @@ declare class BlobArtifactSink implements ArtifactSink {
|
|
|
99
135
|
|
|
100
136
|
/**
|
|
101
137
|
* A ready-made {@link Storage} over an Azure Blob container: get/put/delete (via
|
|
102
|
-
* {@link BlobContainerStore}) plus `wipe` (delete every blob)
|
|
138
|
+
* {@link BlobContainerStore}) plus `wipe` (delete every blob) and `prune` (delete every blob
|
|
139
|
+
* not in a keep set, the zero-downtime publish sweep). Pass it to `createEditor`
|
|
103
140
|
* as `editorFiles` (the working container) and `website` (the published `$web` container).
|
|
104
141
|
* For the working container, also pass `enumerate: () => container.list('')` so the editor
|
|
105
142
|
* can reconcile its content index.
|
|
106
143
|
*/
|
|
107
|
-
declare function azureStorage(container: BlobContainer): Storage;
|
|
144
|
+
declare function azureStorage(container: BlobContainer): Storage & ConditionalBlobStore;
|
|
108
145
|
|
|
109
146
|
/**
|
|
110
147
|
* Pure path/name helper for the blob adapters. Blob storage is a *flat* namespace, a blob
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
2
|
contentTypeFor
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-P6NDWIKK.js";
|
|
4
4
|
|
|
5
5
|
// ../../adapters/azure-blob/src/container.ts
|
|
6
6
|
var InMemoryBlobContainer = class {
|
|
7
7
|
blobs = /* @__PURE__ */ new Map();
|
|
8
8
|
cacheControls = /* @__PURE__ */ new Map();
|
|
9
|
+
/** Per-blob version, bumped on every write, the in-memory stand-in for an ETag. */
|
|
10
|
+
versions = /* @__PURE__ */ new Map();
|
|
11
|
+
nextVersion = 1;
|
|
9
12
|
constructor(seed = {}) {
|
|
10
13
|
for (const [name, value] of Object.entries(seed)) {
|
|
11
|
-
this.
|
|
14
|
+
this.store(name, typeof value === "string" ? new TextEncoder().encode(value) : value);
|
|
12
15
|
}
|
|
13
16
|
}
|
|
17
|
+
store(name, bytes) {
|
|
18
|
+
this.blobs.set(name, bytes);
|
|
19
|
+
const version = this.nextVersion;
|
|
20
|
+
this.nextVersion += 1;
|
|
21
|
+
this.versions.set(name, version);
|
|
22
|
+
return version;
|
|
23
|
+
}
|
|
14
24
|
async list(prefix) {
|
|
15
25
|
const names = [];
|
|
16
26
|
for (const name of this.blobs.keys()) {
|
|
@@ -23,11 +33,24 @@ var InMemoryBlobContainer = class {
|
|
|
23
33
|
}
|
|
24
34
|
async write(name, data, contentType, cacheControl) {
|
|
25
35
|
void contentType;
|
|
26
|
-
this.
|
|
36
|
+
this.store(name, data);
|
|
27
37
|
if (cacheControl !== void 0) this.cacheControls.set(name, cacheControl);
|
|
28
38
|
}
|
|
29
39
|
async remove(name) {
|
|
30
40
|
this.blobs.delete(name);
|
|
41
|
+
this.versions.delete(name);
|
|
42
|
+
}
|
|
43
|
+
async readVersioned(name) {
|
|
44
|
+
const bytes = this.blobs.get(name);
|
|
45
|
+
if (bytes === void 0) return null;
|
|
46
|
+
return { bytes, version: String(this.versions.get(name)) };
|
|
47
|
+
}
|
|
48
|
+
async writeConditional(name, data, contentType, expected) {
|
|
49
|
+
void contentType;
|
|
50
|
+
const current = this.versions.get(name);
|
|
51
|
+
const currentTag = current === void 0 ? null : String(current);
|
|
52
|
+
if (currentTag !== expected) return void 0;
|
|
53
|
+
return String(this.store(name, data));
|
|
31
54
|
}
|
|
32
55
|
/** Test helper: the current blob names. */
|
|
33
56
|
get names() {
|
|
@@ -41,8 +64,15 @@ var InMemoryBlobContainer = class {
|
|
|
41
64
|
|
|
42
65
|
// ../../adapters/azure-blob/src/azure-container.ts
|
|
43
66
|
import { BlobServiceClient } from "@azure/storage-blob";
|
|
67
|
+
function statusCodeOf(error) {
|
|
68
|
+
return typeof error === "object" && error !== null && "statusCode" in error ? error.statusCode : void 0;
|
|
69
|
+
}
|
|
44
70
|
function isNotFound(error) {
|
|
45
|
-
return
|
|
71
|
+
return statusCodeOf(error) === 404;
|
|
72
|
+
}
|
|
73
|
+
function isPreconditionFailure(error) {
|
|
74
|
+
const status = statusCodeOf(error);
|
|
75
|
+
return status === 412 || status === 409;
|
|
46
76
|
}
|
|
47
77
|
var AzureBlobContainer = class _AzureBlobContainer {
|
|
48
78
|
constructor(client) {
|
|
@@ -85,7 +115,41 @@ var AzureBlobContainer = class _AzureBlobContainer {
|
|
|
85
115
|
async remove(name) {
|
|
86
116
|
await this.client.getBlockBlobClient(name).deleteIfExists();
|
|
87
117
|
}
|
|
118
|
+
async readVersioned(name) {
|
|
119
|
+
try {
|
|
120
|
+
const response = await this.client.getBlockBlobClient(name).download();
|
|
121
|
+
const buffer = await streamToBuffer(response.readableStreamBody);
|
|
122
|
+
if (response.etag === void 0) {
|
|
123
|
+
throw new Error(`Azure blob ${name} returned no ETag on download`);
|
|
124
|
+
}
|
|
125
|
+
return { bytes: new Uint8Array(buffer), version: response.etag };
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (isNotFound(error)) return null;
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async writeConditional(name, data, contentType, expected) {
|
|
132
|
+
const conditions = expected === null ? { ifNoneMatch: "*" } : { ifMatch: expected };
|
|
133
|
+
try {
|
|
134
|
+
const response = await this.client.getBlockBlobClient(name).uploadData(Buffer.from(data), {
|
|
135
|
+
blobHTTPHeaders: { blobContentType: contentType },
|
|
136
|
+
conditions
|
|
137
|
+
});
|
|
138
|
+
return response.etag;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (isPreconditionFailure(error)) return void 0;
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
88
144
|
};
|
|
145
|
+
async function streamToBuffer(stream) {
|
|
146
|
+
if (stream === void 0) return Buffer.alloc(0);
|
|
147
|
+
const chunks = [];
|
|
148
|
+
for await (const chunk of stream) {
|
|
149
|
+
chunks.push(chunk instanceof Buffer ? chunk : Buffer.from(chunk));
|
|
150
|
+
}
|
|
151
|
+
return Buffer.concat(chunks);
|
|
152
|
+
}
|
|
89
153
|
|
|
90
154
|
// ../../adapters/azure-blob/src/paths.ts
|
|
91
155
|
function normalizeBlobName(path) {
|
|
@@ -108,6 +172,13 @@ var BlobContainerStore = class {
|
|
|
108
172
|
async delete(key) {
|
|
109
173
|
await this.container.remove(normalizeBlobName(key));
|
|
110
174
|
}
|
|
175
|
+
async getVersioned(key) {
|
|
176
|
+
return await this.container.readVersioned(normalizeBlobName(key)) ?? void 0;
|
|
177
|
+
}
|
|
178
|
+
async putConditional(key, bytes, expected) {
|
|
179
|
+
const name = normalizeBlobName(key);
|
|
180
|
+
return this.container.writeConditional(name, bytes, contentTypeFor(name), expected);
|
|
181
|
+
}
|
|
111
182
|
};
|
|
112
183
|
|
|
113
184
|
// ../../adapters/azure-blob/src/blob-sink.ts
|
|
@@ -138,8 +209,23 @@ function azureStorage(container) {
|
|
|
138
209
|
// strips the publish Cache-Control before it reaches the blob.
|
|
139
210
|
put: (key, bytes, cacheControl) => store.put(key, bytes, cacheControl),
|
|
140
211
|
delete: (key) => store.delete(key),
|
|
212
|
+
// Optimistic-concurrency capability (DESIGN §11d): surfaced so a horizontally-scaled
|
|
213
|
+
// host's IndexedStore compare-and-sets the content index instead of blind-overwriting
|
|
214
|
+
// it, which is what stops two instances losing each other's updates
|
|
215
|
+
// (NANOESIS-MCP-ISSUES Issue 1). Azure blobs back this with native ETag conditions.
|
|
216
|
+
getVersioned: (key) => store.getVersioned(key),
|
|
217
|
+
putConditional: (key, bytes, expected) => store.putConditional(key, bytes, expected),
|
|
141
218
|
wipe: async () => {
|
|
142
219
|
for (const name of await container.list("")) await container.remove(name);
|
|
220
|
+
},
|
|
221
|
+
// Zero-downtime sweep (DESIGN §11): the publish overwrites every artifact in place, then
|
|
222
|
+
// calls this with the just-written path set so only orphaned blobs (pages the new publish
|
|
223
|
+
// did not emit) are removed. Every live URL keeps serving throughout, the old wipe-first
|
|
224
|
+
// path blanked the whole site for the upload window.
|
|
225
|
+
prune: async (keep) => {
|
|
226
|
+
for (const name of await container.list("")) {
|
|
227
|
+
if (!keep.has(name)) await container.remove(name);
|
|
228
|
+
}
|
|
143
229
|
}
|
|
144
230
|
};
|
|
145
231
|
}
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
renderReferenceMarkdown,
|
|
19
19
|
validateSite,
|
|
20
20
|
workingStoreRoundTripDiagnostic
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-P6NDWIKK.js";
|
|
22
22
|
|
|
23
23
|
// ../editor-api/src/scaffold.ts
|
|
24
24
|
var HOME_HTML = `<!doctype html>
|
|
@@ -559,7 +559,7 @@ async function dispatchApi(deps, req) {
|
|
|
559
559
|
return json(422, {
|
|
560
560
|
ok: false,
|
|
561
561
|
errors: result.validation.errors.map((e) => e.message),
|
|
562
|
-
...result.summary !== void 0 && {
|
|
562
|
+
...result.summary !== void 0 && { wouldPublish: result.summary }
|
|
563
563
|
});
|
|
564
564
|
}
|
|
565
565
|
case "/api/scaffold": {
|
|
@@ -949,12 +949,12 @@ function createEditor(config) {
|
|
|
949
949
|
};
|
|
950
950
|
try {
|
|
951
951
|
const validation = await validateSite(working);
|
|
952
|
-
if (validation.ok && wipeBeforePublish && config.website.wipe !== void 0) {
|
|
952
|
+
if (validation.ok && wipeBeforePublish && config.website.prune === void 0 && config.website.wipe !== void 0) {
|
|
953
953
|
await config.website.wipe();
|
|
954
954
|
}
|
|
955
955
|
const dir = config.users === void 0 ? void 0 : authorDirectory(await config.users());
|
|
956
956
|
const prebuild = typeof config.prebuild === "function" ? await config.prebuild() : config.prebuild;
|
|
957
|
-
|
|
957
|
+
const result = await publishSite(working, sink, {
|
|
958
958
|
...config.images !== void 0 && { imageEncoder: config.images },
|
|
959
959
|
...config.purge !== void 0 && { purge: config.purge },
|
|
960
960
|
...config.baseUrl !== void 0 && { baseUrl: config.baseUrl },
|
|
@@ -962,6 +962,10 @@ function createEditor(config) {
|
|
|
962
962
|
...prebuild !== void 0 && { prebuild },
|
|
963
963
|
onProgress: record
|
|
964
964
|
});
|
|
965
|
+
if (result.ok && wipeBeforePublish && config.website.prune !== void 0) {
|
|
966
|
+
await config.website.prune(new Set(result.written));
|
|
967
|
+
}
|
|
968
|
+
return result;
|
|
965
969
|
} finally {
|
|
966
970
|
progressRunning = false;
|
|
967
971
|
}
|
|
@@ -1170,6 +1174,12 @@ var TOOL_SPECS = [
|
|
|
1170
1174
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
1171
1175
|
toRequest: (_args, token) => request("POST", "/api/publish", {}, void 0, token)
|
|
1172
1176
|
},
|
|
1177
|
+
{
|
|
1178
|
+
name: "reconcile",
|
|
1179
|
+
description: "Rebuild the content index from the files actually in storage, recovering any that drifted out of the index. Use this if `validate`, `list_dir`, or a `reference-missing` error reports a file as absent that `read_file` can still return: the file is stored but unenumerable, and reconcile makes it visible again without re-writing it. Idempotent and a no-op when nothing drifted. Returns the count added/removed and the new total. Reports an error on a host that cannot enumerate its store.",
|
|
1180
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
1181
|
+
toRequest: (_args, token) => request("POST", "/api/reconcile", {}, void 0, token)
|
|
1182
|
+
},
|
|
1173
1183
|
{
|
|
1174
1184
|
name: "list_pending_migrations",
|
|
1175
1185
|
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.",
|