@xtrable-ltd/nanoesis 0.1.7 → 0.1.9
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-Y7PUOM5I.js → chunk-J6VYOB47.js} +168 -1
- package/dist/editor-api.d.ts +47 -2
- package/dist/editor-api.js +113 -3
- package/dist/index.d.ts +96 -7
- package/dist/index.js +7 -3
- package/editor/assets/{TemplatesPane-CtH_G1zX.js → TemplatesPane-CHzfB00-.js} +7 -7
- package/editor/assets/{cssMode-Bp_d9uU6.js → cssMode-BahdJh1A.js} +1 -1
- package/editor/assets/{freemarker2-Bjkifjqk.js → freemarker2-2FC3twUE.js} +1 -1
- package/editor/assets/{handlebars-BRat5rme.js → handlebars-pMjPHNx1.js} +1 -1
- package/editor/assets/{html-BABGfo5F.js → html-KTToTG0n.js} +1 -1
- package/editor/assets/{htmlMode-iA96VUY3.js → htmlMode-ufik94dZ.js} +1 -1
- package/editor/assets/{index-BRPY7tEn.css → index-BsRGVHEP.css} +1 -1
- package/editor/assets/{index-B11qMUOV.js → index-CPKtfzWD.js} +71 -70
- package/editor/assets/{javascript-Py4Moqda.js → javascript-CD4kAZXr.js} +1 -1
- package/editor/assets/{jsonMode-ChrNU-Y4.js → jsonMode-ClHucayn.js} +1 -1
- package/editor/assets/{liquid-CB6iNJlZ.js → liquid-B-uYib60.js} +1 -1
- package/editor/assets/{mdx-DOQaWGy9.js → mdx-BOc9oMkZ.js} +1 -1
- package/editor/assets/{python-QNihkXeJ.js → python-BipLFHGs.js} +1 -1
- package/editor/assets/{razor-bRuM_KbJ.js → razor-C0di_gwM.js} +1 -1
- package/editor/assets/{tsMode-CuAeEMPg.js → tsMode-B7fenrcD.js} +1 -1
- package/editor/assets/{typescript-Dw4AxvYT.js → typescript-CDg7c2A-.js} +1 -1
- package/editor/assets/{xml-mN7rLnTp.js → xml-DTAdn5Pw.js} +1 -1
- package/editor/assets/{yaml-tN2lKdvN.js → yaml-B9-OjY0Z.js} +1 -1
- package/editor/index.html +2 -2
- package/package.json +1 -1
|
@@ -2560,6 +2560,171 @@ function canEdit(principal) {
|
|
|
2560
2560
|
return hasRole(principal, "author") || hasRole(principal, "developer");
|
|
2561
2561
|
}
|
|
2562
2562
|
|
|
2563
|
+
// ../engine/src/diagnose/registry.ts
|
|
2564
|
+
function createDiagnosticRegistry() {
|
|
2565
|
+
const diagnostics = /* @__PURE__ */ new Map();
|
|
2566
|
+
const repairs = /* @__PURE__ */ new Map();
|
|
2567
|
+
const runAll = async (deps) => {
|
|
2568
|
+
const out = [];
|
|
2569
|
+
for (const diagnostic of diagnostics.values()) {
|
|
2570
|
+
try {
|
|
2571
|
+
const findings = await diagnostic.run(deps);
|
|
2572
|
+
out.push(...findings);
|
|
2573
|
+
} catch (error) {
|
|
2574
|
+
out.push(crashFinding(diagnostic.id, error));
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
return out;
|
|
2578
|
+
};
|
|
2579
|
+
return {
|
|
2580
|
+
add(diagnostic) {
|
|
2581
|
+
if (diagnostics.has(diagnostic.id)) {
|
|
2582
|
+
throw new Error(`Diagnostic id "${diagnostic.id}" is already registered`);
|
|
2583
|
+
}
|
|
2584
|
+
diagnostics.set(diagnostic.id, diagnostic);
|
|
2585
|
+
},
|
|
2586
|
+
addRepair(repair) {
|
|
2587
|
+
if (repairs.has(repair.id)) {
|
|
2588
|
+
throw new Error(`Repair id "${repair.id}" is already registered`);
|
|
2589
|
+
}
|
|
2590
|
+
repairs.set(repair.id, repair);
|
|
2591
|
+
},
|
|
2592
|
+
runAll,
|
|
2593
|
+
async runRepair(id, deps) {
|
|
2594
|
+
const repair = repairs.get(id);
|
|
2595
|
+
if (repair === void 0) throw new Error(`Unknown repair id: ${id}`);
|
|
2596
|
+
await repair.run(deps);
|
|
2597
|
+
return runAll(deps);
|
|
2598
|
+
}
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2601
|
+
function crashFinding(diagnosticId, error) {
|
|
2602
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2603
|
+
return {
|
|
2604
|
+
id: `${diagnosticId}.crashed`,
|
|
2605
|
+
source: "connectivity",
|
|
2606
|
+
severity: "error",
|
|
2607
|
+
title: `Diagnostic "${diagnosticId}" crashed`,
|
|
2608
|
+
detail: `The diagnostic itself threw an error: ${message}`
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
// ../engine/src/diagnose/port-probes.ts
|
|
2613
|
+
var PROBE_PATH = "__nanoesis-diagnose-probe.tmp";
|
|
2614
|
+
var PROBE_CONTENT = "__nanoesis-diagnose-probe__";
|
|
2615
|
+
var DIAGNOSTIC_ID = "connectivity.working-store-roundtrip";
|
|
2616
|
+
var workingStoreRoundTripDiagnostic = {
|
|
2617
|
+
id: DIAGNOSTIC_ID,
|
|
2618
|
+
async run({ store }) {
|
|
2619
|
+
const encoder = new TextEncoder();
|
|
2620
|
+
const decoder = new TextDecoder();
|
|
2621
|
+
try {
|
|
2622
|
+
await store.write(PROBE_PATH, encoder.encode(PROBE_CONTENT));
|
|
2623
|
+
} catch (error) {
|
|
2624
|
+
return [
|
|
2625
|
+
finding(
|
|
2626
|
+
"write-failed",
|
|
2627
|
+
"Working store write failed",
|
|
2628
|
+
`The diagnostics probe could not write a temporary file (${PROBE_PATH}). The store's adapter reported: ${describe(error)}`
|
|
2629
|
+
)
|
|
2630
|
+
];
|
|
2631
|
+
}
|
|
2632
|
+
try {
|
|
2633
|
+
const bytes = await store.readBytes(PROBE_PATH);
|
|
2634
|
+
if (decoder.decode(bytes) !== PROBE_CONTENT) {
|
|
2635
|
+
await silently(() => store.delete(PROBE_PATH));
|
|
2636
|
+
return [
|
|
2637
|
+
finding(
|
|
2638
|
+
"read-mismatch",
|
|
2639
|
+
"Working store wrote different bytes than were read back",
|
|
2640
|
+
`The probe wrote ${PROBE_CONTENT.length} bytes but readBytes returned a different value. The store's writes are not durably persisting through reads.`
|
|
2641
|
+
)
|
|
2642
|
+
];
|
|
2643
|
+
}
|
|
2644
|
+
} catch (error) {
|
|
2645
|
+
await silently(() => store.delete(PROBE_PATH));
|
|
2646
|
+
return [
|
|
2647
|
+
finding(
|
|
2648
|
+
"read-failed",
|
|
2649
|
+
"Working store could not read its own probe write",
|
|
2650
|
+
`The diagnostics probe wrote ${PROBE_PATH} but reading it back failed: ${describe(error)}. The write may have been silently dropped, or the store is partitioned (writes and reads going to different backends).`
|
|
2651
|
+
)
|
|
2652
|
+
];
|
|
2653
|
+
}
|
|
2654
|
+
try {
|
|
2655
|
+
const entries = await store.list("");
|
|
2656
|
+
if (!entries.some((entry) => entry.name === PROBE_PATH)) {
|
|
2657
|
+
await silently(() => store.delete(PROBE_PATH));
|
|
2658
|
+
return [
|
|
2659
|
+
finding(
|
|
2660
|
+
"list-misses-probe",
|
|
2661
|
+
"Working store cannot enumerate its own write",
|
|
2662
|
+
`The probe at ${PROBE_PATH} read back successfully, but does not appear when the store lists its root. The store's enumeration is out of sync with what it stores \u2014 typically a content-index drift.`
|
|
2663
|
+
)
|
|
2664
|
+
];
|
|
2665
|
+
}
|
|
2666
|
+
} catch (error) {
|
|
2667
|
+
await silently(() => store.delete(PROBE_PATH));
|
|
2668
|
+
return [
|
|
2669
|
+
finding(
|
|
2670
|
+
"list-failed",
|
|
2671
|
+
"Working store list threw",
|
|
2672
|
+
`Enumeration of the store root failed: ${describe(error)}.`
|
|
2673
|
+
)
|
|
2674
|
+
];
|
|
2675
|
+
}
|
|
2676
|
+
try {
|
|
2677
|
+
await store.delete(PROBE_PATH);
|
|
2678
|
+
} catch (error) {
|
|
2679
|
+
return [
|
|
2680
|
+
finding(
|
|
2681
|
+
"delete-failed",
|
|
2682
|
+
"Working store delete threw",
|
|
2683
|
+
`The probe could not be cleaned up: ${describe(error)}. A file named ${PROBE_PATH} may remain in the store.`
|
|
2684
|
+
)
|
|
2685
|
+
];
|
|
2686
|
+
}
|
|
2687
|
+
try {
|
|
2688
|
+
if (await store.exists(PROBE_PATH)) {
|
|
2689
|
+
return [
|
|
2690
|
+
finding(
|
|
2691
|
+
"delete-ineffective",
|
|
2692
|
+
"Working store delete did not remove the probe",
|
|
2693
|
+
`After deleting ${PROBE_PATH} the store still reports it exists. Deletes are not taking effect \u2014 typically a soft-delete configuration that hides items instead of removing them.`
|
|
2694
|
+
)
|
|
2695
|
+
];
|
|
2696
|
+
}
|
|
2697
|
+
} catch (error) {
|
|
2698
|
+
return [
|
|
2699
|
+
finding(
|
|
2700
|
+
"exists-failed",
|
|
2701
|
+
"Working store exists() threw on the probe",
|
|
2702
|
+
`After the probe was deleted, exists() threw rather than returning false: ${describe(error)}.`
|
|
2703
|
+
)
|
|
2704
|
+
];
|
|
2705
|
+
}
|
|
2706
|
+
return [];
|
|
2707
|
+
}
|
|
2708
|
+
};
|
|
2709
|
+
function finding(suffix, title, detail) {
|
|
2710
|
+
return {
|
|
2711
|
+
id: `${DIAGNOSTIC_ID}.${suffix}`,
|
|
2712
|
+
source: "connectivity",
|
|
2713
|
+
severity: "error",
|
|
2714
|
+
title,
|
|
2715
|
+
detail
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2718
|
+
function describe(error) {
|
|
2719
|
+
return error instanceof Error ? error.message : String(error);
|
|
2720
|
+
}
|
|
2721
|
+
async function silently(work) {
|
|
2722
|
+
try {
|
|
2723
|
+
await work();
|
|
2724
|
+
} catch {
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2563
2728
|
export {
|
|
2564
2729
|
slugify,
|
|
2565
2730
|
humanize,
|
|
@@ -2622,5 +2787,7 @@ export {
|
|
|
2622
2787
|
publishSite,
|
|
2623
2788
|
InMemoryArtifactSink,
|
|
2624
2789
|
hasRole,
|
|
2625
|
-
canEdit
|
|
2790
|
+
canEdit,
|
|
2791
|
+
createDiagnosticRegistry,
|
|
2792
|
+
workingStoreRoundTripDiagnostic
|
|
2626
2793
|
};
|
package/dist/editor-api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WorkingStore, IdentityProvider, PublishResult, ReconcileResult, AuthEndpoints, UserAdminEndpoints, AuthorOption, UserSummary, AuthorDirectory } from '@nanoesis/engine';
|
|
1
|
+
import { WorkingStore, IdentityProvider, PublishResult, ReconcileResult, AuthEndpoints, UserAdminEndpoints, AuthorOption, DiagnosticRegistry, UserSummary, AuthorDirectory, DiagnosticCheck, Repair } from '@nanoesis/engine';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Editor white-labelling storage (DESIGN §11, Phase B). This is the *editor app's*
|
|
@@ -139,6 +139,16 @@ interface ApiDeps {
|
|
|
139
139
|
* store and feeds the *same* users to the compiler's `authorDirectory` at publish.
|
|
140
140
|
*/
|
|
141
141
|
readonly authors?: () => Promise<readonly AuthorOption[]>;
|
|
142
|
+
/**
|
|
143
|
+
* Optional diagnostics + self-heal registry. When present, the router mounts the
|
|
144
|
+
* admin-only `GET /api/admin/diagnostics` (run all probes, return findings) and
|
|
145
|
+
* `POST /api/admin/diagnostics/repair` (run a named repair, return refreshed findings).
|
|
146
|
+
* The registry is port-agnostic: it depends only on the engine's port contracts, so
|
|
147
|
+
* adopters using their own adapters get the same diagnostics with no per-host wiring.
|
|
148
|
+
* Hosts typically supply {@link buildDefaultDiagnostics}; omit on a deployment that
|
|
149
|
+
* has no admin operator (an embedded read-only host).
|
|
150
|
+
*/
|
|
151
|
+
readonly diagnostics?: DiagnosticRegistry;
|
|
142
152
|
}
|
|
143
153
|
/**
|
|
144
154
|
* Dispatch one API request. The editor is private, so every route requires an editing
|
|
@@ -241,4 +251,39 @@ declare function readMcpResource(uri: string): {
|
|
|
241
251
|
*/
|
|
242
252
|
declare const SCAFFOLD_FILES: Readonly<Record<string, string>>;
|
|
243
253
|
|
|
244
|
-
|
|
254
|
+
/**
|
|
255
|
+
* The default {@link DiagnosticRegistry} the host mounts behind `/api/admin/diagnostics`.
|
|
256
|
+
* Combines the engine's port-agnostic round-trip probe (works against any adapter) with
|
|
257
|
+
* the scaffold-aware content checks that need to know what a "home template" is. The
|
|
258
|
+
* editor-api layer is the right home for the latter: the engine has no opinion on what
|
|
259
|
+
* paths matter, while the scaffold is bundled here as pure data (so MCP and every host
|
|
260
|
+
* see the same set; see scaffold.ts).
|
|
261
|
+
*
|
|
262
|
+
* Returned registry is mutable — a host that needs site-specific checks can call `add` /
|
|
263
|
+
* `addRepair` before mounting it.
|
|
264
|
+
*/
|
|
265
|
+
declare function buildDefaultDiagnostics(): DiagnosticRegistry;
|
|
266
|
+
/**
|
|
267
|
+
* `templates/home.html` is the locked path the editor refuses to rename, move, or delete,
|
|
268
|
+
* and the scaffold guarantees it on first run. A missing home template means the site has
|
|
269
|
+
* no home page and no template to fall back to; restoring the bundled scaffold version is
|
|
270
|
+
* a one-click fix (the page is just the user's content again, the template is what was
|
|
271
|
+
* deleted out from under them — typically by a direct blob-store edit).
|
|
272
|
+
*/
|
|
273
|
+
declare const homeTemplateMissingDiagnostic: DiagnosticCheck;
|
|
274
|
+
/**
|
|
275
|
+
* Restore `templates/home.html` from the bundled scaffold. Idempotent: if a customised
|
|
276
|
+
* home template already exists this is a no-op, so a stale UI re-firing the repair never
|
|
277
|
+
* silently overwrites real work. The home template is the only file the scaffold owns
|
|
278
|
+
* as "the locked, blessed copy", so reset semantics are correct here.
|
|
279
|
+
*/
|
|
280
|
+
declare const recreateHomeTemplateRepair: Repair;
|
|
281
|
+
/**
|
|
282
|
+
* A site with zero `isPublished: true` items publishes nothing — a common confused-user
|
|
283
|
+
* state. We surface it as a warning (the publish still completes, just empty), not an
|
|
284
|
+
* error, because the situation is legitimate during early authoring. No auto-repair: the
|
|
285
|
+
* user picks what to publish; we only point out that nothing is.
|
|
286
|
+
*/
|
|
287
|
+
declare const noPublishedContentDiagnostic: DiagnosticCheck;
|
|
288
|
+
|
|
289
|
+
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, handleApi, homeTemplateMissingDiagnostic, noPublishedContentDiagnostic, readMcpResource, recreateHomeTemplateRepair };
|
package/dist/editor-api.js
CHANGED
|
@@ -2,13 +2,15 @@ import {
|
|
|
2
2
|
buildAuthoringReference,
|
|
3
3
|
canEdit,
|
|
4
4
|
contentTypeFor,
|
|
5
|
+
createDiagnosticRegistry,
|
|
5
6
|
deriveFields,
|
|
6
7
|
hasRole,
|
|
7
8
|
loadComponents,
|
|
8
9
|
loadTemplate,
|
|
9
10
|
renderReferenceMarkdown,
|
|
10
|
-
validateSite
|
|
11
|
-
|
|
11
|
+
validateSite,
|
|
12
|
+
workingStoreRoundTripDiagnostic
|
|
13
|
+
} from "./chunk-J6VYOB47.js";
|
|
12
14
|
|
|
13
15
|
// ../editor-api/src/scaffold.ts
|
|
14
16
|
var HOME_HTML = `<!doctype html>
|
|
@@ -227,6 +229,31 @@ async function handleUserAdminRoute(deps, req, caller) {
|
|
|
227
229
|
}
|
|
228
230
|
return json(404, { ok: false, error: `Unknown API route: ${req.path}` });
|
|
229
231
|
}
|
|
232
|
+
async function handleDiagnosticsRoute(deps, req, caller) {
|
|
233
|
+
const registry = deps.diagnostics;
|
|
234
|
+
if (registry === void 0) {
|
|
235
|
+
return json(404, { ok: false, error: "diagnostics are not available on this host" });
|
|
236
|
+
}
|
|
237
|
+
if (!hasRole(caller, "admin")) {
|
|
238
|
+
return json(403, { ok: false, error: "admin role required" });
|
|
239
|
+
}
|
|
240
|
+
if (req.path === "/api/admin/diagnostics") {
|
|
241
|
+
if (req.method !== "GET") return json(405, { ok: false, error: "method not allowed" });
|
|
242
|
+
const findings = await registry.runAll({ store: deps.store });
|
|
243
|
+
return json(200, { findings });
|
|
244
|
+
}
|
|
245
|
+
if (req.method !== "POST") return json(405, { ok: false, error: "method not allowed" });
|
|
246
|
+
const body = await parseJsonBody(req);
|
|
247
|
+
if (!isObject(body) || typeof body["id"] !== "string") {
|
|
248
|
+
return json(400, { ok: false, error: "JSON body with repair id required" });
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
const findings = await registry.runRepair(body["id"], { store: deps.store });
|
|
252
|
+
return json(200, { findings });
|
|
253
|
+
} catch (error) {
|
|
254
|
+
return json(400, { ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
230
257
|
async function handleBrandingRoute(deps, req) {
|
|
231
258
|
if (req.path !== "/api/branding" && req.path !== "/api/branding/logo") return void 0;
|
|
232
259
|
const store = deps.branding;
|
|
@@ -292,6 +319,9 @@ async function dispatchApi(deps, req) {
|
|
|
292
319
|
if (req.path === "/api/users" || req.path.startsWith("/api/users/")) {
|
|
293
320
|
return handleUserAdminRoute(deps, req, principal);
|
|
294
321
|
}
|
|
322
|
+
if (req.path === "/api/admin/diagnostics" || req.path === "/api/admin/diagnostics/repair") {
|
|
323
|
+
return handleDiagnosticsRoute(deps, req, principal);
|
|
324
|
+
}
|
|
295
325
|
const get = (key) => req.query.get(key) ?? "";
|
|
296
326
|
switch (req.path) {
|
|
297
327
|
case "/api/list": {
|
|
@@ -610,6 +640,82 @@ function readMcpResource(uri) {
|
|
|
610
640
|
return void 0;
|
|
611
641
|
}
|
|
612
642
|
|
|
643
|
+
// ../editor-api/src/diagnose.ts
|
|
644
|
+
function buildDefaultDiagnostics() {
|
|
645
|
+
const registry = createDiagnosticRegistry();
|
|
646
|
+
registry.add(workingStoreRoundTripDiagnostic);
|
|
647
|
+
registry.add(homeTemplateMissingDiagnostic);
|
|
648
|
+
registry.add(noPublishedContentDiagnostic);
|
|
649
|
+
registry.addRepair(recreateHomeTemplateRepair);
|
|
650
|
+
return registry;
|
|
651
|
+
}
|
|
652
|
+
var HOME_PATH = "templates/home.html";
|
|
653
|
+
var homeTemplateMissingDiagnostic = {
|
|
654
|
+
id: "content.home-template",
|
|
655
|
+
async run({ store }) {
|
|
656
|
+
if (await store.exists(HOME_PATH)) return [];
|
|
657
|
+
return [
|
|
658
|
+
{
|
|
659
|
+
id: "content.home-template.missing",
|
|
660
|
+
source: "content",
|
|
661
|
+
severity: "error",
|
|
662
|
+
title: "Home template is missing",
|
|
663
|
+
detail: `The locked path ${HOME_PATH} does not exist. Without it, the editor cannot render the home page and publishing will fail to compile the index. Recreate it from the bundled scaffold to restore the working state.`,
|
|
664
|
+
repair: { id: "content.home-template.recreate", label: "Recreate home template" }
|
|
665
|
+
}
|
|
666
|
+
];
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
var recreateHomeTemplateRepair = {
|
|
670
|
+
id: "content.home-template.recreate",
|
|
671
|
+
async run({ store }) {
|
|
672
|
+
if (await store.exists(HOME_PATH)) return;
|
|
673
|
+
const bytes = new TextEncoder().encode(SCAFFOLD_FILES[HOME_PATH]);
|
|
674
|
+
await store.write(HOME_PATH, bytes);
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
var noPublishedContentDiagnostic = {
|
|
678
|
+
id: "content.no-published-content",
|
|
679
|
+
async run({ store }) {
|
|
680
|
+
const published = await anyPublishedContentItem(store);
|
|
681
|
+
if (published) return [];
|
|
682
|
+
return [
|
|
683
|
+
{
|
|
684
|
+
id: "content.no-published-content",
|
|
685
|
+
source: "content",
|
|
686
|
+
severity: "warning",
|
|
687
|
+
title: "Nothing is published",
|
|
688
|
+
detail: "No content item has isPublished: true, so a publish would produce no pages. Open a content item and toggle Published, then publish again."
|
|
689
|
+
}
|
|
690
|
+
];
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
async function anyPublishedContentItem(store, dir = "content") {
|
|
694
|
+
let entries;
|
|
695
|
+
try {
|
|
696
|
+
entries = await store.list(dir);
|
|
697
|
+
} catch {
|
|
698
|
+
return false;
|
|
699
|
+
}
|
|
700
|
+
for (const entry of entries) {
|
|
701
|
+
const path = dir === "" ? entry.name : `${dir}/${entry.name}`;
|
|
702
|
+
if (entry.kind === "dir") {
|
|
703
|
+
if (await anyPublishedContentItem(store, path)) return true;
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
if (!entry.name.endsWith(".json")) continue;
|
|
707
|
+
try {
|
|
708
|
+
const text = await store.readText(path);
|
|
709
|
+
const data = JSON.parse(text);
|
|
710
|
+
if (typeof data === "object" && data !== null && data.isPublished === true) {
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
} catch {
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
|
|
613
719
|
// ../editor-api/src/branding.ts
|
|
614
720
|
import { mkdir, readFile, rename, rm, writeFile } from "fs/promises";
|
|
615
721
|
import { dirname, join } from "path";
|
|
@@ -714,7 +820,11 @@ export {
|
|
|
714
820
|
SCAFFOLD_FILES,
|
|
715
821
|
authorDirectory,
|
|
716
822
|
authorOptions,
|
|
823
|
+
buildDefaultDiagnostics,
|
|
717
824
|
callMcpTool,
|
|
718
825
|
handleApi,
|
|
719
|
-
|
|
826
|
+
homeTemplateMissingDiagnostic,
|
|
827
|
+
noPublishedContentDiagnostic,
|
|
828
|
+
readMcpResource,
|
|
829
|
+
recreateHomeTemplateRepair
|
|
720
830
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -919,18 +919,18 @@ declare function analyzeTemplate(source: string, components?: ComponentMap): Tem
|
|
|
919
919
|
* are returned as data (not thrown), so a host can present the clear list a user
|
|
920
920
|
* needs (CLAUDE.md §2: "3 things would break: …").
|
|
921
921
|
*/
|
|
922
|
-
type Severity = 'error' | 'warning';
|
|
923
|
-
interface Diagnostic {
|
|
924
|
-
readonly severity: Severity;
|
|
922
|
+
type Severity$1 = 'error' | 'warning';
|
|
923
|
+
interface Diagnostic$1 {
|
|
924
|
+
readonly severity: Severity$1;
|
|
925
925
|
readonly code: string;
|
|
926
926
|
readonly message: string;
|
|
927
927
|
/** Content path of the item/dir the diagnostic concerns, when applicable. */
|
|
928
928
|
readonly path?: string;
|
|
929
929
|
}
|
|
930
930
|
interface ValidationResult {
|
|
931
|
-
readonly diagnostics: readonly Diagnostic[];
|
|
932
|
-
readonly errors: readonly Diagnostic[];
|
|
933
|
-
readonly warnings: readonly Diagnostic[];
|
|
931
|
+
readonly diagnostics: readonly Diagnostic$1[];
|
|
932
|
+
readonly errors: readonly Diagnostic$1[];
|
|
933
|
+
readonly warnings: readonly Diagnostic$1[];
|
|
934
934
|
/** True when there are no errors, i.e. a publish may proceed. */
|
|
935
935
|
readonly ok: boolean;
|
|
936
936
|
}
|
|
@@ -1302,4 +1302,93 @@ declare function escapeJsonStringContent(value: string): string;
|
|
|
1302
1302
|
*/
|
|
1303
1303
|
declare function sanitizeUrl(url: string): string;
|
|
1304
1304
|
|
|
1305
|
-
|
|
1305
|
+
/**
|
|
1306
|
+
* The diagnostics + self-heal feature (CLAUDE.md §2: expected failures are data, not
|
|
1307
|
+
* exceptions; we generalise the validation gate's shape from "is this content
|
|
1308
|
+
* publishable?" to "is this whole installation healthy?"). A diagnostic is a port-agnostic
|
|
1309
|
+
* probe that returns {@link Finding}s; a repair is an idempotent fix that runs through the
|
|
1310
|
+
* same ports. Both layers know nothing of any specific host or adapter: when an adapter
|
|
1311
|
+
* fails a round-trip probe, the surfacing detail is whatever that adapter put in its
|
|
1312
|
+
* thrown error's message. Adapter authors who want richer detail write richer errors,
|
|
1313
|
+
* not a new interface on the engine.
|
|
1314
|
+
*/
|
|
1315
|
+
type Severity = 'error' | 'warning' | 'info';
|
|
1316
|
+
/**
|
|
1317
|
+
* What broad area of the system a finding concerns; UI groups by this. Deliberately
|
|
1318
|
+
* coarse and stable — adding adapter-specific categories ("kv", "cloudflare", …) would
|
|
1319
|
+
* couple the engine to specific cloud concepts, which is what we are avoiding.
|
|
1320
|
+
*/
|
|
1321
|
+
type Source = 'content' | 'index' | 'auth' | 'connectivity';
|
|
1322
|
+
/**
|
|
1323
|
+
* One thing the diagnostics gate found wrong (or noteworthy). Shape mirrors the
|
|
1324
|
+
* validation gate's {@link Diagnostic} type so a future merger is one step.
|
|
1325
|
+
*/
|
|
1326
|
+
interface Finding {
|
|
1327
|
+
/** Stable across runs of the same diagnostic, so the UI can dedupe and target repairs. */
|
|
1328
|
+
readonly id: string;
|
|
1329
|
+
readonly source: Source;
|
|
1330
|
+
readonly severity: Severity;
|
|
1331
|
+
/** Short human-readable headline ("Home template is missing"). */
|
|
1332
|
+
readonly title: string;
|
|
1333
|
+
/** Full prose explaining what's wrong and where it was detected. */
|
|
1334
|
+
readonly detail: string;
|
|
1335
|
+
/** Present iff the engine can fix this in-place. Manual fixes go in `detail` text. */
|
|
1336
|
+
readonly repair?: {
|
|
1337
|
+
readonly id: string;
|
|
1338
|
+
readonly label: string;
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
/**
|
|
1342
|
+
* The ports a diagnostic is allowed to depend on. Just the working store today, the
|
|
1343
|
+
* port the editor's content API already binds (DESIGN §11c). Round-trip probes exercise
|
|
1344
|
+
* `write`/`list`/`exists`/`readBytes`/`delete`; content checks just read. Anything beyond
|
|
1345
|
+
* those (a `PurgeService`, a `BlobStore`, …) is added here as a new optional field, never
|
|
1346
|
+
* a host- or adapter-specific surface.
|
|
1347
|
+
*/
|
|
1348
|
+
interface DiagnoseDeps {
|
|
1349
|
+
readonly store: WorkingStore;
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* One probe. `id` is stable and used to namespace the finding ids it emits. `run` returns
|
|
1353
|
+
* the {@link Finding}s it generated (empty array when there's nothing to report).
|
|
1354
|
+
*/
|
|
1355
|
+
interface Diagnostic {
|
|
1356
|
+
readonly id: string;
|
|
1357
|
+
run(deps: DiagnoseDeps): Promise<readonly Finding[]>;
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* One in-place fix. Idempotent: calling `run` twice does not double-write. The host
|
|
1361
|
+
* re-runs the diagnostic set after a repair to refresh the UI, so repairs don't have to
|
|
1362
|
+
* return findings themselves.
|
|
1363
|
+
*/
|
|
1364
|
+
interface Repair {
|
|
1365
|
+
readonly id: string;
|
|
1366
|
+
run(deps: DiagnoseDeps): Promise<void>;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
/**
|
|
1370
|
+
* The diagnostic registry: an in-process collection of {@link Diagnostic}s and
|
|
1371
|
+
* {@link Repair}s the host's `/api/admin/diagnostics` route runs. A buggy diagnostic does
|
|
1372
|
+
* not take down the page — its thrown error is caught and surfaced as a finding with
|
|
1373
|
+
* `id` "<diagnostic-id>.crashed", so the user sees the failure exactly the way they see
|
|
1374
|
+
* every other finding. (CLAUDE.md §2 spirit: failures are data, not exceptions, even when
|
|
1375
|
+
* the failure is the probe itself.)
|
|
1376
|
+
*
|
|
1377
|
+
* Ids are unique per registry; registering a duplicate throws, since that is a
|
|
1378
|
+
* programmer error, not user-visible data.
|
|
1379
|
+
*/
|
|
1380
|
+
interface DiagnosticRegistry {
|
|
1381
|
+
/** Register a diagnostic. Throws if its id is already in the registry. */
|
|
1382
|
+
add(diagnostic: Diagnostic): void;
|
|
1383
|
+
/** Register a repair. Throws if its id is already in the registry. */
|
|
1384
|
+
addRepair(repair: Repair): void;
|
|
1385
|
+
/** Run every registered diagnostic in order; concatenate their findings. */
|
|
1386
|
+
runAll(deps: DiagnoseDeps): Promise<readonly Finding[]>;
|
|
1387
|
+
/** Run the named repair, then return a fresh `runAll` so the UI re-renders. */
|
|
1388
|
+
runRepair(id: string, deps: DiagnoseDeps): Promise<readonly Finding[]>;
|
|
1389
|
+
}
|
|
1390
|
+
declare function createDiagnosticRegistry(): DiagnosticRegistry;
|
|
1391
|
+
|
|
1392
|
+
declare const workingStoreRoundTripDiagnostic: Diagnostic;
|
|
1393
|
+
|
|
1394
|
+
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 PageEntry, 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 ResetPasswordRequest, type ResolveContext, type Role, type RssOptions, type Scope, type Severity$1 as Severity, type SiteConfig, type SortFile, type TemplateAnalysis, type TokenContext, type TokenRef, type TreeNode, type UpdateUserRequest, type UserAdminEndpoints, type UserSummary, type ValidationResult, type ValueKind, type WorkingStore, analyzeTemplate, buildAuthoringReference, buildContentIndex, buildPictureMarkup, buildRedirects, buildResolveContext, buildRss, buildSitemap, canEdit, compilePage, compileSite, compileTemplate, contentHash, contentTypeFor, createDiagnosticRegistry, deriveFields, emptyIndex, escapeHtmlAttribute, escapeHtmlText, escapeJsonStringContent, findTokens, hasRole, humanize, inferControl, isFieldType, joinAuthors, loadComponentScripts, loadComponentStyles, loadComponents, loadContentTree, loadDocumentShell, loadIndex, loadRedirects, loadSiteConfig, loadTemplate, noopPurgeService, outputPathForItem, parseContentItem, parseRedirects, parseSortFile, processImage, publishSite, reconcileIndex, renderAuthors, renderReferenceMarkdown, sanitizeUrl, saveIndex, slugify, textContent, toAuthorRefs, urlForItem, validateSite, valueKindOf, wholeValueToken, workingStoreRoundTripDiagnostic };
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
compileTemplate,
|
|
23
23
|
contentHash,
|
|
24
24
|
contentTypeFor,
|
|
25
|
+
createDiagnosticRegistry,
|
|
25
26
|
deriveFields,
|
|
26
27
|
emptyIndex,
|
|
27
28
|
escapeHtmlAttribute,
|
|
@@ -60,8 +61,9 @@ import {
|
|
|
60
61
|
urlForItem,
|
|
61
62
|
validateSite,
|
|
62
63
|
valueKindOf,
|
|
63
|
-
wholeValueToken
|
|
64
|
-
|
|
64
|
+
wholeValueToken,
|
|
65
|
+
workingStoreRoundTripDiagnostic
|
|
66
|
+
} from "./chunk-J6VYOB47.js";
|
|
65
67
|
export {
|
|
66
68
|
ContentParseError,
|
|
67
69
|
DEFAULT_DIRS,
|
|
@@ -86,6 +88,7 @@ export {
|
|
|
86
88
|
compileTemplate,
|
|
87
89
|
contentHash,
|
|
88
90
|
contentTypeFor,
|
|
91
|
+
createDiagnosticRegistry,
|
|
89
92
|
deriveFields,
|
|
90
93
|
emptyIndex,
|
|
91
94
|
escapeHtmlAttribute,
|
|
@@ -124,5 +127,6 @@ export {
|
|
|
124
127
|
urlForItem,
|
|
125
128
|
validateSite,
|
|
126
129
|
valueKindOf,
|
|
127
|
-
wholeValueToken
|
|
130
|
+
wholeValueToken,
|
|
131
|
+
workingStoreRoundTripDiagnostic
|
|
128
132
|
};
|