@riebeckite/honox 0.0.31 → 0.0.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/index.js CHANGED
@@ -293,20 +293,17 @@ import {
293
293
  // src/content_assets.ts
294
294
  import fs5 from "node:fs/promises";
295
295
  import path7 from "node:path";
296
- import {
297
- isIgnoredContentPath as isIgnoredContentPath2,
298
- isImagePath as isImagePath2,
299
- normalizeContentPath as normalizeContentPath2
300
- } from "@riebeckite/core";
296
+ import { isImagePath as isImagePath2, normalizeContentPath as normalizeContentPath2 } from "@riebeckite/core";
297
+ import { isIgnoredContentPath as isIgnoredContentPath2 } from "@riebeckite/node";
301
298
 
302
299
  // src/content_watch.ts
303
300
  import path6 from "node:path";
304
301
  import {
305
302
  isAttachmentPath,
306
- isIgnoredContentPath,
307
303
  isImagePath,
308
304
  normalizeContentPath
309
305
  } from "@riebeckite/core";
306
+ import { isIgnoredContentPath } from "@riebeckite/node";
310
307
 
311
308
  // src/runtime_modules.ts
312
309
  var riebeckiteConfigModuleId = "virtual:riebeckite/config";
@@ -355,11 +352,15 @@ export const config = resolveHonoxConfig(
355
352
  }
356
353
  function createContentModule() {
357
354
  return `import { ContentManager } from "@riebeckite/core";
355
+ import { FileSystemContentSource } from "@riebeckite/node";
358
356
  import { config } from ${JSON.stringify(riebeckiteConfigModuleId)};
359
357
 
360
358
  export const content = new ContentManager(
361
- config.content.directory,
362
- config.content.exclude,
359
+ config.content.source ?? new FileSystemContentSource(
360
+ config.content.directory,
361
+ config.content.exclude,
362
+ ),
363
+ [],
363
364
  { config, plugins: config.plugins },
364
365
  );
365
366
  `;
@@ -996,7 +997,7 @@ async function writeSsgOutputMetrics(metrics) {
996
997
  }
997
998
 
998
999
  // src/ssg/output_cache.ts
999
- var faultInjectionCounts = /* @__PURE__ */ new Map();
1000
+ var outputCacheValidationConcurrency = 12;
1000
1001
  function decideOutputCacheRejection(decision) {
1001
1002
  if (!decision.changeSetPresent) return "change-set-unavailable";
1002
1003
  if (decision.changeSetRequiresFullRegeneration)
@@ -1112,7 +1113,6 @@ async function saveOutputCache(path11, state, outputDirectory, timingsMs) {
1112
1113
  const entriesPath = outputCacheEntriesPath(path11);
1113
1114
  const temporaryPath = `${path11}.${randomUUID2()}.tmp`;
1114
1115
  try {
1115
- await waitForFaultInjection("metadata-save-start");
1116
1116
  const outputs = await measure(
1117
1117
  timingsMs,
1118
1118
  "outputCacheEntriesWrite",
@@ -1140,7 +1140,6 @@ async function saveOutputCache(path11, state, outputDirectory, timingsMs) {
1140
1140
  "outputCacheRename",
1141
1141
  () => replaceFile(temporaryPath, path11)
1142
1142
  );
1143
- await waitForFaultInjection("metadata-committed");
1144
1143
  const checksum = createHash3("sha256").update(serialized).digest("hex");
1145
1144
  if (state.finalOutputInventory && state.configFile) {
1146
1145
  await saveFastPathCache(
@@ -1167,7 +1166,7 @@ function isOutputCacheState(value) {
1167
1166
  }
1168
1167
  function isOutputInventory(value) {
1169
1168
  return typeof value === "object" && value !== null && !Array.isArray(value) && Object.entries(value).every(
1170
- ([outputPath, checksum]) => isSafeLogicalOutputPath(outputPath) && isChecksum(checksum)
1169
+ ([outputPath2, checksum]) => isSafeLogicalOutputPath(outputPath2) && isChecksum(checksum)
1171
1170
  );
1172
1171
  }
1173
1172
  function isOutputCacheEntries(value) {
@@ -1175,7 +1174,7 @@ function isOutputCacheEntries(value) {
1175
1174
  return false;
1176
1175
  }
1177
1176
  return Object.entries(value).every(
1178
- ([outputPath, entry]) => isSafeLogicalOutputPath(outputPath) && typeof entry === "object" && entry !== null && typeof entry.cachePath === "string" && entry.cachePath === outputCacheEntryName(outputPath) && isChecksum(entry.checksum)
1177
+ ([outputPath2, entry]) => isSafeLogicalOutputPath(outputPath2) && typeof entry === "object" && entry !== null && typeof entry.cachePath === "string" && entry.cachePath === outputCacheEntryName(outputPath2) && isChecksum(entry.checksum)
1179
1178
  );
1180
1179
  }
1181
1180
  function outputCacheEntriesPath(outputCachePath) {
@@ -1184,31 +1183,30 @@ function outputCacheEntriesPath(outputCachePath) {
1184
1183
  async function saveOutputCacheEntries(directory, outputDirectory, outputs, finalOutputInventory, timingsMs) {
1185
1184
  await ensureOutputCacheEntriesDirectory(directory);
1186
1185
  const entries = {};
1187
- for (const outputPath of Object.keys(outputs).sort()) {
1188
- assertSafeLogicalOutputPath(outputPath);
1189
- const existing = outputs[outputPath];
1186
+ for (const outputPath2 of Object.keys(outputs).sort()) {
1187
+ assertSafeLogicalOutputPath(outputPath2);
1188
+ const existing = outputs[outputPath2];
1190
1189
  if (existing.cachePath && existing.checksum) {
1191
- if (existing.cachePath !== outputCacheEntryName(outputPath) || !isChecksum(existing.checksum)) {
1192
- throw new Error(`Invalid SSG output cache entry: ${outputPath}`);
1190
+ if (existing.cachePath !== outputCacheEntryName(outputPath2) || !isChecksum(existing.checksum)) {
1191
+ throw new Error(`Invalid SSG output cache entry: ${outputPath2}`);
1193
1192
  }
1194
- entries[outputPath] = existing;
1193
+ entries[outputPath2] = existing;
1195
1194
  continue;
1196
1195
  }
1197
- const source = resolveOutputCacheFile(outputDirectory, outputPath);
1198
- const cachePath = outputCacheEntryName(outputPath);
1196
+ const source = resolveOutputCacheFile(outputDirectory, outputPath2);
1197
+ const cachePath = outputCacheEntryName(outputPath2);
1199
1198
  const destination = join3(directory, cachePath);
1200
- await waitForFaultInjection("entry-create-start");
1201
1199
  await replaceOutputCacheEntry(source, destination, timingsMs);
1202
- const checksum = finalOutputInventory?.[outputPath];
1200
+ const checksum = finalOutputInventory?.[outputPath2];
1203
1201
  if (!checksum) {
1204
1202
  const cachedChecksum = await checksumFile(destination);
1205
1203
  if (!cachedChecksum) {
1206
- throw new Error(`Could not checksum cached output: ${outputPath}`);
1204
+ throw new Error(`Could not checksum cached output: ${outputPath2}`);
1207
1205
  }
1208
- entries[outputPath] = { cachePath, checksum: cachedChecksum };
1206
+ entries[outputPath2] = { cachePath, checksum: cachedChecksum };
1209
1207
  continue;
1210
1208
  }
1211
- entries[outputPath] = { cachePath, checksum };
1209
+ entries[outputPath2] = { cachePath, checksum };
1212
1210
  }
1213
1211
  return entries;
1214
1212
  }
@@ -1216,20 +1214,43 @@ async function validateOutputCacheEntries(outputCachePath, entries, timingsMs) {
1216
1214
  try {
1217
1215
  const directory = outputCacheEntriesPath(outputCachePath);
1218
1216
  if (!await isSafeOutputCacheEntriesDirectory(directory)) return false;
1219
- for (const [outputPath, entry] of Object.entries(entries)) {
1220
- if (!isSafeLogicalOutputPath(outputPath) || entry.cachePath !== outputCacheEntryName(outputPath) || !isChecksum(entry.checksum)) {
1221
- return false;
1217
+ const entriesToValidate = Object.entries(entries);
1218
+ let nextEntryIndex = 0;
1219
+ let valid = true;
1220
+ const validateEntry = async () => {
1221
+ while (valid) {
1222
+ const next = entriesToValidate[nextEntryIndex++];
1223
+ if (!next) return;
1224
+ const [outputPath2, entry] = next;
1225
+ if (!isSafeLogicalOutputPath(outputPath2) || entry.cachePath !== outputCacheEntryName(outputPath2) || !isChecksum(entry.checksum)) {
1226
+ valid = false;
1227
+ return;
1228
+ }
1229
+ const path11 = resolveOutputCacheFile(directory, entry.cachePath);
1230
+ if (!await isRegularFile(path11)) {
1231
+ valid = false;
1232
+ return;
1233
+ }
1234
+ if (await measure(
1235
+ timingsMs,
1236
+ "outputCacheEntryChecksum",
1237
+ () => checksumFile(path11)
1238
+ ) !== entry.checksum)
1239
+ valid = false;
1222
1240
  }
1223
- const path11 = resolveOutputCacheFile(directory, entry.cachePath);
1224
- if (!await isRegularFile(path11)) return false;
1225
- if (await measure(
1226
- timingsMs,
1227
- "outputCacheEntryChecksum",
1228
- () => checksumFile(path11)
1229
- ) !== entry.checksum)
1230
- return false;
1231
- }
1232
- return true;
1241
+ };
1242
+ await Promise.all(
1243
+ Array.from(
1244
+ {
1245
+ length: Math.min(
1246
+ outputCacheValidationConcurrency,
1247
+ entriesToValidate.length
1248
+ )
1249
+ },
1250
+ validateEntry
1251
+ )
1252
+ );
1253
+ return valid;
1233
1254
  } catch {
1234
1255
  return false;
1235
1256
  }
@@ -1263,7 +1284,6 @@ async function saveFastPathCache(path11, state, timingsMs) {
1263
1284
  }
1264
1285
  const temporaryPath = `${path11}.${randomUUID2()}.tmp`;
1265
1286
  try {
1266
- await waitForFaultInjection("sidecar-save-start");
1267
1287
  await measure(
1268
1288
  timingsMs,
1269
1289
  "sidecarWrite",
@@ -1275,7 +1295,6 @@ async function saveFastPathCache(path11, state, timingsMs) {
1275
1295
  "sidecarRename",
1276
1296
  () => replaceFile(temporaryPath, path11)
1277
1297
  );
1278
- await waitForFaultInjection("sidecar-committed");
1279
1298
  } catch (error) {
1280
1299
  await rm2(temporaryPath, { force: true }).catch(() => void 0);
1281
1300
  throw error;
@@ -1358,8 +1377,8 @@ function assertSafeLogicalOutputPath(path11) {
1358
1377
  throw new Error(`Invalid SSG output path: ${path11}`);
1359
1378
  }
1360
1379
  }
1361
- function outputCacheEntryName(outputPath) {
1362
- return createHash3("sha256").update(outputPath).digest("hex");
1380
+ function outputCacheEntryName(outputPath2) {
1381
+ return createHash3("sha256").update(outputPath2).digest("hex");
1363
1382
  }
1364
1383
  function isChecksum(value) {
1365
1384
  return typeof value === "string" && /^[a-f0-9]{64}$/u.test(value);
@@ -1407,19 +1426,16 @@ async function replaceOutputCacheEntry(source, destination, timingsMs) {
1407
1426
  "outputCacheEntryWrite",
1408
1427
  () => writeFile2(temporaryPath, new Uint8Array())
1409
1428
  );
1410
- await waitForFaultInjection("entry-body-write");
1411
1429
  await measure(
1412
1430
  timingsMs,
1413
1431
  "outputCacheEntryCopy",
1414
1432
  () => copyFile(source, temporaryPath)
1415
1433
  );
1416
- await waitForFaultInjection("entry-rename-before");
1417
1434
  await measure(
1418
1435
  timingsMs,
1419
1436
  "outputCacheEntryRename",
1420
1437
  () => replaceFile(temporaryPath, destination)
1421
1438
  );
1422
- await waitForFaultInjection("entry-committed");
1423
1439
  } catch (error) {
1424
1440
  await rm2(temporaryPath, { force: true }).catch(() => void 0);
1425
1441
  throw error;
@@ -1428,7 +1444,6 @@ async function replaceOutputCacheEntry(source, destination, timingsMs) {
1428
1444
  async function collectUnreferencedOutputCacheEntries(directory, outputs, timingsMs) {
1429
1445
  try {
1430
1446
  if (!await isSafeOutputCacheEntriesDirectory(directory)) return;
1431
- await waitForFaultInjection("gc-start");
1432
1447
  const referenced = new Set(
1433
1448
  Object.values(outputs).map((entry) => entry.cachePath)
1434
1449
  );
@@ -1443,7 +1458,6 @@ async function collectUnreferencedOutputCacheEntries(directory, outputs, timings
1443
1458
  }
1444
1459
  const path11 = join3(directory, entry.name);
1445
1460
  if (await isRegularFile(path11)) {
1446
- await waitForFaultInjection("gc-entry-delete");
1447
1461
  await measure(
1448
1462
  timingsMs,
1449
1463
  "outputCacheGcDelete",
@@ -1455,25 +1469,6 @@ async function collectUnreferencedOutputCacheEntries(directory, outputs, timings
1455
1469
  return;
1456
1470
  }
1457
1471
  }
1458
- async function waitForFaultInjection(point) {
1459
- if (process.env.NODE_ENV !== "test" || process.env.RIEBECKITE_SSG_FAULT_INJECTION !== "1" || process.env.RIEBECKITE_SSG_FAULT_POINT !== point) {
1460
- return;
1461
- }
1462
- const occurrence = Number.parseInt(
1463
- process.env.RIEBECKITE_SSG_FAULT_OCCURRENCE ?? "1",
1464
- 10
1465
- );
1466
- const count = (faultInjectionCounts.get(point) ?? 0) + 1;
1467
- faultInjectionCounts.set(point, count);
1468
- if (!Number.isSafeInteger(occurrence) || occurrence < 1 || count !== occurrence)
1469
- return;
1470
- const signalFile = process.env.RIEBECKITE_SSG_FAULT_SIGNAL_FILE;
1471
- if (!signalFile) return;
1472
- await writeFile2(signalFile, `${point}
1473
- `, "utf8");
1474
- await new Promise(() => {
1475
- });
1476
- }
1477
1472
 
1478
1473
  // src/ssg_plugin.ts
1479
1474
  var defaultEntry = "./src/index.tsx";
@@ -1543,8 +1538,17 @@ function riebeckiteSsg(options = {}) {
1543
1538
  ...config.resolve,
1544
1539
  builtins: [...config.resolve.builtins, /^node:/]
1545
1540
  },
1546
- plugins: [],
1541
+ plugins: [
1542
+ {
1543
+ name: "riebeckite-ssg-package-resolution",
1544
+ enforce: "post",
1545
+ configResolved(runtimeConfig) {
1546
+ runtimeConfig.resolve.alias = [];
1547
+ }
1548
+ }
1549
+ ],
1547
1550
  build: { ssr: true },
1551
+ oxc: { jsx: { runtime: "automatic", importSource: "hono/jsx" } },
1548
1552
  environments: {
1549
1553
  ssr: { resolve: { noExternal: [/^honox(?:\/.*)?$/] } }
1550
1554
  },
@@ -1595,7 +1599,7 @@ function riebeckiteSsg(options = {}) {
1595
1599
  );
1596
1600
  viteClientAssetPaths = [
1597
1601
  .../* @__PURE__ */ new Set([...viteClientAssetPaths, ...Object.keys(bundle)])
1598
- ].filter((outputPath) => !siteOwnedOutputPaths.has(outputPath)).sort();
1602
+ ].filter((outputPath2) => !siteOwnedOutputPaths.has(outputPath2)).sort();
1599
1603
  const siteOutputCollisions = findBuildOutputSiteCollisions(
1600
1604
  Object.keys(bundle),
1601
1605
  siteOwnedOutputPaths
@@ -1924,14 +1928,12 @@ function riebeckiteSsg(options = {}) {
1924
1928
  "publicFingerprint",
1925
1929
  () => fingerprintDirectory(resolve2(publicDir))
1926
1930
  ) : void 0;
1927
- await waitForFaultInjection("build-state-before");
1928
1931
  await saveOutputCache(
1929
1932
  outputCache.path,
1930
1933
  outputCache.state,
1931
1934
  outputCache.outDir,
1932
1935
  outputCache.timingsMs
1933
1936
  );
1934
- await waitForFaultInjection("build-state-committed");
1935
1937
  } catch (error) {
1936
1938
  this.warn(
1937
1939
  `SSG output cache could not be saved; the next build regenerates every output. ${error instanceof Error ? error.message : String(error)}`
@@ -2091,55 +2093,160 @@ function riebeckiteClientModule(getConfig) {
2091
2093
  }
2092
2094
  function createClientModule(config, development) {
2093
2095
  const imports = [];
2094
- const initializers = [];
2096
+ const appInitializers = [];
2097
+ const pageInitializers = [];
2098
+ imports.push('import { ClientLifecycle } from "@riebeckite/core/client";');
2099
+ if (config.spa !== void 0) {
2100
+ imports.push('import { createClient } from "honox/client";');
2101
+ imports.push('import { startSpaNavigation } from "@riebeckite/spa";');
2102
+ }
2095
2103
  if (development) {
2096
2104
  imports.push(
2097
2105
  'import { initDevelopmentInspector } from "virtual:riebeckite/development-inspector";'
2098
2106
  );
2099
- initializers.push("initDevelopmentInspector()");
2107
+ appInitializers.push("initDevelopmentInspector()");
2100
2108
  }
2101
2109
  for (const [index, entry] of config.plugins.flatMap((plugin) => plugin.clientEntries ?? []).entries()) {
2102
2110
  const localName = `pluginClientInitializer${index}`;
2103
2111
  const importTarget = entry.exportName ? `{ ${entry.exportName} as ${localName} }` : localName;
2104
- imports.push(
2105
- `import ${importTarget} from ${JSON.stringify(entry.moduleSpecifier)};`
2106
- );
2112
+ if (entry.presenceSelector === void 0) {
2113
+ imports.push(
2114
+ `import ${importTarget} from ${JSON.stringify(entry.moduleSpecifier)};`
2115
+ );
2116
+ }
2107
2117
  const publicConfig = entry.publicConfig;
2108
- const initializer = publicConfig === void 0 ? `${localName}()` : `${localName}(${serializePublicClientConfig(publicConfig)})`;
2118
+ const serializedConfig = publicConfig === void 0 ? "undefined" : serializePublicClientConfig(publicConfig);
2119
+ const initializer = entry.initializerArguments === "context" ? `${localName}(context)` : entry.initializerArguments === "root" ? `${localName}(context.root)` : `${localName}(context.config)`;
2120
+ const initializers = entry.scope === "app" ? appInitializers : pageInitializers;
2121
+ if (entry.presenceSelector === void 0) {
2122
+ initializers.push(
2123
+ `mountPluginClientInitializer(${JSON.stringify(entry.scope ?? "page")}, ${JSON.stringify(entry.moduleSpecifier)}, root, ${serializedConfig}, (context) => ${initializer})`
2124
+ );
2125
+ continue;
2126
+ }
2127
+ const dynamicInitializer = entry.initializerArguments === "context" ? `initializer(context)` : entry.initializerArguments === "root" ? `initializer(context.root)` : `initializer(context.config)`;
2109
2128
  initializers.push(
2110
- `runPluginClientInitializer(${JSON.stringify(entry.moduleSpecifier)}, () => ${initializer})`
2129
+ `mountConditionalPluginClientInitializer(${JSON.stringify(entry.scope ?? "page")}, ${JSON.stringify(entry.moduleSpecifier)}, root, ${serializedConfig}, ${JSON.stringify(entry.presenceSelector)}, async () => { const { ${entry.exportName ?? "default"}: initializer } = await import(${JSON.stringify(entry.moduleSpecifier)}); return (context) => ${dynamicInitializer}; })`
2111
2130
  );
2112
2131
  }
2113
2132
  return `${imports.join("\n")}
2114
2133
 
2115
- function runPluginClientInitializer(name, initialize) {
2116
- try {
2117
- const result = initialize();
2118
- if (result && typeof result.then === "function") {
2119
- result.catch((error) => reportPluginClientError(name, error));
2120
- }
2121
- } catch (error) {
2122
- reportPluginClientError(name, error);
2123
- }
2134
+ const lifecycle = new ClientLifecycle();
2135
+ let stopSpaNavigation;
2136
+
2137
+ function mountPluginClientInitializer(scope, name, root, config, initialize) {
2138
+ void lifecycle.mount({ scope, name, root, config, initialize });
2139
+ }
2140
+
2141
+ function mountConditionalPluginClientInitializer(scope, name, root, config, presenceSelector, load) {
2142
+ if (!root.querySelector(presenceSelector)) return;
2143
+ void load().then((initialize) => {
2144
+ if ("isConnected" in root && !root.isConnected) return;
2145
+ void lifecycle.mount({ scope, name, root, config, initialize });
2146
+ }, (error) => console.error("[riebeckite] Client initializer module failed: " + name, error));
2147
+ }
2148
+
2149
+ function mountAppClientInitializers(root) {
2150
+ ${appInitializers.map((initializer) => ` ${initializer};`).join("\n")}
2124
2151
  }
2125
2152
 
2126
- function reportPluginClientError(name, error) {
2127
- console.error("[riebeckite] Plugin client initializer failed: " + name, error);
2153
+ function mountPageClientInitializers(root) {
2154
+ ${pageInitializers.map((initializer) => ` ${initializer};`).join("\n")}
2128
2155
  }
2129
2156
 
2130
2157
  export function initRiebeckiteClient() {
2131
- ${initializers.map((initializer) => ` ${initializer};`).join("\n")}
2158
+ mountAppClientInitializers(document);
2159
+ mountPageClientInitializers(document.querySelector("#main-content") ?? document);
2160
+ ${config.spa === void 0 ? "" : ` stopSpaNavigation ??= startSpaNavigation({
2161
+ ...${serializePublicClientConfig(config.spa)},
2162
+ onBeforePageCommit: () => lifecycle.unmount("page"),
2163
+ onPageCommit: async ({ root, signal }) => {
2164
+ if (signal.aborted) return;
2165
+ await createClient();
2166
+ if (signal.aborted || !root.isConnected) return;
2167
+ mountPageClientInitializers(root);
2168
+ },
2169
+ });`}
2170
+ }
2171
+
2172
+ export async function remountRiebeckitePage(root) {
2173
+ await lifecycle.unmount("page");
2174
+ mountPageClientInitializers(root);
2175
+ }
2176
+
2177
+ export async function disposeRiebeckiteClient() {
2178
+ stopSpaNavigation?.();
2179
+ stopSpaNavigation = undefined;
2180
+ await lifecycle.dispose();
2132
2181
  }
2133
2182
  `;
2134
2183
  }
2135
2184
 
2136
2185
  // src/development_inspector.ts
2137
- import { collectDevelopmentDiagnostics } from "@riebeckite/diagnostics";
2186
+ import {
2187
+ buildOutputPlan as buildOutputPlan2,
2188
+ ContentManager
2189
+ } from "@riebeckite/core";
2190
+ import {
2191
+ collectDevelopmentDiagnostics,
2192
+ diagnoseOutputPlan,
2193
+ sortDevelopmentDiagnostics
2194
+ } from "@riebeckite/diagnostics";
2195
+ import { FileSystemContentSource } from "@riebeckite/node";
2138
2196
  var clientModuleId2 = "virtual:riebeckite/development-inspector";
2139
2197
  var resolvedClientModuleId2 = `\0${clientModuleId2}`;
2140
- var endpointPath = "/__riebeckite/inspector/report";
2198
+ var reportPath = "/__riebeckite/inspector/report";
2199
+ var outputPath = "/__riebeckite/inspector/output";
2200
+ var listedOutputLimit = 20;
2141
2201
  function riebeckiteDevelopmentInspector(getApplication) {
2142
2202
  let revision = 0;
2203
+ let inspection = null;
2204
+ const inspectApplication = (application, currentRevision) => {
2205
+ if (inspection?.revision === currentRevision && inspection.application === application) {
2206
+ return inspection;
2207
+ }
2208
+ if (inspection) {
2209
+ const previous = inspection;
2210
+ const disposePrevious = () => {
2211
+ void previous.dispose().catch(() => {
2212
+ });
2213
+ };
2214
+ void Promise.allSettled([previous.report, previous.output]).then(
2215
+ disposePrevious
2216
+ );
2217
+ inspection = null;
2218
+ }
2219
+ const content = createInspectionContent(application);
2220
+ let disposed = false;
2221
+ const dispose = async () => {
2222
+ if (disposed) return;
2223
+ disposed = true;
2224
+ await content.dispose();
2225
+ };
2226
+ const report = collectDevelopmentDiagnostics({
2227
+ config: application.config,
2228
+ content,
2229
+ revision: currentRevision,
2230
+ includeOutput: false
2231
+ });
2232
+ const output = report.then(
2233
+ () => inspectContentOutputs(content),
2234
+ () => inspectContentOutputs(content)
2235
+ );
2236
+ const entry = {
2237
+ application,
2238
+ revision: currentRevision,
2239
+ report,
2240
+ output,
2241
+ dispose
2242
+ };
2243
+ inspection = entry;
2244
+ void report.catch(() => {
2245
+ if (inspection === entry) inspection = null;
2246
+ void output.then(dispose, dispose);
2247
+ });
2248
+ return entry;
2249
+ };
2143
2250
  return {
2144
2251
  name: "riebeckite-development-inspector",
2145
2252
  apply: "serve",
@@ -2151,7 +2258,7 @@ function riebeckiteDevelopmentInspector(getApplication) {
2151
2258
  return id === resolvedClientModuleId2 ? inspectorClientModule() : null;
2152
2259
  },
2153
2260
  configureServer(server) {
2154
- const middleware = async (request, response) => {
2261
+ const reportMiddleware = async (request, response) => {
2155
2262
  if (!isLocalRequest(request.headers.host, server)) {
2156
2263
  response.statusCode = 403;
2157
2264
  response.end();
@@ -2159,25 +2266,16 @@ function riebeckiteDevelopmentInspector(getApplication) {
2159
2266
  }
2160
2267
  try {
2161
2268
  const application = getApplication();
2162
- const report = await collectDevelopmentDiagnostics({
2163
- config: application.config,
2164
- revision
2165
- });
2269
+ const report = await inspectApplication(application, revision).report;
2166
2270
  response.setHeader("content-type", "application/json; charset=utf-8");
2167
2271
  response.setHeader("cache-control", "no-store");
2168
- const currentPage = currentPageFromRequest(request.url);
2169
2272
  response.end(
2170
2273
  JSON.stringify({
2171
2274
  ...report,
2172
2275
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2173
- currentPage,
2276
+ currentPage: currentPageFromRequest(request.url),
2174
2277
  theme: application.config.theme.name,
2175
- plugins: application.config.plugins.map((plugin) => plugin.name),
2176
- capabilities: {
2177
- dependencies: "unavailable",
2178
- output: "unavailable",
2179
- pageMetadata: "unavailable"
2180
- }
2278
+ plugins: application.config.plugins.map((plugin) => plugin.name)
2181
2279
  })
2182
2280
  );
2183
2281
  } catch {
@@ -2186,8 +2284,39 @@ function riebeckiteDevelopmentInspector(getApplication) {
2186
2284
  response.end(JSON.stringify({ code: "INSPECTOR_REPORT_FAILED" }));
2187
2285
  }
2188
2286
  };
2189
- server.middlewares.use(endpointPath, middleware);
2190
- return () => prioritizeMiddleware(server, middleware);
2287
+ const outputMiddleware = async (request, response) => {
2288
+ if (!isLocalRequest(request.headers.host, server)) {
2289
+ response.statusCode = 403;
2290
+ response.end();
2291
+ return;
2292
+ }
2293
+ try {
2294
+ const application = getApplication();
2295
+ const entry = inspectApplication(application, revision);
2296
+ const result = await entry.output;
2297
+ response.setHeader("content-type", "application/json; charset=utf-8");
2298
+ response.setHeader("cache-control", "no-store");
2299
+ response.end(
2300
+ JSON.stringify({
2301
+ version: 1,
2302
+ revision: entry.revision,
2303
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2304
+ ...result,
2305
+ diagnostics: sortDevelopmentDiagnostics(result.diagnostics)
2306
+ })
2307
+ );
2308
+ } catch {
2309
+ response.statusCode = 500;
2310
+ response.setHeader("content-type", "application/json; charset=utf-8");
2311
+ response.end(JSON.stringify({ code: "INSPECTOR_OUTPUT_FAILED" }));
2312
+ }
2313
+ };
2314
+ server.middlewares.use(reportPath, reportMiddleware);
2315
+ server.middlewares.use(outputPath, outputMiddleware);
2316
+ return () => {
2317
+ prioritizeMiddleware(server, outputMiddleware, outputPath);
2318
+ prioritizeMiddleware(server, reportMiddleware, reportPath);
2319
+ };
2191
2320
  },
2192
2321
  handleHotUpdate({ server }) {
2193
2322
  revision += 1;
@@ -2195,16 +2324,142 @@ function riebeckiteDevelopmentInspector(getApplication) {
2195
2324
  }
2196
2325
  };
2197
2326
  }
2327
+ function createInspectionContent(application) {
2328
+ const { config, contentRoot } = application;
2329
+ return new ContentManager(
2330
+ config.content.source ?? new FileSystemContentSource(contentRoot, config.content.exclude),
2331
+ config.content.exclude,
2332
+ { config, commitBuildState: false }
2333
+ );
2334
+ }
2335
+ async function inspectContentOutputs(content) {
2336
+ try {
2337
+ const changes = await content.getOutputChangeSet();
2338
+ const plan = buildOutputPlan2({
2339
+ inventory: [...changes.affected, ...changes.unchanged],
2340
+ removed: changes.removed,
2341
+ affectedPaths: new Set(changes.affected.map((output) => output.path)),
2342
+ cacheAvailable: changes.unchanged.length > 0
2343
+ });
2344
+ return {
2345
+ output: inspectOutput(changes, plan),
2346
+ dependencies: inspectDependencies(changes),
2347
+ diagnostics: diagnoseOutputPlan(plan)
2348
+ };
2349
+ } catch {
2350
+ return {
2351
+ output: {
2352
+ status: "unavailable",
2353
+ reason: "output inspection failed"
2354
+ },
2355
+ dependencies: {
2356
+ status: "unavailable",
2357
+ reason: "dependency inspection failed"
2358
+ },
2359
+ diagnostics: []
2360
+ };
2361
+ }
2362
+ }
2363
+ function inspectOutput(changes, plan) {
2364
+ return {
2365
+ status: "available",
2366
+ candidateOutputCount: changes.candidateOutputCount,
2367
+ affectedOutputCount: changes.affectedOutputCount,
2368
+ unchangedOutputCount: changes.unchangedOutputCount,
2369
+ removedOutputCount: changes.removedOutputCount,
2370
+ fullRegenerationRequired: changes.fullRegenerationRequired,
2371
+ conflicts: plan.conflicts.map((conflict) => ({
2372
+ path: conflict.path,
2373
+ owners: conflict.owners
2374
+ })),
2375
+ affected: changes.affected.slice(0, listedOutputLimit).map(listOutput),
2376
+ hiddenAffectedCount: Math.max(
2377
+ 0,
2378
+ changes.affected.length - listedOutputLimit
2379
+ ),
2380
+ removed: changes.removed.slice(0, listedOutputLimit).map(listOutput),
2381
+ hiddenRemovedCount: Math.max(0, changes.removed.length - listedOutputLimit)
2382
+ };
2383
+ }
2384
+ function inspectDependencies(changes) {
2385
+ const outputs = [...changes.affected, ...changes.unchanged];
2386
+ const kinds = /* @__PURE__ */ new Map();
2387
+ const listed = [];
2388
+ let dependencyCount = 0;
2389
+ let dependentOutputCount = 0;
2390
+ let unknownOutputCount = 0;
2391
+ let hiddenOutputCount = 0;
2392
+ for (const output of outputs) {
2393
+ for (const dependency of output.dependencies) {
2394
+ dependencyCount += 1;
2395
+ kinds.set(dependency.type, (kinds.get(dependency.type) ?? 0) + 1);
2396
+ }
2397
+ if (output.dependencies.some(({ type }) => type === "unknown")) {
2398
+ unknownOutputCount += 1;
2399
+ }
2400
+ if (output.dependencies.length === 0) continue;
2401
+ dependentOutputCount += 1;
2402
+ if (listed.length < listedOutputLimit) {
2403
+ listed.push({
2404
+ path: output.path,
2405
+ dependencies: output.dependencies.map(dependencyLabel)
2406
+ });
2407
+ } else {
2408
+ hiddenOutputCount += 1;
2409
+ }
2410
+ }
2411
+ return {
2412
+ status: "available",
2413
+ outputCount: outputs.length,
2414
+ dependentOutputCount,
2415
+ dependencyCount,
2416
+ unknownOutputCount,
2417
+ kinds: [...kinds.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([kind, count]) => ({ kind, count })),
2418
+ outputs: listed,
2419
+ hiddenOutputCount
2420
+ };
2421
+ }
2422
+ function listOutput(output) {
2423
+ return {
2424
+ path: output.path,
2425
+ kind: output.kind,
2426
+ producer: output.producer
2427
+ };
2428
+ }
2429
+ function dependencyLabel(dependency) {
2430
+ switch (dependency.type) {
2431
+ case "content":
2432
+ return `content:${dependency.slug}`;
2433
+ case "tag":
2434
+ return `tag:${dependency.tag}`;
2435
+ case "folder":
2436
+ return `folder:${dependency.folder}`;
2437
+ case "file":
2438
+ return `file:${dependency.path}`;
2439
+ case "public-location":
2440
+ return `public-location:${dependency.pathname}`;
2441
+ case "projection":
2442
+ return `projection:${dependency.projection}`;
2443
+ case "external":
2444
+ return `external:${dependency.id}`;
2445
+ case "content-query":
2446
+ return `content-query:${dependency.properties.join(",")}${dependency.unknown ? "?" : ""}`;
2447
+ case "global":
2448
+ return "global";
2449
+ case "unknown":
2450
+ return "unknown";
2451
+ }
2452
+ }
2198
2453
  function currentPageFromRequest(url) {
2199
- const value = new URL(url ?? endpointPath, "http://localhost").searchParams.get(
2454
+ const value = new URL(url ?? reportPath, "http://localhost").searchParams.get(
2200
2455
  "path"
2201
2456
  );
2202
- return value && value.startsWith("/") && !value.startsWith("//") ? value : "/";
2457
+ return value?.startsWith("/") && !value.startsWith("//") ? value : "/";
2203
2458
  }
2204
- function prioritizeMiddleware(server, middleware) {
2459
+ function prioritizeMiddleware(server, middleware, path11) {
2205
2460
  const stack = server.middlewares.stack;
2206
2461
  const index = stack?.findIndex(
2207
- (layer) => layer.handle === middleware || layer.route === endpointPath
2462
+ (layer) => layer.handle === middleware || layer.route === path11
2208
2463
  ) ?? -1;
2209
2464
  if (index > 0 && stack) stack.unshift(...stack.splice(index, 1));
2210
2465
  }
@@ -2218,7 +2473,8 @@ function isLocalRequest(host, server) {
2218
2473
  }
2219
2474
  function inspectorClientModule() {
2220
2475
  return `
2221
- const endpoint = ${JSON.stringify(endpointPath)};
2476
+ const reportEndpoint = ${JSON.stringify(reportPath)};
2477
+ const outputEndpoint = ${JSON.stringify(outputPath)};
2222
2478
  const panelId = "riebeckite-development-inspector";
2223
2479
 
2224
2480
  export function initDevelopmentInspector() {
@@ -2228,23 +2484,57 @@ export function initDevelopmentInspector() {
2228
2484
  let generation = 0;
2229
2485
  let report;
2230
2486
  let activeTab = 0;
2487
+ let refreshing = false;
2488
+ let pendingRefresh = false;
2489
+ let loadingTimer;
2231
2490
  const tabs = ["Overview", "Diagnostics", "Dependencies", "Plugins", "Output"];
2232
2491
  const refresh = async () => {
2233
2492
  if (!panel?.isConnected) return;
2493
+ if (refreshing) {
2494
+ pendingRefresh = true;
2495
+ return;
2496
+ }
2497
+ refreshing = true;
2234
2498
  controller?.abort();
2235
2499
  controller = new AbortController();
2236
2500
  const current = ++generation;
2237
2501
  try {
2238
- const response = await fetch(endpoint + "?path=" + encodeURIComponent(location.pathname), { cache: "no-store", signal: controller.signal });
2502
+ const response = await fetch(reportEndpoint + "?path=" + encodeURIComponent(location.pathname), { cache: "no-store", signal: controller.signal });
2239
2503
  if (!response.ok) throw new Error("Inspector report is unavailable.");
2240
2504
  const next = await response.json();
2241
2505
  if (current !== generation || !panel?.isConnected) return;
2242
2506
  report = next;
2243
2507
  render();
2508
+ void loadOutput(current);
2244
2509
  } catch (error) {
2245
2510
  if (error.name === "AbortError" || current !== generation) return;
2246
- renderFailure(panel.shadowRoot, error instanceof Error ? error.message : String(error));
2247
- panel.shadowRoot.querySelector("[data-close]").addEventListener("click", close);
2511
+ clearInterval(loadingTimer);
2512
+ renderFailure(panel.shadowRoot, error instanceof Error ? error.message : String(error));
2513
+ panel.shadowRoot.querySelector("[data-close]").addEventListener("click", close);
2514
+ } finally {
2515
+ refreshing = false;
2516
+ if (pendingRefresh) {
2517
+ pendingRefresh = false;
2518
+ void refresh();
2519
+ }
2520
+ }
2521
+ };
2522
+ const loadOutput = async (current) => {
2523
+ try {
2524
+ const response = await fetch(outputEndpoint, { cache: "no-store", signal: controller.signal });
2525
+ if (!response.ok) throw new Error("Inspector output is unavailable.");
2526
+ const next = await response.json();
2527
+ if (current !== generation || !panel?.isConnected) return;
2528
+ report.output = next.output;
2529
+ report.dependencies = next.dependencies;
2530
+ report.diagnostics = report.diagnostics.concat(next.diagnostics);
2531
+ report.summary = summarize(report.diagnostics);
2532
+ render();
2533
+ } catch (error) {
2534
+ if (error.name === "AbortError" || current !== generation) return;
2535
+ report.output = { status: "unavailable", reason: "output inspection failed" };
2536
+ report.dependencies = { status: "unavailable", reason: "dependency inspection failed" };
2537
+ render();
2248
2538
  }
2249
2539
  };
2250
2540
  const open = () => {
@@ -2252,16 +2542,30 @@ export function initDevelopmentInspector() {
2252
2542
  panel = document.createElement("div");
2253
2543
  panel.id = panelId;
2254
2544
  panel.attachShadow({ mode: "open" });
2255
- document.body.append(panel);
2256
2545
  }
2257
- refresh();
2546
+ if (!panel.isConnected) document.body.append(panel);
2547
+ showLoading();
2548
+ void refresh();
2258
2549
  };
2259
2550
  const close = () => {
2260
2551
  generation += 1;
2552
+ pendingRefresh = false;
2261
2553
  controller?.abort();
2554
+ clearInterval(loadingTimer);
2262
2555
  panel?.remove();
2263
2556
  launcher?.focus();
2264
2557
  };
2558
+ const showLoading = () => {
2559
+ if (!panel) return;
2560
+ clearInterval(loadingTimer);
2561
+ panel.shadowRoot.innerHTML = '<style>' + styles + '</style><section role="dialog" aria-modal="false" aria-labelledby="rb-loading-title"><header><strong id="rb-loading-title">Riebeckite Inspector</strong><button type="button" data-close aria-label="Close inspector">Close</button></header><p class="status" aria-live="polite">Loading inspector report…</p></section>';
2562
+ const status = panel.shadowRoot.querySelector(".status");
2563
+ const started = Date.now();
2564
+ loadingTimer = setInterval(() => {
2565
+ if (status?.isConnected) status.textContent = 'Loading inspector report… ' + Math.round((Date.now() - started) / 1000) + 's';
2566
+ }, 1000);
2567
+ panel.shadowRoot.querySelector("[data-close]").addEventListener("click", close);
2568
+ };
2265
2569
  launcher = document.createElement("button");
2266
2570
  launcher.type = "button";
2267
2571
  launcher.textContent = "Inspector";
@@ -2282,8 +2586,9 @@ export function initDevelopmentInspector() {
2282
2586
  import.meta.hot?.on("riebeckite:inspector", refresh);
2283
2587
 
2284
2588
  function render() {
2589
+ clearInterval(loadingTimer);
2285
2590
  const root = panel.shadowRoot;
2286
- root.innerHTML = '<style>' + styles + '</style><section role="dialog" aria-modal="false" aria-labelledby="rb-title" tabindex="-1"><header><div><strong id="rb-title">Riebeckite Inspector</strong><p>Ctrl+Alt+R</p></div><button type="button" data-close aria-label="Close inspector">Close</button></header><div class="summary"><b>' + report.summary.errors + '</b> errors <b>' + report.summary.warnings + '</b> warnings <b>' + report.summary.info + '</b> info</div><div role="tablist" aria-label="Inspector sections">' + tabs.map((tab, index) => '<button id="rb-tab-' + index + '" role="tab" aria-controls="rb-panel-' + index + '" aria-selected="' + (index === activeTab) + '" tabindex="' + (index === activeTab ? 0 : -1) + '" data-tab="' + index + '">' + tab + '</button>').join("") + '</div><div id="rb-panel-' + activeTab + '" role="tabpanel" aria-labelledby="rb-tab-' + activeTab + '" tabindex="0">' + panelContent() + '</div><p class="status" aria-live="polite">Inspector report updated</p></section>';
2591
+ root.innerHTML = '<style>' + styles + '</style><section role="dialog" aria-modal="false" aria-labelledby="rb-title" tabindex="-1"><header><div><strong id="rb-title">Riebeckite Inspector</strong><p>Ctrl+Alt+R</p></div><button type="button" data-close aria-label="Close inspector">Close</button></header><div class="summary"><b>' + report.summary.errors + '</b> errors <b>' + report.summary.warnings + '</b> warnings <b>' + report.summary.info + '</b> info</div><div role="tablist" aria-label="Inspector sections">' + tabs.map((tab, index) => '<button id="rb-tab-' + index + '" role="tab" aria-controls="rb-panel-' + index + '" aria-selected="' + (index === activeTab) + '" tabindex="' + (index === activeTab ? 0 : -1) + '" data-tab="' + index + '">' + tab + '</button>').join("") + '</div><div id="rb-panel-' + activeTab + '" role="tabpanel" aria-labelledby="rb-tab-' + activeTab + '" tabindex="0">' + panelContent() + '</div><p class="status" aria-live="polite">' + (report.output ? "Inspector report updated" : "Inspector report updated · loading output plan…") + '</p></section>';
2287
2592
  root.querySelector("[data-close]").addEventListener("click", close);
2288
2593
  root.querySelectorAll("[data-tab]").forEach((button) => button.addEventListener("click", () => { activeTab = Number(button.dataset.tab); render(); }));
2289
2594
  root.querySelector("[role=tablist]").addEventListener("keydown", (event) => {
@@ -2298,20 +2603,78 @@ export function initDevelopmentInspector() {
2298
2603
  function panelContent() {
2299
2604
  if (activeTab === 0) return '<dl><dt>Page</dt><dd>' + escapeHtml(report.currentPage) + '</dd><dt>Theme</dt><dd>' + escapeHtml(report.theme) + '</dd><dt>Revision</dt><dd>' + escapeHtml(report.revision) + '</dd></dl>';
2300
2605
  if (activeTab === 1) return diagnosticList(report.diagnostics);
2301
- if (activeTab === 2) return unavailable("Dependencies", report.capabilities.dependencies);
2606
+ if (activeTab === 2) return dependencyReport(report.dependencies);
2302
2607
  if (activeTab === 3) return '<ul>' + (report.plugins.map((plugin) => '<li>' + escapeHtml(plugin) + '</li>').join("") || "<li>No plugins configured.</li>") + '</ul>';
2303
- return unavailable("Output", report.capabilities.output);
2608
+ return outputReport(report.output);
2304
2609
  }
2305
2610
  }
2306
2611
 
2612
+ function summarize(diagnostics) {
2613
+ return {
2614
+ errors: diagnostics.filter((diagnostic) => diagnostic.severity === "error").length,
2615
+ warnings: diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length,
2616
+ info: diagnostics.filter((diagnostic) => diagnostic.severity === "info").length,
2617
+ };
2618
+ }
2619
+
2307
2620
  function diagnosticList(diagnostics) {
2308
2621
  return '<ul>' + (diagnostics.map((diagnostic) =>
2309
2622
  '<li data-severity="' + escapeHtml(diagnostic.severity) + '"><b>' + escapeHtml(diagnostic.code) + '</b> ' + escapeHtml(diagnostic.message) + location(diagnostic) + '</li>'
2310
2623
  ).join("") || "<li>No diagnostic findings.</li>") + '</ul>';
2311
2624
  }
2312
2625
 
2626
+ function outputReport(output) {
2627
+ if (!output) return '<p class="status" aria-live="polite">Loading output plan…</p>';
2628
+ if (output.status !== "available") return unavailable("Output", output.reason);
2629
+ return '<dl>' +
2630
+ '<dt>Candidate</dt><dd>' + output.candidateOutputCount + '</dd>' +
2631
+ '<dt>Affected</dt><dd>' + output.affectedOutputCount + '</dd>' +
2632
+ '<dt>Unchanged</dt><dd>' + output.unchangedOutputCount + '</dd>' +
2633
+ '<dt>Removed</dt><dd>' + output.removedOutputCount + '</dd>' +
2634
+ '<dt>Full regeneration</dt><dd>' + (output.fullRegenerationRequired ? "required" : "not required") + '</dd>' +
2635
+ '<dt>Owner conflicts</dt><dd>' + output.conflicts.length + '</dd>' +
2636
+ '</dl>' +
2637
+ listedOutputs("Affected outputs", output.affected, output.hiddenAffectedCount) +
2638
+ listedOutputs("Removed outputs", output.removed, output.hiddenRemovedCount) +
2639
+ conflictList(output.conflicts);
2640
+ }
2641
+
2642
+ function listedOutputs(title, outputs, hiddenCount) {
2643
+ if (outputs.length === 0) return "";
2644
+ return '<h3>' + escapeHtml(title) + '</h3><ul>' + outputs.map((output) =>
2645
+ '<li><code>' + escapeHtml(output.path) + '</code> <small>' + escapeHtml(output.kind) + ' · ' + escapeHtml(output.producer) + '</small></li>'
2646
+ ).join("") + '</ul>' + (hiddenCount > 0 ? '<p class="more">' + hiddenCount + ' more not shown.</p>' : "");
2647
+ }
2648
+
2649
+ function conflictList(conflicts) {
2650
+ if (conflicts.length === 0) return "";
2651
+ return '<h3>Owner conflicts</h3><ul>' + conflicts.map((conflict) =>
2652
+ '<li><code>' + escapeHtml(conflict.path) + '</code> <small>' + escapeHtml(conflict.owners.join(", ")) + '</small></li>'
2653
+ ).join("") + '</ul>';
2654
+ }
2655
+
2656
+ function dependencyReport(dependencies) {
2657
+ if (!dependencies) return '<p class="status" aria-live="polite">Loading dependencies…</p>';
2658
+ if (dependencies.status !== "available") return unavailable("Dependencies", dependencies.reason);
2659
+ return '<dl>' +
2660
+ '<dt>Outputs</dt><dd>' + dependencies.outputCount + '</dd>' +
2661
+ '<dt>With dependencies</dt><dd>' + dependencies.dependentOutputCount + '</dd>' +
2662
+ '<dt>Dependencies</dt><dd>' + dependencies.dependencyCount + '</dd>' +
2663
+ '<dt>Unknown outputs</dt><dd>' + dependencies.unknownOutputCount + '</dd>' +
2664
+ '</dl>' +
2665
+ (dependencies.kinds.length === 0 ? "<p>No output dependencies.</p>" :
2666
+ '<h3>Kinds</h3><ul>' + dependencies.kinds.map((kind) =>
2667
+ '<li>' + escapeHtml(kind.kind) + ' <small>' + kind.count + '</small></li>'
2668
+ ).join("") + '</ul>') +
2669
+ (dependencies.outputs.length === 0 ? "" :
2670
+ '<h3>Outputs</h3><ul>' + dependencies.outputs.map((output) =>
2671
+ '<li><code>' + escapeHtml(output.path) + '</code><br><small>' + escapeHtml(output.dependencies.join(", ")) + '</small></li>'
2672
+ ).join("") + '</ul>') +
2673
+ (dependencies.hiddenOutputCount > 0 ? '<p class="more">' + dependencies.hiddenOutputCount + ' more outputs not shown.</p>' : "");
2674
+ }
2675
+
2313
2676
  function unavailable(subject, capability) {
2314
- return '<p class="partial">' + escapeHtml(subject) + ': ' + escapeHtml(capability) + ' in the development inspector.</p>';
2677
+ return '<p class="partial">' + escapeHtml(subject) + ' unavailable: ' + escapeHtml(capability) + '.</p>';
2315
2678
  }
2316
2679
 
2317
2680
  function renderFailure(root, message) {
@@ -2327,7 +2690,7 @@ function escapeHtml(value) {
2327
2690
  return String(value).replace(/[&<>"]/g, (character) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[character]);
2328
2691
  }
2329
2692
 
2330
- const styles = ':host{all:initial}section{position:fixed;right:16px;bottom:56px;z-index:2147483647;width:min(540px,calc(100vw - 32px));max-height:70vh;overflow:auto;background:#171717;color:#f4f4f4;border:1px solid #555;padding:16px;font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;box-shadow:0 8px 24px #0006}header{display:flex;justify-content:space-between;gap:16px;border-bottom:1px solid #555;padding-bottom:8px}header p,.status,small{color:#b8b8b8;margin:2px 0}.summary{margin:12px 0}button{border:1px solid #666;background:#242424;color:#fff;padding:6px 8px;cursor:pointer}button[aria-selected="true"]{border-color:#e2c57b;color:#ffe08a}.partial{color:#e2c57b}ul{margin:12px 0;padding-left:20px}li{margin:8px 0}li[data-severity="error"]{color:#ff9797}li[data-severity="warning"]{color:#ffe08a}dl{display:grid;grid-template-columns:max-content 1fr;gap:8px 16px}dt{color:#b8b8b8}.failure{color:#ff9797}@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}@media(max-width:560px){section{right:8px;bottom:52px;width:calc(100vw - 32px);max-height:75vh}}';
2693
+ const styles = ':host{all:initial}section{position:fixed;right:16px;bottom:56px;z-index:2147483647;width:min(540px,calc(100vw - 32px));max-height:70vh;overflow:auto;background:#171717;color:#f4f4f4;border:1px solid #555;padding:16px;font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;box-shadow:0 8px 24px #0006}header{display:flex;justify-content:space-between;gap:16px;border-bottom:1px solid #555;padding-bottom:8px}header p,.status,small{color:#b8b8b8;margin:2px 0}.summary{margin:12px 0}button{border:1px solid #666;background:#242424;color:#fff;padding:6px 8px;cursor:pointer}button[aria-selected="true"]{border-color:#e2c57b;color:#ffe08a}.partial{color:#e2c57b}.more{color:#b8b8b8;margin:4px 0}h3{color:#b8b8b8;font-size:12px;margin:12px 0 4px;text-transform:uppercase}ul{margin:12px 0;padding-left:20px}li{margin:8px 0}li[data-severity="error"]{color:#ff9797}li[data-severity="warning"]{color:#ffe08a}dl{display:grid;grid-template-columns:max-content 1fr;gap:8px 16px}dt{color:#b8b8b8}.failure{color:#ff9797}@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}@media(max-width:560px){section{right:8px;bottom:52px;width:calc(100vw - 32px);max-height:75vh}}';
2331
2694
  `;
2332
2695
  }
2333
2696
 
@@ -2340,6 +2703,14 @@ var viteConfigFileNames = [
2340
2703
  "vite.config.js",
2341
2704
  "vite.config.mjs"
2342
2705
  ];
2706
+ var ignoredApplicationRootDirectoryNames = /* @__PURE__ */ new Set([
2707
+ ".git",
2708
+ ".riebeckite",
2709
+ "node_modules",
2710
+ "test",
2711
+ "tests",
2712
+ "templates"
2713
+ ]);
2343
2714
  async function resolveHonoxApplicationRoot(configRoot, startDirectory) {
2344
2715
  const roots = await findViteApplicationRoots(configRoot, startDirectory);
2345
2716
  if (roots.length === 1) return roots[0];
@@ -2402,12 +2773,24 @@ async function startHonoxDevServer(options) {
2402
2773
  });
2403
2774
  }
2404
2775
  await server.listen(options.server?.port);
2405
- options.onServerReady?.([
2776
+ const urls = [
2406
2777
  ...server.resolvedUrls?.local ?? [],
2407
2778
  ...server.resolvedUrls?.network ?? []
2408
- ]);
2409
- server.printUrls();
2410
- await waitForShutdown(server);
2779
+ ];
2780
+ const shutdown = waitForShutdown(server);
2781
+ try {
2782
+ server.printUrls();
2783
+ await options.onServerReady?.(urls);
2784
+ } catch (error) {
2785
+ shutdown.dispose();
2786
+ if (shutdown.isClosing()) {
2787
+ await shutdown.promise;
2788
+ return;
2789
+ }
2790
+ await server.close();
2791
+ throw error;
2792
+ }
2793
+ await shutdown.promise;
2411
2794
  });
2412
2795
  }
2413
2796
  async function buildHonoxApplication(options) {
@@ -2438,14 +2821,28 @@ async function withWorkingDirectory(root, task) {
2438
2821
  async function runTraced(tracer, name, buildTask) {
2439
2822
  return tracer ? await tracer.span(name, {}, buildTask) : await buildTask();
2440
2823
  }
2441
- async function waitForShutdown(server) {
2442
- await new Promise((resolve3) => {
2443
- const close = () => {
2444
- void server.close().finally(resolve3);
2445
- };
2446
- process.once("SIGINT", close);
2447
- process.once("SIGTERM", close);
2824
+ function waitForShutdown(server) {
2825
+ let resolve3 = () => {
2826
+ };
2827
+ let closing = false;
2828
+ const close = () => {
2829
+ if (closing) return;
2830
+ closing = true;
2831
+ void server.close().finally(resolve3);
2832
+ };
2833
+ const promise = new Promise((resolvePromise) => {
2834
+ resolve3 = resolvePromise;
2448
2835
  });
2836
+ process.once("SIGINT", close);
2837
+ process.once("SIGTERM", close);
2838
+ return {
2839
+ promise,
2840
+ isClosing: () => closing,
2841
+ dispose: () => {
2842
+ process.removeListener("SIGINT", close);
2843
+ process.removeListener("SIGTERM", close);
2844
+ }
2845
+ };
2449
2846
  }
2450
2847
  async function findViteApplicationRoots(root, startDirectory) {
2451
2848
  const applicationRoot = startDirectory ? await findViteApplicationRootFrom(startDirectory, root) : void 0;
@@ -2458,7 +2855,7 @@ async function findViteApplicationRoots(root, startDirectory) {
2458
2855
  if (await hasViteConfig(directory)) applicationRoots.push(directory);
2459
2856
  const entries = await fs7.readdir(directory, { withFileTypes: true });
2460
2857
  for (const entry of entries) {
2461
- if (!entry.isDirectory() || entry.name === "node_modules" || entry.name === ".git" || entry.name === "tests") {
2858
+ if (!entry.isDirectory() || ignoredApplicationRootDirectoryNames.has(entry.name)) {
2462
2859
  continue;
2463
2860
  }
2464
2861
  directories.push(path9.join(directory, entry.name));
@@ -2541,6 +2938,9 @@ function riebeckite(options = {}, onApplicationResolved) {
2541
2938
  define: {
2542
2939
  "process.env.RIEBECKITE_APP_ROOT": JSON.stringify(appRoot)
2543
2940
  },
2941
+ oxc: {
2942
+ jsx: { runtime: "automatic", importSource: "hono/jsx" }
2943
+ },
2544
2944
  server: {
2545
2945
  watch: {
2546
2946
  ignored: ["**/.riebeckite/**"]