@xtrable-ltd/nanoesis 0.1.12 → 0.1.14
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.js +1 -1
- package/dist/{chunk-XO3CT6GL.js → chunk-CBLBQP27.js} +275 -220
- package/dist/{chunk-EN5SMEWJ.js → chunk-PKCYTOHW.js} +6 -6
- package/dist/editor-api.js +2 -2
- package/dist/index.d.ts +50 -12
- package/dist/index.js +1 -1
- package/dist/mcp.js +3 -3
- package/editor/assets/MigrationsPane-DSiYzhDN.js +4 -0
- package/editor/assets/{TemplatesPane-B5hn_v0Z.js → TemplatesPane-DacDItlh.js} +172 -168
- package/editor/assets/{cssMode-BbIf5k6I.js → cssMode-CiAvvnrb.js} +1 -1
- package/editor/assets/{freemarker2-DoW0pSYV.js → freemarker2-C7G3JwfA.js} +1 -1
- package/editor/assets/{handlebars-DLlET-qc.js → handlebars-DsJPq-J8.js} +1 -1
- package/editor/assets/{html-4khbqrhe.js → html-i_wgtIMk.js} +1 -1
- package/editor/assets/{htmlMode-DblHkZ-k.js → htmlMode-Bx_dmHBj.js} +1 -1
- package/editor/assets/index-CLUiygGJ.js +138 -0
- package/editor/assets/{javascript-CgPO2Hmj.js → javascript-C3n1eUNo.js} +1 -1
- package/editor/assets/{jsonMode-BrWh2436.js → jsonMode-BpC5c5px.js} +1 -1
- package/editor/assets/{liquid-BsQJXwPT.js → liquid-B3_Yp2NE.js} +1 -1
- package/editor/assets/{mdx-AO8t67gx.js → mdx-nOh_vCzd.js} +1 -1
- package/editor/assets/{python-3w4sZj5c.js → python-BZXwPif_.js} +1 -1
- package/editor/assets/{razor-BFsvo06w.js → razor-BYwO2G_q.js} +1 -1
- package/editor/assets/{tsMode-QrC4ERjp.js → tsMode-CmNOLqvJ.js} +1 -1
- package/editor/assets/{typescript-BXJ3QLad.js → typescript-C9KU4rft.js} +1 -1
- package/editor/assets/{xml-CxKYn1FP.js → xml-CFRBv758.js} +1 -1
- package/editor/assets/{yaml-BmWLvF7Q.js → yaml-Co0yIk6T.js} +1 -1
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/editor/assets/MigrationsPane-BYGqWBAA.js +0 -4
- package/editor/assets/index-Do1drqEQ.js +0 -138
|
@@ -43,6 +43,15 @@ var ContentParseError = class extends Error {
|
|
|
43
43
|
this.name = "ContentParseError";
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
|
+
var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
47
|
+
"template",
|
|
48
|
+
"title",
|
|
49
|
+
"isPublished",
|
|
50
|
+
"fields",
|
|
51
|
+
"created",
|
|
52
|
+
"published",
|
|
53
|
+
"updated"
|
|
54
|
+
]);
|
|
46
55
|
function isPlainObject(value) {
|
|
47
56
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
48
57
|
}
|
|
@@ -72,6 +81,9 @@ function parseOptionalString(raw, key) {
|
|
|
72
81
|
return value;
|
|
73
82
|
}
|
|
74
83
|
function parseContentItem(raw) {
|
|
84
|
+
return parseContentItemWithDiagnostics(raw).item;
|
|
85
|
+
}
|
|
86
|
+
function parseContentItemWithDiagnostics(raw) {
|
|
75
87
|
if (!isPlainObject(raw)) {
|
|
76
88
|
throw new ContentParseError("A content item must be a JSON object.");
|
|
77
89
|
}
|
|
@@ -100,7 +112,8 @@ function parseContentItem(raw) {
|
|
|
100
112
|
const created = parseOptionalString(raw, "created");
|
|
101
113
|
const published = parseOptionalString(raw, "published");
|
|
102
114
|
const updated = parseOptionalString(raw, "updated");
|
|
103
|
-
|
|
115
|
+
const unknownTopLevelKeys = Object.keys(raw).filter((key) => !KNOWN_TOP_LEVEL_KEYS.has(key)).sort();
|
|
116
|
+
const item = {
|
|
104
117
|
title: raw.title,
|
|
105
118
|
isPublished,
|
|
106
119
|
fields,
|
|
@@ -109,6 +122,7 @@ function parseContentItem(raw) {
|
|
|
109
122
|
...published !== void 0 && { published },
|
|
110
123
|
...updated !== void 0 && { updated }
|
|
111
124
|
};
|
|
125
|
+
return { item, diagnostics: { unknownTopLevelKeys } };
|
|
112
126
|
}
|
|
113
127
|
|
|
114
128
|
// ../engine/src/content/sort.ts
|
|
@@ -154,6 +168,218 @@ function copy(bytes) {
|
|
|
154
168
|
return bytes.slice();
|
|
155
169
|
}
|
|
156
170
|
|
|
171
|
+
// ../engine/src/url/redirects.ts
|
|
172
|
+
var DEFAULT_STATUS = 301;
|
|
173
|
+
var OUTPUT_PATH = "_redirects";
|
|
174
|
+
function isRule(value) {
|
|
175
|
+
if (typeof value !== "object" || value === null) return false;
|
|
176
|
+
const record = value;
|
|
177
|
+
return typeof record.from === "string" && typeof record.to === "string";
|
|
178
|
+
}
|
|
179
|
+
function parseRedirects(raw) {
|
|
180
|
+
if (!Array.isArray(raw)) return [];
|
|
181
|
+
const rules = [];
|
|
182
|
+
for (const entry of raw) {
|
|
183
|
+
if (!isRule(entry)) continue;
|
|
184
|
+
const status = entry.status;
|
|
185
|
+
rules.push(
|
|
186
|
+
typeof status === "number" && Number.isInteger(status) ? { from: entry.from, to: entry.to, status } : { from: entry.from, to: entry.to }
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
return rules;
|
|
190
|
+
}
|
|
191
|
+
function resolveTarget(from, exact) {
|
|
192
|
+
const seen = /* @__PURE__ */ new Set([from]);
|
|
193
|
+
let target = exact.get(from).to;
|
|
194
|
+
while (exact.has(target) && !seen.has(target)) {
|
|
195
|
+
seen.add(target);
|
|
196
|
+
target = exact.get(target).to;
|
|
197
|
+
}
|
|
198
|
+
return target;
|
|
199
|
+
}
|
|
200
|
+
function buildRedirects(rules, liveUrls) {
|
|
201
|
+
const exact = /* @__PURE__ */ new Map();
|
|
202
|
+
const wildcard = /* @__PURE__ */ new Map();
|
|
203
|
+
for (const rule of rules) {
|
|
204
|
+
(rule.from.includes("*") ? wildcard : exact).set(rule.from, rule);
|
|
205
|
+
}
|
|
206
|
+
const resolved = [];
|
|
207
|
+
for (const [from, rule] of exact) {
|
|
208
|
+
if (liveUrls.has(from)) continue;
|
|
209
|
+
const to = resolveTarget(from, exact);
|
|
210
|
+
if (to === from) continue;
|
|
211
|
+
resolved.push(rule.status !== void 0 ? { from, to, status: rule.status } : { from, to });
|
|
212
|
+
}
|
|
213
|
+
resolved.push(...wildcard.values());
|
|
214
|
+
if (resolved.length === 0) return void 0;
|
|
215
|
+
resolved.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
|
|
216
|
+
const contents = resolved.map((rule) => `${rule.from} ${rule.to} ${rule.status ?? DEFAULT_STATUS}
|
|
217
|
+
`).join("");
|
|
218
|
+
return { path: OUTPUT_PATH, contents };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ../engine/src/content/loader.ts
|
|
222
|
+
var SORT_FILE = "_sort.json";
|
|
223
|
+
var REDIRECTS_FILE = "_redirects.json";
|
|
224
|
+
var SITE_CONFIG_FILE = "_site.json";
|
|
225
|
+
var ASSETS_DIR = "assets";
|
|
226
|
+
var ITEM_EXT = ".json";
|
|
227
|
+
var DEFAULT_DIRS = {
|
|
228
|
+
content: "content",
|
|
229
|
+
templates: "templates",
|
|
230
|
+
components: "components",
|
|
231
|
+
/** Static passthrough, copied verbatim to the published root (DESIGN §8). The
|
|
232
|
+
* engine never reads it; named here so hosts/editor share one source of truth. */
|
|
233
|
+
public: "public"
|
|
234
|
+
};
|
|
235
|
+
function join(...parts) {
|
|
236
|
+
return parts.filter((part) => part !== "").join("/");
|
|
237
|
+
}
|
|
238
|
+
function stripBom(text) {
|
|
239
|
+
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
240
|
+
}
|
|
241
|
+
async function loadContentTree(source, contentDir = DEFAULT_DIRS.content) {
|
|
242
|
+
return loadDir(source, contentDir, "", "");
|
|
243
|
+
}
|
|
244
|
+
async function loadDir(source, dirPath, slug, treePath) {
|
|
245
|
+
const [entries, sort] = await Promise.all([source.list(dirPath), readSort(source, dirPath)]);
|
|
246
|
+
const childMap = /* @__PURE__ */ new Map();
|
|
247
|
+
for (const entry of entries) {
|
|
248
|
+
if (entry.kind === "file") {
|
|
249
|
+
if (entry.name === SORT_FILE || entry.name === REDIRECTS_FILE || entry.name === SITE_CONFIG_FILE || !entry.name.endsWith(ITEM_EXT))
|
|
250
|
+
continue;
|
|
251
|
+
const childSlug = entry.name.slice(0, -ITEM_EXT.length);
|
|
252
|
+
childMap.set(
|
|
253
|
+
childSlug,
|
|
254
|
+
await loadItem(source, join(dirPath, entry.name), childSlug, treePath)
|
|
255
|
+
);
|
|
256
|
+
} else {
|
|
257
|
+
if (entry.name === ASSETS_DIR) continue;
|
|
258
|
+
const childPath = join(treePath, entry.name);
|
|
259
|
+
childMap.set(
|
|
260
|
+
entry.name,
|
|
261
|
+
await loadDir(source, join(dirPath, entry.name), entry.name, childPath)
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const name = sort.name ?? (slug === "" ? "" : humanize(slug));
|
|
266
|
+
return {
|
|
267
|
+
kind: "dir",
|
|
268
|
+
slug,
|
|
269
|
+
name,
|
|
270
|
+
path: treePath,
|
|
271
|
+
children: orderChildren(childMap, sort.order),
|
|
272
|
+
...sort.collection !== void 0 && { collection: sort.collection },
|
|
273
|
+
...sort.defaultTemplate !== void 0 && { defaultTemplate: sort.defaultTemplate }
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
async function loadItem(source, filePath, slug, parentPath2) {
|
|
277
|
+
const raw = await source.readText(filePath);
|
|
278
|
+
try {
|
|
279
|
+
const { item, diagnostics } = parseContentItemWithDiagnostics(JSON.parse(raw));
|
|
280
|
+
return {
|
|
281
|
+
kind: "item",
|
|
282
|
+
slug,
|
|
283
|
+
path: join(parentPath2, slug),
|
|
284
|
+
item,
|
|
285
|
+
...diagnostics.unknownTopLevelKeys.length > 0 && { parseDiagnostics: diagnostics }
|
|
286
|
+
};
|
|
287
|
+
} catch (error) {
|
|
288
|
+
throw new Error(`Failed to load ${filePath}: ${error.message}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async function readSort(source, dirPath) {
|
|
292
|
+
const path = join(dirPath, SORT_FILE);
|
|
293
|
+
if (!await source.exists(path)) return { order: [] };
|
|
294
|
+
try {
|
|
295
|
+
return parseSortFile(JSON.parse(await source.readText(path)));
|
|
296
|
+
} catch {
|
|
297
|
+
return { order: [] };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async function loadRedirects(source, contentDir = DEFAULT_DIRS.content) {
|
|
301
|
+
const path = join(contentDir, REDIRECTS_FILE);
|
|
302
|
+
if (!await source.exists(path)) return [];
|
|
303
|
+
try {
|
|
304
|
+
return parseRedirects(JSON.parse(stripBom(await source.readText(path))));
|
|
305
|
+
} catch {
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
async function loadSiteConfig(source, contentDir = DEFAULT_DIRS.content) {
|
|
310
|
+
const path = join(contentDir, SITE_CONFIG_FILE);
|
|
311
|
+
if (!await source.exists(path)) return {};
|
|
312
|
+
try {
|
|
313
|
+
const raw = JSON.parse(stripBom(await source.readText(path)));
|
|
314
|
+
const baseUrl = typeof raw.baseUrl === "string" ? raw.baseUrl.trim() : "";
|
|
315
|
+
return baseUrl !== "" ? { baseUrl } : {};
|
|
316
|
+
} catch {
|
|
317
|
+
return {};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function orderChildren(map, order) {
|
|
321
|
+
const out = [];
|
|
322
|
+
const used = /* @__PURE__ */ new Set();
|
|
323
|
+
for (const slug of order) {
|
|
324
|
+
const node = map.get(slug);
|
|
325
|
+
if (node !== void 0) {
|
|
326
|
+
out.push(node);
|
|
327
|
+
used.add(slug);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
for (const [slug, node] of map) {
|
|
331
|
+
if (!used.has(slug)) out.push(node);
|
|
332
|
+
}
|
|
333
|
+
return out;
|
|
334
|
+
}
|
|
335
|
+
function loadComponents(source, componentsDir = DEFAULT_DIRS.components) {
|
|
336
|
+
return loadComponentFiles(source, componentsDir, "html");
|
|
337
|
+
}
|
|
338
|
+
function loadComponentStyles(source, componentsDir = DEFAULT_DIRS.components) {
|
|
339
|
+
return loadComponentFiles(source, componentsDir, "css");
|
|
340
|
+
}
|
|
341
|
+
function loadComponentScripts(source, componentsDir = DEFAULT_DIRS.components) {
|
|
342
|
+
return loadComponentFiles(source, componentsDir, "js");
|
|
343
|
+
}
|
|
344
|
+
async function loadComponentFiles(source, componentsDir, extension) {
|
|
345
|
+
const suffix = `.${extension}`;
|
|
346
|
+
const map = /* @__PURE__ */ new Map();
|
|
347
|
+
const walk = async (dir) => {
|
|
348
|
+
if (!await source.exists(dir)) return;
|
|
349
|
+
for (const entry of await source.list(dir)) {
|
|
350
|
+
const full = join(dir, entry.name);
|
|
351
|
+
if (entry.kind === "dir") {
|
|
352
|
+
await walk(full);
|
|
353
|
+
} else if (entry.name.endsWith(suffix)) {
|
|
354
|
+
const key = entry.name.slice(0, -suffix.length).toLowerCase();
|
|
355
|
+
map.set(key, await source.readText(full));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
await walk(componentsDir);
|
|
360
|
+
return map;
|
|
361
|
+
}
|
|
362
|
+
async function loadTemplate(source, name, templatesDir = DEFAULT_DIRS.templates) {
|
|
363
|
+
return source.readText(join(templatesDir, `${name}.html`));
|
|
364
|
+
}
|
|
365
|
+
function loadTemplateStyle(source, name, templatesDir = DEFAULT_DIRS.templates) {
|
|
366
|
+
return tryReadText(source, join(templatesDir, `${name}.css`));
|
|
367
|
+
}
|
|
368
|
+
function loadTemplateScript(source, name, templatesDir = DEFAULT_DIRS.templates) {
|
|
369
|
+
return tryReadText(source, join(templatesDir, `${name}.js`));
|
|
370
|
+
}
|
|
371
|
+
var DOCUMENT_SHELL = "document";
|
|
372
|
+
function loadDocumentShell(source, templatesDir = DEFAULT_DIRS.templates) {
|
|
373
|
+
return tryReadText(source, join(templatesDir, `${DOCUMENT_SHELL}.html`));
|
|
374
|
+
}
|
|
375
|
+
async function tryReadText(source, path) {
|
|
376
|
+
try {
|
|
377
|
+
return await source.readText(path);
|
|
378
|
+
} catch {
|
|
379
|
+
return void 0;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
157
383
|
// ../engine/src/html/dom.ts
|
|
158
384
|
import { parse, parseFragment, serialize } from "parse5";
|
|
159
385
|
function isElement(node) {
|
|
@@ -776,10 +1002,15 @@ var IndexedStore = class {
|
|
|
776
1002
|
}
|
|
777
1003
|
}
|
|
778
1004
|
this.index = nextIndex;
|
|
1005
|
+
let parseDiagnostics;
|
|
1006
|
+
if (isContentItemPath(target)) {
|
|
1007
|
+
parseDiagnostics = contentParseDiagnosticsFor(bytes);
|
|
1008
|
+
}
|
|
779
1009
|
return {
|
|
780
1010
|
...stamped !== void 0 && { stamped },
|
|
781
1011
|
...stampIncomplete === true && { stampIncomplete: true },
|
|
782
|
-
...schemaDelta !== void 0 && { schemaDelta }
|
|
1012
|
+
...schemaDelta !== void 0 && { schemaDelta },
|
|
1013
|
+
...parseDiagnostics !== void 0 && { parseDiagnostics }
|
|
783
1014
|
};
|
|
784
1015
|
});
|
|
785
1016
|
}
|
|
@@ -895,8 +1126,23 @@ function stampTargetOf(target) {
|
|
|
895
1126
|
if (dir === void 0) return void 0;
|
|
896
1127
|
const stem = target.slice(dir.length + 1, -".html".length);
|
|
897
1128
|
if (stem === "" || stem.includes("@v")) return void 0;
|
|
1129
|
+
if (dir === "templates" && stem === DOCUMENT_SHELL) return void 0;
|
|
898
1130
|
return { dir, name: stem };
|
|
899
1131
|
}
|
|
1132
|
+
function isContentItemPath(target) {
|
|
1133
|
+
if (!target.startsWith("content/") || !target.endsWith(".json")) return false;
|
|
1134
|
+
const tail = target.slice(target.lastIndexOf("/") + 1);
|
|
1135
|
+
return tail !== "_sort.json" && tail !== "_redirects.json" && tail !== "_site.json";
|
|
1136
|
+
}
|
|
1137
|
+
function contentParseDiagnosticsFor(bytes) {
|
|
1138
|
+
try {
|
|
1139
|
+
const text = new TextDecoder().decode(bytes);
|
|
1140
|
+
const { diagnostics } = parseContentItemWithDiagnostics(JSON.parse(text));
|
|
1141
|
+
return diagnostics.unknownTopLevelKeys.length > 0 ? diagnostics : void 0;
|
|
1142
|
+
} catch {
|
|
1143
|
+
return void 0;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
900
1146
|
function guarded(key) {
|
|
901
1147
|
if (key === "" || key.startsWith(RESERVED_PREFIX)) {
|
|
902
1148
|
throw new Error(`Refusing to mutate a reserved key: ${key === "" ? "(root)" : key}`);
|
|
@@ -973,212 +1219,6 @@ var InMemoryContentSource = class {
|
|
|
973
1219
|
}
|
|
974
1220
|
};
|
|
975
1221
|
|
|
976
|
-
// ../engine/src/url/redirects.ts
|
|
977
|
-
var DEFAULT_STATUS = 301;
|
|
978
|
-
var OUTPUT_PATH = "_redirects";
|
|
979
|
-
function isRule(value) {
|
|
980
|
-
if (typeof value !== "object" || value === null) return false;
|
|
981
|
-
const record = value;
|
|
982
|
-
return typeof record.from === "string" && typeof record.to === "string";
|
|
983
|
-
}
|
|
984
|
-
function parseRedirects(raw) {
|
|
985
|
-
if (!Array.isArray(raw)) return [];
|
|
986
|
-
const rules = [];
|
|
987
|
-
for (const entry of raw) {
|
|
988
|
-
if (!isRule(entry)) continue;
|
|
989
|
-
const status = entry.status;
|
|
990
|
-
rules.push(
|
|
991
|
-
typeof status === "number" && Number.isInteger(status) ? { from: entry.from, to: entry.to, status } : { from: entry.from, to: entry.to }
|
|
992
|
-
);
|
|
993
|
-
}
|
|
994
|
-
return rules;
|
|
995
|
-
}
|
|
996
|
-
function resolveTarget(from, exact) {
|
|
997
|
-
const seen = /* @__PURE__ */ new Set([from]);
|
|
998
|
-
let target = exact.get(from).to;
|
|
999
|
-
while (exact.has(target) && !seen.has(target)) {
|
|
1000
|
-
seen.add(target);
|
|
1001
|
-
target = exact.get(target).to;
|
|
1002
|
-
}
|
|
1003
|
-
return target;
|
|
1004
|
-
}
|
|
1005
|
-
function buildRedirects(rules, liveUrls) {
|
|
1006
|
-
const exact = /* @__PURE__ */ new Map();
|
|
1007
|
-
const wildcard = /* @__PURE__ */ new Map();
|
|
1008
|
-
for (const rule of rules) {
|
|
1009
|
-
(rule.from.includes("*") ? wildcard : exact).set(rule.from, rule);
|
|
1010
|
-
}
|
|
1011
|
-
const resolved = [];
|
|
1012
|
-
for (const [from, rule] of exact) {
|
|
1013
|
-
if (liveUrls.has(from)) continue;
|
|
1014
|
-
const to = resolveTarget(from, exact);
|
|
1015
|
-
if (to === from) continue;
|
|
1016
|
-
resolved.push(rule.status !== void 0 ? { from, to, status: rule.status } : { from, to });
|
|
1017
|
-
}
|
|
1018
|
-
resolved.push(...wildcard.values());
|
|
1019
|
-
if (resolved.length === 0) return void 0;
|
|
1020
|
-
resolved.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
|
|
1021
|
-
const contents = resolved.map((rule) => `${rule.from} ${rule.to} ${rule.status ?? DEFAULT_STATUS}
|
|
1022
|
-
`).join("");
|
|
1023
|
-
return { path: OUTPUT_PATH, contents };
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
// ../engine/src/content/loader.ts
|
|
1027
|
-
var SORT_FILE = "_sort.json";
|
|
1028
|
-
var REDIRECTS_FILE = "_redirects.json";
|
|
1029
|
-
var SITE_CONFIG_FILE = "_site.json";
|
|
1030
|
-
var ASSETS_DIR = "assets";
|
|
1031
|
-
var ITEM_EXT = ".json";
|
|
1032
|
-
var DEFAULT_DIRS = {
|
|
1033
|
-
content: "content",
|
|
1034
|
-
templates: "templates",
|
|
1035
|
-
components: "components",
|
|
1036
|
-
/** Static passthrough, copied verbatim to the published root (DESIGN §8). The
|
|
1037
|
-
* engine never reads it; named here so hosts/editor share one source of truth. */
|
|
1038
|
-
public: "public"
|
|
1039
|
-
};
|
|
1040
|
-
function join(...parts) {
|
|
1041
|
-
return parts.filter((part) => part !== "").join("/");
|
|
1042
|
-
}
|
|
1043
|
-
function stripBom(text) {
|
|
1044
|
-
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
1045
|
-
}
|
|
1046
|
-
async function loadContentTree(source, contentDir = DEFAULT_DIRS.content) {
|
|
1047
|
-
return loadDir(source, contentDir, "", "");
|
|
1048
|
-
}
|
|
1049
|
-
async function loadDir(source, dirPath, slug, treePath) {
|
|
1050
|
-
const [entries, sort] = await Promise.all([source.list(dirPath), readSort(source, dirPath)]);
|
|
1051
|
-
const childMap = /* @__PURE__ */ new Map();
|
|
1052
|
-
for (const entry of entries) {
|
|
1053
|
-
if (entry.kind === "file") {
|
|
1054
|
-
if (entry.name === SORT_FILE || entry.name === REDIRECTS_FILE || entry.name === SITE_CONFIG_FILE || !entry.name.endsWith(ITEM_EXT))
|
|
1055
|
-
continue;
|
|
1056
|
-
const childSlug = entry.name.slice(0, -ITEM_EXT.length);
|
|
1057
|
-
childMap.set(
|
|
1058
|
-
childSlug,
|
|
1059
|
-
await loadItem(source, join(dirPath, entry.name), childSlug, treePath)
|
|
1060
|
-
);
|
|
1061
|
-
} else {
|
|
1062
|
-
if (entry.name === ASSETS_DIR) continue;
|
|
1063
|
-
const childPath = join(treePath, entry.name);
|
|
1064
|
-
childMap.set(
|
|
1065
|
-
entry.name,
|
|
1066
|
-
await loadDir(source, join(dirPath, entry.name), entry.name, childPath)
|
|
1067
|
-
);
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
const name = sort.name ?? (slug === "" ? "" : humanize(slug));
|
|
1071
|
-
return {
|
|
1072
|
-
kind: "dir",
|
|
1073
|
-
slug,
|
|
1074
|
-
name,
|
|
1075
|
-
path: treePath,
|
|
1076
|
-
children: orderChildren(childMap, sort.order),
|
|
1077
|
-
...sort.collection !== void 0 && { collection: sort.collection },
|
|
1078
|
-
...sort.defaultTemplate !== void 0 && { defaultTemplate: sort.defaultTemplate }
|
|
1079
|
-
};
|
|
1080
|
-
}
|
|
1081
|
-
async function loadItem(source, filePath, slug, parentPath2) {
|
|
1082
|
-
const raw = await source.readText(filePath);
|
|
1083
|
-
try {
|
|
1084
|
-
const item = parseContentItem(JSON.parse(raw));
|
|
1085
|
-
return { kind: "item", slug, path: join(parentPath2, slug), item };
|
|
1086
|
-
} catch (error) {
|
|
1087
|
-
throw new Error(`Failed to load ${filePath}: ${error.message}`);
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
async function readSort(source, dirPath) {
|
|
1091
|
-
const path = join(dirPath, SORT_FILE);
|
|
1092
|
-
if (!await source.exists(path)) return { order: [] };
|
|
1093
|
-
try {
|
|
1094
|
-
return parseSortFile(JSON.parse(await source.readText(path)));
|
|
1095
|
-
} catch {
|
|
1096
|
-
return { order: [] };
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
async function loadRedirects(source, contentDir = DEFAULT_DIRS.content) {
|
|
1100
|
-
const path = join(contentDir, REDIRECTS_FILE);
|
|
1101
|
-
if (!await source.exists(path)) return [];
|
|
1102
|
-
try {
|
|
1103
|
-
return parseRedirects(JSON.parse(stripBom(await source.readText(path))));
|
|
1104
|
-
} catch {
|
|
1105
|
-
return [];
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
async function loadSiteConfig(source, contentDir = DEFAULT_DIRS.content) {
|
|
1109
|
-
const path = join(contentDir, SITE_CONFIG_FILE);
|
|
1110
|
-
if (!await source.exists(path)) return {};
|
|
1111
|
-
try {
|
|
1112
|
-
const raw = JSON.parse(stripBom(await source.readText(path)));
|
|
1113
|
-
const baseUrl = typeof raw.baseUrl === "string" ? raw.baseUrl.trim() : "";
|
|
1114
|
-
return baseUrl !== "" ? { baseUrl } : {};
|
|
1115
|
-
} catch {
|
|
1116
|
-
return {};
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
function orderChildren(map, order) {
|
|
1120
|
-
const out = [];
|
|
1121
|
-
const used = /* @__PURE__ */ new Set();
|
|
1122
|
-
for (const slug of order) {
|
|
1123
|
-
const node = map.get(slug);
|
|
1124
|
-
if (node !== void 0) {
|
|
1125
|
-
out.push(node);
|
|
1126
|
-
used.add(slug);
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
for (const [slug, node] of map) {
|
|
1130
|
-
if (!used.has(slug)) out.push(node);
|
|
1131
|
-
}
|
|
1132
|
-
return out;
|
|
1133
|
-
}
|
|
1134
|
-
function loadComponents(source, componentsDir = DEFAULT_DIRS.components) {
|
|
1135
|
-
return loadComponentFiles(source, componentsDir, "html");
|
|
1136
|
-
}
|
|
1137
|
-
function loadComponentStyles(source, componentsDir = DEFAULT_DIRS.components) {
|
|
1138
|
-
return loadComponentFiles(source, componentsDir, "css");
|
|
1139
|
-
}
|
|
1140
|
-
function loadComponentScripts(source, componentsDir = DEFAULT_DIRS.components) {
|
|
1141
|
-
return loadComponentFiles(source, componentsDir, "js");
|
|
1142
|
-
}
|
|
1143
|
-
async function loadComponentFiles(source, componentsDir, extension) {
|
|
1144
|
-
const suffix = `.${extension}`;
|
|
1145
|
-
const map = /* @__PURE__ */ new Map();
|
|
1146
|
-
const walk = async (dir) => {
|
|
1147
|
-
if (!await source.exists(dir)) return;
|
|
1148
|
-
for (const entry of await source.list(dir)) {
|
|
1149
|
-
const full = join(dir, entry.name);
|
|
1150
|
-
if (entry.kind === "dir") {
|
|
1151
|
-
await walk(full);
|
|
1152
|
-
} else if (entry.name.endsWith(suffix)) {
|
|
1153
|
-
const key = entry.name.slice(0, -suffix.length).toLowerCase();
|
|
1154
|
-
map.set(key, await source.readText(full));
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
};
|
|
1158
|
-
await walk(componentsDir);
|
|
1159
|
-
return map;
|
|
1160
|
-
}
|
|
1161
|
-
async function loadTemplate(source, name, templatesDir = DEFAULT_DIRS.templates) {
|
|
1162
|
-
return source.readText(join(templatesDir, `${name}.html`));
|
|
1163
|
-
}
|
|
1164
|
-
function loadTemplateStyle(source, name, templatesDir = DEFAULT_DIRS.templates) {
|
|
1165
|
-
return tryReadText(source, join(templatesDir, `${name}.css`));
|
|
1166
|
-
}
|
|
1167
|
-
function loadTemplateScript(source, name, templatesDir = DEFAULT_DIRS.templates) {
|
|
1168
|
-
return tryReadText(source, join(templatesDir, `${name}.js`));
|
|
1169
|
-
}
|
|
1170
|
-
var DOCUMENT_SHELL = "document";
|
|
1171
|
-
function loadDocumentShell(source, templatesDir = DEFAULT_DIRS.templates) {
|
|
1172
|
-
return tryReadText(source, join(templatesDir, `${DOCUMENT_SHELL}.html`));
|
|
1173
|
-
}
|
|
1174
|
-
async function tryReadText(source, path) {
|
|
1175
|
-
try {
|
|
1176
|
-
return await source.readText(path);
|
|
1177
|
-
} catch {
|
|
1178
|
-
return void 0;
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
1222
|
// ../engine/src/url/references.ts
|
|
1183
1223
|
var REFERENCE_PREFIX = "ref:";
|
|
1184
1224
|
function referenceTarget(value) {
|
|
@@ -2598,6 +2638,12 @@ function guardrailsSection() {
|
|
|
2598
2638
|
{
|
|
2599
2639
|
name: "richtext values are HTML, shorttext/text are plain text",
|
|
2600
2640
|
summary: "For a `richtext` field, write the value as HTML (`<p>...</p>` etc). For `shorttext` or `text`, write plain text \u2014 any HTML you include is escaped at render time. If the gate emits a `content.type-drift` warning, a field's type changed destructively (commonly richtext \u2192 shorttext) and the old HTML content now renders as escaped text."
|
|
2641
|
+
},
|
|
2642
|
+
{
|
|
2643
|
+
name: "Content fields nest under `fields`, not at the top level",
|
|
2644
|
+
summary: "A content/*.json item has system keys at the top level (`template`, `title`, `isPublished`, `created`, `published`, `updated`) and ALL template-specific values under `fields`. Writing `{ template, title, body, tagline }` at the top level silently drops `body`/`tagline` because the parser is tolerant of unknown keys; the page publishes with empty placeholders. After write_file, check the response for `parseDiagnostics.unknownTopLevelKeys` and the gate for `content.unknown-top-level-key` \u2014 both list what was dropped.",
|
|
2645
|
+
detail: 'System keys are case-sensitive: `IsPublished` is not the same as `isPublished` (the typo silently defaults to false), and `Fields` is not `fields`. The same drop-and-flag applies. The published HTML for a tolerated-drift item will render with empty `<div class="prose"></div>` etc., which is the smoking-gun symptom.',
|
|
2646
|
+
example: '<!-- WRONG (body and tagline silently dropped) -->\n{ "template": "about", "title": "Hi", "body": "...", "tagline": "..." }\n\n<!-- RIGHT -->\n{ "template": "about", "title": "Hi", "fields": { "title": "Hi", "body": "...", "tagline": "..." } }'
|
|
2601
2647
|
}
|
|
2602
2648
|
]
|
|
2603
2649
|
};
|
|
@@ -2780,6 +2826,15 @@ async function validateSite(source) {
|
|
|
2780
2826
|
const owners = urlOwners.get(urlForItem(node.path)) ?? [];
|
|
2781
2827
|
owners.push(node.path);
|
|
2782
2828
|
urlOwners.set(urlForItem(node.path), owners);
|
|
2829
|
+
const dropped = node.parseDiagnostics?.unknownTopLevelKeys ?? [];
|
|
2830
|
+
if (dropped.length > 0) {
|
|
2831
|
+
add(
|
|
2832
|
+
"warning",
|
|
2833
|
+
"content.unknown-top-level-key",
|
|
2834
|
+
`"${node.path}" has top-level key${dropped.length === 1 ? "" : "s"} the parser does not recognise: ${dropped.map((k) => `"${k}"`).join(", ")}. They were dropped \u2014 content values belong under "fields" (and system keys are case-sensitive: "isPublished", not "IsPublished").`,
|
|
2835
|
+
node.path
|
|
2836
|
+
);
|
|
2837
|
+
}
|
|
2783
2838
|
if (templateName === void 0) {
|
|
2784
2839
|
add(
|
|
2785
2840
|
"error",
|
|
@@ -3253,6 +3308,18 @@ export {
|
|
|
3253
3308
|
parseContentItem,
|
|
3254
3309
|
parseSortFile,
|
|
3255
3310
|
InMemoryBlobStore,
|
|
3311
|
+
parseRedirects,
|
|
3312
|
+
buildRedirects,
|
|
3313
|
+
DEFAULT_DIRS,
|
|
3314
|
+
loadContentTree,
|
|
3315
|
+
loadRedirects,
|
|
3316
|
+
loadSiteConfig,
|
|
3317
|
+
loadComponents,
|
|
3318
|
+
loadComponentStyles,
|
|
3319
|
+
loadComponentScripts,
|
|
3320
|
+
loadTemplate,
|
|
3321
|
+
DOCUMENT_SHELL,
|
|
3322
|
+
loadDocumentShell,
|
|
3256
3323
|
FIELD_TYPES,
|
|
3257
3324
|
isFieldType,
|
|
3258
3325
|
valueKindOf,
|
|
@@ -3276,18 +3343,6 @@ export {
|
|
|
3276
3343
|
reconcileIndex,
|
|
3277
3344
|
IndexedStore,
|
|
3278
3345
|
InMemoryContentSource,
|
|
3279
|
-
parseRedirects,
|
|
3280
|
-
buildRedirects,
|
|
3281
|
-
DEFAULT_DIRS,
|
|
3282
|
-
loadContentTree,
|
|
3283
|
-
loadRedirects,
|
|
3284
|
-
loadSiteConfig,
|
|
3285
|
-
loadComponents,
|
|
3286
|
-
loadComponentStyles,
|
|
3287
|
-
loadComponentScripts,
|
|
3288
|
-
loadTemplate,
|
|
3289
|
-
DOCUMENT_SHELL,
|
|
3290
|
-
loadDocumentShell,
|
|
3291
3346
|
analyzeTemplate,
|
|
3292
3347
|
pendingMigrations,
|
|
3293
3348
|
bestFitSnapshot,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
renderReferenceMarkdown,
|
|
16
16
|
validateSite,
|
|
17
17
|
workingStoreRoundTripDiagnostic
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-CBLBQP27.js";
|
|
19
19
|
|
|
20
20
|
// ../editor-api/src/scaffold.ts
|
|
21
21
|
var HOME_HTML = `<!doctype html>
|
|
@@ -500,7 +500,10 @@ async function dispatchApi(deps, req) {
|
|
|
500
500
|
ok: true,
|
|
501
501
|
...result.stamped !== void 0 && { stamped: result.stamped },
|
|
502
502
|
...result.stampIncomplete === true && { stampIncomplete: true },
|
|
503
|
-
...result.schemaDelta !== void 0 && { schemaDelta: result.schemaDelta }
|
|
503
|
+
...result.schemaDelta !== void 0 && { schemaDelta: result.schemaDelta },
|
|
504
|
+
...result.parseDiagnostics !== void 0 && {
|
|
505
|
+
parseDiagnostics: result.parseDiagnostics
|
|
506
|
+
}
|
|
504
507
|
});
|
|
505
508
|
} catch (error) {
|
|
506
509
|
return json(500, { ok: false, error: String(error) });
|
|
@@ -567,9 +570,6 @@ async function dispatchApi(deps, req) {
|
|
|
567
570
|
if (deps.reconcile === void 0) {
|
|
568
571
|
return json(404, { ok: false, error: "reconcile is not available on this host" });
|
|
569
572
|
}
|
|
570
|
-
if (!hasRole(principal, "admin")) {
|
|
571
|
-
return json(403, { ok: false, error: "admin role required" });
|
|
572
|
-
}
|
|
573
573
|
const result = await deps.reconcile();
|
|
574
574
|
return json(200, {
|
|
575
575
|
ok: true,
|
|
@@ -694,7 +694,7 @@ var TOOL_SPECS = [
|
|
|
694
694
|
},
|
|
695
695
|
{
|
|
696
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. 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).",
|
|
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
698
|
inputSchema: {
|
|
699
699
|
type: "object",
|
|
700
700
|
properties: {
|
package/dist/editor-api.js
CHANGED
|
@@ -18,8 +18,8 @@ import {
|
|
|
18
18
|
recreateHomeTemplateRepair,
|
|
19
19
|
templateSnapshotIntegrityDiagnostic,
|
|
20
20
|
templateSuffixConflictDiagnostic
|
|
21
|
-
} from "./chunk-
|
|
22
|
-
import "./chunk-
|
|
21
|
+
} from "./chunk-PKCYTOHW.js";
|
|
22
|
+
import "./chunk-CBLBQP27.js";
|
|
23
23
|
export {
|
|
24
24
|
FileBrandingStore,
|
|
25
25
|
InMemoryBrandingStore,
|