@ng-doc/builder 22.0.0 → 22.1.0

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.
Files changed (85) hide show
  1. package/engine/builders/helpers/get-playground-metadata.js +7 -1
  2. package/engine/builders/helpers/get-playground-metadata.js.map +1 -1
  3. package/engine/builders/page/demo-assets.builder.js +2 -0
  4. package/engine/builders/page/demo-assets.builder.js.map +1 -1
  5. package/engine/builders/shared/content.builder.js +2 -0
  6. package/engine/builders/shared/content.builder.js.map +1 -1
  7. package/engine/builders/shared/page-wrapper.builder.js +2 -0
  8. package/engine/builders/shared/page-wrapper.builder.js.map +1 -1
  9. package/generator/bootstrap/bin.js +27 -3
  10. package/generator/bootstrap/cli.js +27 -3
  11. package/generator/bootstrap/index.js +17 -3
  12. package/generator/build-metafile.json +529 -221
  13. package/generator/build-provenance.json +6 -4
  14. package/generator/compiler/index.d.ts +20 -0
  15. package/generator/compiler/index.js +1875 -625
  16. package/generator/compiler/plan.d.ts +24 -0
  17. package/generator/content/code-snippet.d.ts +20 -0
  18. package/generator/content/content-compiler.d.ts +5 -1
  19. package/generator/content/format-cache.d.ts +108 -0
  20. package/generator/content/highlight-cache.d.ts +12 -36
  21. package/generator/content/html-pipeline.d.ts +10 -1
  22. package/generator/content/html-pool.d.ts +8 -2
  23. package/generator/content/html-worker.js +622 -39
  24. package/generator/contracts.d.ts +39 -0
  25. package/generator/discovery/index.d.ts +2 -0
  26. package/generator/kernel/flags.d.ts +2 -0
  27. package/generator/outputs/index.d.ts +13 -0
  28. package/generator/semantic/angular.d.ts +21 -1
  29. package/generator/semantic/formatting.d.ts +27 -0
  30. package/generator/semantic/fragments.d.ts +11 -2
  31. package/generator/templates/api/class-declaration.html.nunj +5 -3
  32. package/generator/templates/demo-app.ts.nunj +37 -0
  33. package/generator/templates/demo-routes.ts.nunj +23 -0
  34. package/generator/templates/index.ts.nunj +5 -0
  35. package/generator/templates/page-wrapper.ts.nunj +6 -1
  36. package/generator/templates/page.ts.nunj +9 -2
  37. package/generator/templates/routes.ts.nunj +7 -3
  38. package/generator/templates/symbol/members.html.nunj +6 -4
  39. package/generator/templates/symbol/page.html.nunj +1 -1
  40. package/generator/vite/application-builder.js +6 -1
  41. package/generator/vite/application.d.ts +14 -0
  42. package/generator/vite/demo-application.d.ts +34 -0
  43. package/generator/vite/dev-server-builder.js +6 -1
  44. package/generator/vite/host-updates.d.ts +69 -2
  45. package/generator/vite/index.js +490 -130
  46. package/generator/vite/lifecycle.d.ts +1 -0
  47. package/generator/vite/prerender-entry.js +72 -2
  48. package/generator/vite/prerender-runtime.d.ts +13 -0
  49. package/generator/vite/prerender.d.ts +2 -0
  50. package/helpers/format-cache.d.ts +34 -0
  51. package/helpers/format-cache.js +26 -0
  52. package/helpers/format-cache.js.map +1 -0
  53. package/helpers/format-code.js +9 -2
  54. package/helpers/format-code.js.map +1 -1
  55. package/helpers/to-template-string.d.ts +5 -1
  56. package/helpers/to-template-string.js +7 -2
  57. package/helpers/to-template-string.js.map +1 -1
  58. package/interfaces/code-block-options.d.ts +5 -0
  59. package/interfaces/configuration.d.ts +84 -1
  60. package/package.json +5 -4
  61. package/parsers/parse-code-block-params.d.ts +10 -1
  62. package/parsers/parse-code-block-params.js +29 -3
  63. package/parsers/parse-code-block-params.js.map +1 -1
  64. package/schematics/collection.json +1 -1
  65. package/schematics/migrate-to-vite/analyze.js +46 -3
  66. package/schematics/migrate-to-vite/analyze.js.map +1 -1
  67. package/schematics/migrate-to-vite/setup/workspace.d.ts +5 -0
  68. package/schematics/migrate-to-vite/setup/workspace.js +20 -0
  69. package/schematics/migrate-to-vite/setup/workspace.js.map +1 -1
  70. package/schematics/migrate-to-vite/testing/fixtures.d.ts +12 -0
  71. package/schematics/migrate-to-vite/testing/fixtures.js +27 -0
  72. package/schematics/migrate-to-vite/testing/fixtures.js.map +1 -1
  73. package/schematics/vite-setup/index.d.ts +9 -5
  74. package/schematics/vite-setup/index.js +11 -5
  75. package/schematics/vite-setup/index.js.map +1 -1
  76. package/schematics/vite-setup/schema.json +1 -1
  77. package/templates/api/class-declaration.html.nunj +5 -3
  78. package/templates/demo-app.ts.nunj +37 -0
  79. package/templates/demo-routes.ts.nunj +23 -0
  80. package/templates/index.ts.nunj +5 -0
  81. package/templates/page-wrapper.ts.nunj +6 -1
  82. package/templates/page.ts.nunj +9 -2
  83. package/templates/routes.ts.nunj +7 -3
  84. package/templates/symbol/members.html.nunj +6 -4
  85. package/templates/symbol/page.html.nunj +1 -1
@@ -1,5 +1,5 @@
1
1
  // libs/builder/generator/vite/plugin.ts
2
- import path19 from "node:path";
2
+ import path20 from "node:path";
3
3
 
4
4
  // libs/builder/generator/artifacts/index.ts
5
5
  import { randomUUID } from "node:crypto";
@@ -49,24 +49,24 @@ function sha256Hex(value) {
49
49
  function canonicalJson(value) {
50
50
  return canonical(value, /* @__PURE__ */ new Set()) ?? "null";
51
51
  }
52
- function canonical(value, path24) {
52
+ function canonical(value, path25) {
53
53
  if (value !== null && typeof value === "object" && typeof value.toJSON === "function")
54
54
  value = value.toJSON();
55
55
  if (value === null || typeof value !== "object") return JSON.stringify(value);
56
- if (path24.has(value)) throw new TypeError("Converting circular structure to canonical JSON");
57
- path24.add(value);
56
+ if (path25.has(value)) throw new TypeError("Converting circular structure to canonical JSON");
57
+ path25.add(value);
58
58
  let text5;
59
59
  if (Array.isArray(value)) {
60
- text5 = `[${Array.from(value, (item) => canonical(item, path24) ?? "null").join(",")}]`;
60
+ text5 = `[${Array.from(value, (item) => canonical(item, path25) ?? "null").join(",")}]`;
61
61
  } else {
62
62
  const fields = [];
63
63
  for (const key of Object.keys(value).sort(compareCodeUnits)) {
64
- const item = canonical(value[key], path24);
64
+ const item = canonical(value[key], path25);
65
65
  if (item !== void 0) fields.push(`${JSON.stringify(key)}:${item}`);
66
66
  }
67
67
  text5 = `{${fields.join(",")}}`;
68
68
  }
69
- path24.delete(value);
69
+ path25.delete(value);
70
70
  return text5;
71
71
  }
72
72
  function digestOf(value) {
@@ -1927,9 +1927,11 @@ function isRoute(value) {
1927
1927
  "metadata",
1928
1928
  "category",
1929
1929
  "modulePath",
1930
- "apiListSegment"
1930
+ "apiListSegment",
1931
+ "demoModulePath",
1932
+ "demoNames"
1931
1933
  ]
1932
- ) && [value.id, value.path, value.title].every(string) && (value.parentId === void 0 || string(value.parentId)) && (value.order === void 0 || finite(value.order)) && (value.hidden === void 0 || typeof value.hidden === "boolean") && (value.icon === void 0 || string(value.icon)) && (value.modulePath === void 0 || string(value.modulePath)) && (value.apiListSegment === void 0 || string(value.apiListSegment)) && (value.metadata === void 0 || isRecord(value.metadata) && exact(value.metadata, ["description", "tags"]) && string(value.metadata.description) && isRecord(value.metadata.tags) && Object.values(value.metadata.tags).every(stringArray)) && (value.category === void 0 || isRecord(value.category) && exact(value.category, ["runtimeImport"], ["expandable", "expanded"]) && isRecord(value.category.runtimeImport) && exact(value.category.runtimeImport, ["source", "exportName"]) && string(value.category.runtimeImport.source) && string(value.category.runtimeImport.exportName) && (value.category.expandable === void 0 || typeof value.category.expandable === "boolean") && (value.category.expanded === void 0 || typeof value.category.expanded === "boolean"));
1934
+ ) && [value.id, value.path, value.title].every(string) && (value.parentId === void 0 || string(value.parentId)) && (value.order === void 0 || finite(value.order)) && (value.hidden === void 0 || typeof value.hidden === "boolean") && (value.icon === void 0 || string(value.icon)) && (value.modulePath === void 0 || string(value.modulePath)) && (value.apiListSegment === void 0 || string(value.apiListSegment)) && (value.demoModulePath === void 0 || string(value.demoModulePath)) && (value.demoNames === void 0 || stringArray(value.demoNames)) && (value.metadata === void 0 || isRecord(value.metadata) && exact(value.metadata, ["description", "tags"]) && string(value.metadata.description) && isRecord(value.metadata.tags) && Object.values(value.metadata.tags).every(stringArray)) && (value.category === void 0 || isRecord(value.category) && exact(value.category, ["runtimeImport"], ["expandable", "expanded"]) && isRecord(value.category.runtimeImport) && exact(value.category.runtimeImport, ["source", "exportName"]) && string(value.category.runtimeImport.source) && string(value.category.runtimeImport.exportName) && (value.category.expandable === void 0 || typeof value.category.expandable === "boolean") && (value.category.expanded === void 0 || typeof value.category.expanded === "boolean"));
1933
1935
  }
1934
1936
  function isApiList(value) {
1935
1937
  return isRecord(value) && exact(
@@ -2174,7 +2176,9 @@ var TRACKED_PROGRAM_REUSE_FLAG = "NGDOC_TRACKED_PROGRAM_REUSE";
2174
2176
  var FAST_START_FLAG = "NGDOC_FAST_START";
2175
2177
  var PARALLEL_WRITES_FLAG = "NGDOC_PARALLEL_WRITES";
2176
2178
  var HIGHLIGHT_CACHE_FLAG = "NGDOC_HIGHLIGHT_CACHE";
2179
+ var FORMAT_CACHE_FLAG = "NGDOC_FORMAT_CACHE";
2177
2180
  var PARALLEL_RENDER_FLAG = "NGDOC_PARALLEL_RENDER";
2181
+ var USED_GRAMMARS_FLAG = "NGDOC_USED_GRAMMARS";
2178
2182
  var FLAGS = Object.freeze(
2179
2183
  [
2180
2184
  {
@@ -2252,6 +2256,16 @@ var FLAGS = Object.freeze(
2252
2256
  description: "Code blocks are highlighted with one Shiki highlighter per runtime and cached by their text, language, meta, themes and Shiki version; development keeps the cache beside the artifact cache.",
2253
2257
  verify: true
2254
2258
  },
2259
+ {
2260
+ name: FORMAT_CACHE_FLAG,
2261
+ description: "The Prettier configuration is resolved once per generation, and formatted signatures and snippets are cached by their code, parser, configuration and Prettier version; development keeps the cache beside the artifact cache.",
2262
+ verify: true
2263
+ },
2264
+ {
2265
+ name: USED_GRAMMARS_FLAG,
2266
+ description: "Code blocks are highlighted by a highlighter that loads only the Shiki grammars their languages reach, instead of every bundled grammar; the HTML is the same.",
2267
+ verify: false
2268
+ },
2255
2269
  {
2256
2270
  name: PARALLEL_RENDER_FLAG,
2257
2271
  description: "A large generation runs the HTML processing of its pages (highlighting, anchors, keyword links, search records) on up to four worker threads, while the main thread prepares the next pages in order.",
@@ -2333,11 +2347,11 @@ async function changedInputs(inputs, ownedRoots) {
2333
2347
  const refresher = createDependencyRefresher();
2334
2348
  const changes = /* @__PURE__ */ new Map();
2335
2349
  const add = (kind, file) => {
2336
- const path24 = normalize(file);
2337
- if (owned(path24, roots)) return;
2338
- const existing = changes.get(path24);
2350
+ const path25 = normalize(file);
2351
+ if (owned(path25, roots)) return;
2352
+ const existing = changes.get(path25);
2339
2353
  if (!existing || CHANGE_RANK[kind] > CHANGE_RANK[existing.kind])
2340
- changes.set(path24, { kind, path: path24 });
2354
+ changes.set(path25, { kind, path: path25 });
2341
2355
  };
2342
2356
  for (let offset = 0; offset < inputs.length; offset += CONCURRENCY) {
2343
2357
  await Promise.all(
@@ -3270,7 +3284,7 @@ var GeneratorBuildSession = class {
3270
3284
  const pending = state.pending;
3271
3285
  if (queued) {
3272
3286
  const changes = new Map(queued.changes.map((change) => [change.path, change]));
3273
- pending.changes.forEach((change, path24) => changes.set(path24, change));
3287
+ pending.changes.forEach((change, path25) => changes.set(path25, change));
3274
3288
  queued.changes = [...changes.values()];
3275
3289
  queued.origin = queued.changes.length ? "filesystem" : "reconcile";
3276
3290
  queued.waiters.push(...pending.waiters);
@@ -4911,7 +4925,7 @@ function createCandidateOutputCommitter(options = {}, host = {}) {
4911
4925
 
4912
4926
  // libs/builder/generator/bootstrap/constants.ts
4913
4927
  var GENERATOR_COMPILER_VERSION = "0.0.1";
4914
- var GENERATOR_TOOLCHAIN_DIGEST = "a837a4b43736f6b95b5565f442aa62d2f2175fb549d7a44138d2955d465c9ac3";
4928
+ var GENERATOR_TOOLCHAIN_DIGEST = "d3b66d41f47865fb003a881897f703f6b49568eefa8f301d410768b97f5647e2";
4915
4929
 
4916
4930
  // libs/builder/generator/bootstrap/index.ts
4917
4931
  var PERSISTENT_WORKER_ENV = PERSISTENT_WORKER_FLAG;
@@ -5062,6 +5076,9 @@ var COMPILER_TYPESCRIPT = /\.[cm]?ts(?![a-z])/;
5062
5076
  var JAVASCRIPT_MODULE = /\.[cm]?jsx?$/;
5063
5077
  var COMPILER_RESOURCE = /\.(?:html?|css|less|sass|scss)$/;
5064
5078
  var HOST_COMPLETION_TIMEOUT_MS = 3e4;
5079
+ var HOST_BURST_EVENTS = 1e3;
5080
+ var HOST_BURST_WINDOW_MS = 1e3;
5081
+ var HOST_BURST_QUIET_MS = 2e3;
5065
5082
  function deferred2(resolved = false) {
5066
5083
  let settled = false;
5067
5084
  let settle = () => {
@@ -5130,9 +5147,10 @@ function observePhysicalVersion(file) {
5130
5147
  }
5131
5148
  }
5132
5149
  var HostUpdateCoordinator = class {
5133
- constructor(notify, fail) {
5150
+ constructor(notify, fail, recovery) {
5134
5151
  this.notify = notify;
5135
5152
  this.fail = fail;
5153
+ this.recovery = recovery;
5136
5154
  this.attempts = /* @__PURE__ */ new Map();
5137
5155
  this.pendingSources = /* @__PURE__ */ new Map();
5138
5156
  this.claimedSources = /* @__PURE__ */ new Map();
@@ -5147,6 +5165,8 @@ var HostUpdateCoordinator = class {
5147
5165
  this.hostDrain = deferred2(true);
5148
5166
  this.sequence = 0;
5149
5167
  this.disposed = false;
5168
+ /** The start and the hot updates of the current burst-detection window. */
5169
+ this.window = { start: 0, events: 0 };
5150
5170
  }
5151
5171
  seed(outputRoot, manifest) {
5152
5172
  this.outputRoot = normalize3(outputRoot);
@@ -5239,6 +5259,7 @@ var HostUpdateCoordinator = class {
5239
5259
  }
5240
5260
  const filePath = normalize3(file);
5241
5261
  const generated = !!this.outputRoot && relativeWithin(this.outputRoot, filePath) !== void 0;
5262
+ if (this.outputRoot && !generated) this.hotUpdate();
5242
5263
  const token = {
5243
5264
  id: ++this.sequence,
5244
5265
  path: filePath,
@@ -5427,6 +5448,8 @@ var HostUpdateCoordinator = class {
5427
5448
  }
5428
5449
  dispose() {
5429
5450
  this.disposed = true;
5451
+ if (this.burst?.timer) clearTimeout(this.burst.timer);
5452
+ this.burst = void 0;
5430
5453
  this.pendingGeneration = void 0;
5431
5454
  this.pendingManifest = void 0;
5432
5455
  for (const attempt of this.attempts.values()) {
@@ -5713,13 +5736,109 @@ var HostUpdateCoordinator = class {
5713
5736
  })
5714
5737
  ]).catch((error2) => {
5715
5738
  const failure3 = error2 instanceof Error ? error2 : new Error(String(error2));
5716
- this.fail(failure3);
5739
+ this.timedOut(failure3);
5717
5740
  throw failure3;
5718
5741
  });
5719
5742
  } finally {
5720
5743
  if (timeout) clearTimeout(timeout);
5721
5744
  }
5722
5745
  }
5746
+ /** Counts a hot update towards a burst of file events (see the class description). */
5747
+ hotUpdate() {
5748
+ const now = Date.now();
5749
+ if (this.burst) {
5750
+ this.burst.last = now;
5751
+ this.burst.events = true;
5752
+ return;
5753
+ }
5754
+ if (now - this.window.start >= HOST_BURST_WINDOW_MS) this.window = { start: now, events: 0 };
5755
+ if (++this.window.events >= HOST_BURST_EVENTS) this.openBurst(now).events = true;
5756
+ }
5757
+ /**
5758
+ * A host update did not settle in time. The first of a burst is reported as it happens (as
5759
+ * every timeout was before); the others only count towards the burst's summary, so a flood of
5760
+ * file events logs two lines instead of one per event.
5761
+ * @param failure The timeout, thrown to its waiter as well.
5762
+ */
5763
+ timedOut(failure3) {
5764
+ const burst = this.burst ?? this.openBurst(Date.now());
5765
+ burst.last = Date.now();
5766
+ if (burst.timeouts++ === 0) this.fail(failure3);
5767
+ }
5768
+ openBurst(now) {
5769
+ const burst = { events: false, timeouts: 0, last: now };
5770
+ this.burst = burst;
5771
+ this.armBurst(burst, HOST_BURST_QUIET_MS);
5772
+ return burst;
5773
+ }
5774
+ /**
5775
+ * One timer per burst, re-armed for the rest of the quiet period when activity moved `last`.
5776
+ * @param burst The open burst.
5777
+ * @param delay When to check it next.
5778
+ */
5779
+ armBurst(burst, delay) {
5780
+ burst.timer = setTimeout(() => {
5781
+ if (this.burst !== burst || this.disposed) return;
5782
+ const quiet = Date.now() - burst.last;
5783
+ if (quiet < HOST_BURST_QUIET_MS) {
5784
+ this.armBurst(burst, HOST_BURST_QUIET_MS - quiet);
5785
+ return;
5786
+ }
5787
+ this.burst = void 0;
5788
+ this.window = { start: 0, events: 0 };
5789
+ void this.recover(burst);
5790
+ }, delay);
5791
+ burst.timer.unref?.();
5792
+ }
5793
+ /**
5794
+ * The pass after a burst: the summary of its held-back timeouts, then every open output
5795
+ * obligation whose file is still what its generation wrote is reported again, in path order,
5796
+ * and every recorded input is re-observed once no generation is in flight. An output a newer generation rewrote since is left
5797
+ * to that generation's own report, which supersedes the older expectation.
5798
+ * @param burst The burst that ended.
5799
+ */
5800
+ async recover(burst) {
5801
+ if (burst.timeouts > 1) {
5802
+ this.recovery?.warn(
5803
+ `[NGDOC_VITE_HOST_TIMEOUT] ${burst.timeouts - 1} more host update(s) did not settle within ${HOST_COMPLETION_TIMEOUT_MS / 1e3} s while files kept changing. NgDoc checks the generated files and the documentation inputs again.`
5804
+ );
5805
+ }
5806
+ const open2 = /* @__PURE__ */ new Map();
5807
+ for (const attempt of [...this.attempts.values()].sort(
5808
+ (left, right) => right.generation - left.generation
5809
+ )) {
5810
+ if (!attempt.resultSeen) continue;
5811
+ for (const expected of attempt.expected.values()) {
5812
+ if (!expected.completed && !open2.has(expected.path)) open2.set(expected.path, expected);
5813
+ }
5814
+ }
5815
+ for (const [file, expected] of [...open2].sort(([left], [right]) => left < right ? -1 : 1)) {
5816
+ let current;
5817
+ let written;
5818
+ try {
5819
+ [current, written] = await Promise.all([observePhysicalState(file), expected.state]);
5820
+ } catch {
5821
+ continue;
5822
+ }
5823
+ if (this.disposed) return;
5824
+ const unchanged = expected.digest === void 0 ? current.version === "missing" : current.digest === expected.digest && current.version === written.version;
5825
+ if (expected.completed || !unchanged) continue;
5826
+ try {
5827
+ this.recovery?.replay(
5828
+ file,
5829
+ expected.kind === "update" ? "update" : expected.digest === void 0 ? "delete" : "create"
5830
+ );
5831
+ } catch {
5832
+ }
5833
+ }
5834
+ for (let latest = this.latestAttempt; latest && !latest.resultSeen && !this.disposed; latest = this.latestAttempt)
5835
+ await latest.result.promise;
5836
+ if (this.disposed) return;
5837
+ try {
5838
+ this.recovery?.rescan();
5839
+ } catch {
5840
+ }
5841
+ }
5723
5842
  };
5724
5843
 
5725
5844
  // libs/builder/generator/vite/angular-composition.ts
@@ -6335,6 +6454,28 @@ function sameRuntimeConfiguration(left, right) {
6335
6454
  return left.outputRoot === right.outputRoot && left.cacheRoot === right.cacheRoot && left.assetDirectory === right.assetDirectory;
6336
6455
  }
6337
6456
 
6457
+ // libs/builder/generator/vite/demo-application.ts
6458
+ import { readFile as readFile5 } from "node:fs/promises";
6459
+ import path11 from "node:path";
6460
+ var DEMO_APPLICATION_MODULE = "demo-app.ts";
6461
+ var DEMO_PATH = /^export const NG_DOC_DEMO_PATH = '([^'\\\n]*)';$/m;
6462
+ var DEMO_PAGES = /^export const NG_DOC_DEMO_PAGES: string\[\] = (.*);$/m;
6463
+ async function readDemoApplication(outputRoot) {
6464
+ const module = path11.join(outputRoot, DEMO_APPLICATION_MODULE);
6465
+ const source = await readFile5(module, "utf8").catch(() => void 0);
6466
+ const demoPath = source === void 0 ? void 0 : DEMO_PATH.exec(source)?.[1];
6467
+ const pages2 = source === void 0 ? void 0 : demoPages(DEMO_PAGES.exec(source)?.[1]);
6468
+ return demoPath && pages2 ? { module, path: demoPath, pages: pages2 } : void 0;
6469
+ }
6470
+ function demoPages(json) {
6471
+ try {
6472
+ const value = json === void 0 ? void 0 : JSON.parse(json);
6473
+ return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
6474
+ } catch {
6475
+ return void 0;
6476
+ }
6477
+ }
6478
+
6338
6479
  // libs/builder/generator/vite/diagnostics.ts
6339
6480
  function diagnosticText(diagnostic4) {
6340
6481
  const source = diagnostic4.source ? ` (${diagnostic4.source.path}${diagnostic4.source.line ? `:${diagnostic4.source.line}` : ""})` : "";
@@ -6350,7 +6491,7 @@ function hostDiagnostic(code, message4) {
6350
6491
  }
6351
6492
 
6352
6493
  // libs/builder/generator/vite/lease.ts
6353
- import path11 from "node:path";
6494
+ import path12 from "node:path";
6354
6495
  var identities = /* @__PURE__ */ new Map();
6355
6496
  var outputRoots = /* @__PURE__ */ new Map();
6356
6497
  function acquireOutputLease(identity2, defaultOutputRoot) {
@@ -6358,7 +6499,7 @@ function acquireOutputLease(identity2, defaultOutputRoot) {
6358
6499
  throw new Error(`[NGDOC_VITE_OUTPUT_LEASE] Output owner is already active for ${identity2}.`);
6359
6500
  }
6360
6501
  const token = Symbol(identity2);
6361
- const initialRoot = canonicalDrive(path11.resolve(defaultOutputRoot).replace(/\\/g, "/"));
6502
+ const initialRoot = canonicalDrive(path12.resolve(defaultOutputRoot).replace(/\\/g, "/"));
6362
6503
  if (outputRoots.has(initialRoot)) {
6363
6504
  throw new Error(
6364
6505
  `[NGDOC_VITE_OUTPUT_LEASE] Default output root is already active: ${initialRoot}.`
@@ -6372,7 +6513,7 @@ function acquireOutputLease(identity2, defaultOutputRoot) {
6372
6513
  return {
6373
6514
  admit(value) {
6374
6515
  if (disposed) throw new Error("[NGDOC_VITE_OUTPUT_LEASE] Output lease is disposed.");
6375
- const normalized = canonicalDrive(path11.resolve(value).replace(/\\/g, "/"));
6516
+ const normalized = canonicalDrive(path12.resolve(value).replace(/\\/g, "/"));
6376
6517
  const owner = outputRoots.get(normalized);
6377
6518
  if (owner && owner !== token) {
6378
6519
  throw new Error(
@@ -6403,11 +6544,11 @@ function acquireOutputLease(identity2, defaultOutputRoot) {
6403
6544
 
6404
6545
  // libs/builder/generator/vite/lifecycle.ts
6405
6546
  import { existsSync as existsSync5 } from "node:fs";
6406
- import path16 from "node:path";
6547
+ import path17 from "node:path";
6407
6548
 
6408
6549
  // libs/builder/generator/vite/assets.ts
6409
- import { readFile as readFile5 } from "node:fs/promises";
6410
- import path12 from "node:path";
6550
+ import { readFile as readFile6 } from "node:fs/promises";
6551
+ import path13 from "node:path";
6411
6552
  var GENERATED_ASSET_PATH = "assets/ng-doc";
6412
6553
  var MIME = /* @__PURE__ */ new Map([
6413
6554
  [".css", "text/css; charset=utf-8"],
@@ -6429,7 +6570,7 @@ function normalizedBase(base) {
6429
6570
  return value.endsWith("/") ? value : `${value}/`;
6430
6571
  }
6431
6572
  function safeRelative2(value) {
6432
- return !!value && !value.includes("\0") && !value.includes("\\") && !path12.posix.isAbsolute(value) && !value.split("/").some((segment) => segment === ".." || segment === "");
6573
+ return !!value && !value.includes("\0") && !value.includes("\\") && !path13.posix.isAbsolute(value) && !value.split("/").some((segment) => segment === ".." || segment === "");
6433
6574
  }
6434
6575
  function inventory(configuration, manifest) {
6435
6576
  const prefix = `${configuration.assetDirectory.replace(/\\/g, "/").replace(/\/$/, "")}/`;
@@ -6446,7 +6587,7 @@ function inventory(configuration, manifest) {
6446
6587
  throw new Error(`[NGDOC_VITE_ASSET_COLLISION] Duplicate generated asset: ${relative}.`);
6447
6588
  }
6448
6589
  result.set(relative, {
6449
- file: path12.join(configuration.outputRoot, output),
6590
+ file: path13.join(configuration.outputRoot, output),
6450
6591
  outputName: outputName2
6451
6592
  });
6452
6593
  }
@@ -6485,12 +6626,12 @@ var GeneratedAssetInventory = class {
6485
6626
  next2();
6486
6627
  return;
6487
6628
  }
6488
- void readFile5(asset.file).then(
6629
+ void readFile6(asset.file).then(
6489
6630
  (source) => {
6490
6631
  response.statusCode = 200;
6491
6632
  response.setHeader(
6492
6633
  "Content-Type",
6493
- MIME.get(path12.extname(relative).toLowerCase()) ?? "application/octet-stream"
6634
+ MIME.get(path13.extname(relative).toLowerCase()) ?? "application/octet-stream"
6494
6635
  );
6495
6636
  response.end(source);
6496
6637
  },
@@ -6513,7 +6654,7 @@ var GeneratedAssetInventory = class {
6513
6654
  context.emitFile({
6514
6655
  type: "asset",
6515
6656
  fileName: asset.outputName,
6516
- source: await readFile5(asset.file)
6657
+ source: await readFile6(asset.file)
6517
6658
  });
6518
6659
  }
6519
6660
  }
@@ -6524,7 +6665,7 @@ var GeneratedAssetInventory = class {
6524
6665
 
6525
6666
  // libs/builder/generator/vite/vite-event-source.ts
6526
6667
  import { existsSync as existsSync4, realpathSync as realpathSync2, statSync as statSync6 } from "node:fs";
6527
- import path15 from "node:path";
6668
+ import path16 from "node:path";
6528
6669
 
6529
6670
  // libs/builder/generator/vite/external-probes.ts
6530
6671
  import { statSync as statSync5 } from "node:fs";
@@ -6605,7 +6746,7 @@ var ExternalProbeWatcher = class {
6605
6746
 
6606
6747
  // libs/builder/generator/vite/missing-paths.ts
6607
6748
  import { existsSync as existsSync3, watch as watchDirectory } from "node:fs";
6608
- import path13 from "node:path";
6749
+ import path14 from "node:path";
6609
6750
  var MAX_MISSING_PATH_DIRECTORIES = 1024;
6610
6751
  var defaultWatch = (directory, listener) => watchDirectory(
6611
6752
  directory,
@@ -6613,9 +6754,9 @@ var defaultWatch = (directory, listener) => watchDirectory(
6613
6754
  (event, name) => listener(event, typeof name === "string" ? name : null)
6614
6755
  );
6615
6756
  function anchorOf(target) {
6616
- let cursor = path13.dirname(target);
6757
+ let cursor = path14.dirname(target);
6617
6758
  while (!existsSync3(cursor)) {
6618
- const parent = path13.dirname(cursor);
6759
+ const parent = path14.dirname(cursor);
6619
6760
  if (parent === cursor) break;
6620
6761
  cursor = parent;
6621
6762
  }
@@ -6702,7 +6843,7 @@ var MissingPathWatcher = class {
6702
6843
  const members2 = [...this.members.get(anchor) ?? []];
6703
6844
  const gone = !existsSync3(anchor);
6704
6845
  for (const target of members2) {
6705
- const entry = path13.relative(anchor, target).split(path13.sep)[0];
6846
+ const entry = path14.relative(anchor, target).split(path14.sep)[0];
6706
6847
  if (!gone && name !== null && name !== entry) continue;
6707
6848
  this.refresh(target);
6708
6849
  }
@@ -6725,9 +6866,9 @@ var MissingPathWatcher = class {
6725
6866
  // libs/builder/generator/vite/watch-inputs.ts
6726
6867
  import { Minimatch } from "minimatch";
6727
6868
  import { access } from "node:fs/promises";
6728
- import path14 from "node:path";
6869
+ import path15 from "node:path";
6729
6870
  function normalize4(value) {
6730
- return path14.resolve(value).replace(/\\/g, "/");
6871
+ return path15.resolve(value).replace(/\\/g, "/");
6731
6872
  }
6732
6873
  function union(left, right) {
6733
6874
  const files = new Set([...left?.files ?? [], ...right?.files ?? []].map(normalize4));
@@ -6749,14 +6890,14 @@ function inputKey(inputs) {
6749
6890
  return JSON.stringify(inputs);
6750
6891
  }
6751
6892
  function absolutePattern(root, pattern) {
6752
- return normalize4(path14.isAbsolute(pattern) ? pattern : path14.join(root, pattern));
6893
+ return normalize4(path15.isAbsolute(pattern) ? pattern : path15.join(root, pattern));
6753
6894
  }
6754
6895
  function patternBase(root, pattern) {
6755
6896
  const absolute2 = absolutePattern(root, pattern);
6756
6897
  const magic = absolute2.search(/[*?{}[\]()!]/);
6757
- if (magic < 0) return path14.dirname(absolute2).replace(/\\/g, "/");
6898
+ if (magic < 0) return path15.dirname(absolute2).replace(/\\/g, "/");
6758
6899
  const slash2 = absolute2.lastIndexOf("/", magic);
6759
- return (slash2 > 0 ? absolute2.slice(0, slash2) : path14.parse(absolute2).root).replace(/\\/g, "/");
6900
+ return (slash2 > 0 ? absolute2.slice(0, slash2) : path15.parse(absolute2).root).replace(/\\/g, "/");
6760
6901
  }
6761
6902
  async function nearestExisting(value) {
6762
6903
  let cursor = normalize4(value);
@@ -6765,7 +6906,7 @@ async function nearestExisting(value) {
6765
6906
  await access(cursor);
6766
6907
  return cursor;
6767
6908
  } catch {
6768
- const parent = path14.dirname(cursor).replace(/\\/g, "/");
6909
+ const parent = path15.dirname(cursor).replace(/\\/g, "/");
6769
6910
  if (parent === cursor) return cursor;
6770
6911
  cursor = parent;
6771
6912
  }
@@ -6775,16 +6916,16 @@ function ancestorOf(target, candidate) {
6775
6916
  return target === candidate || target.startsWith(`${candidate}/`);
6776
6917
  }
6777
6918
  function globPattern(pattern) {
6778
- return path14.sep === "\\" ? pattern.replace(/\\/g, "/") : pattern;
6919
+ return path15.sep === "\\" ? pattern.replace(/\\/g, "/") : pattern;
6779
6920
  }
6780
6921
  function compilePattern(pattern) {
6781
6922
  return {
6782
- absolute: path14.isAbsolute(pattern),
6923
+ absolute: path15.isAbsolute(pattern),
6783
6924
  matcher: new Minimatch(globPattern(pattern), { dot: true })
6784
6925
  };
6785
6926
  }
6786
6927
  function patternMatches(glob2, pattern, changed) {
6787
- const subject = pattern.absolute ? changed : path14.relative(glob2.root, changed).replace(/\\/g, "/");
6928
+ const subject = pattern.absolute ? changed : path15.relative(glob2.root, changed).replace(/\\/g, "/");
6788
6929
  return pattern.matcher.match(subject);
6789
6930
  }
6790
6931
  function globMatches(glob2, changed) {
@@ -6871,7 +7012,7 @@ var WatchInputRegistry = class {
6871
7012
  const directories = /* @__PURE__ */ new Set();
6872
7013
  for (const target of globBases) {
6873
7014
  const existing = await nearestExisting(target);
6874
- if (existing === path14.parse(existing).root.replace(/\\/g, "/")) {
7015
+ if (existing === path15.parse(existing).root.replace(/\\/g, "/")) {
6875
7016
  throw new Error(
6876
7017
  `[NGDOC_VITE_WATCH_ROOT] Refusing an unbounded filesystem-root glob watch for ${target}.`
6877
7018
  );
@@ -6882,7 +7023,7 @@ var WatchInputRegistry = class {
6882
7023
  }
6883
7024
  }
6884
7025
  for (const target of desired) {
6885
- if (target === path14.parse(target).root.replace(/\\/g, "/")) {
7026
+ if (target === path15.parse(target).root.replace(/\\/g, "/")) {
6886
7027
  throw new Error(
6887
7028
  `[NGDOC_VITE_WATCH_ROOT] Refusing an unbounded filesystem-root file watch for ${target}.`
6888
7029
  );
@@ -6941,7 +7082,7 @@ var WatchInputRegistry = class {
6941
7082
 
6942
7083
  // libs/builder/generator/vite/vite-event-source.ts
6943
7084
  function normalize5(value) {
6944
- return canonicalDrive(path15.resolve(value).replace(/\\/g, "/"));
7085
+ return canonicalDrive(path16.resolve(value).replace(/\\/g, "/"));
6945
7086
  }
6946
7087
  function within2(target, root) {
6947
7088
  return target === root || target.startsWith(`${root}/`);
@@ -6962,12 +7103,12 @@ function statVersion(file) {
6962
7103
  }
6963
7104
  }
6964
7105
  function listed(watched, target) {
6965
- return watched.has(target) || (watched.get(path15.dirname(target))?.includes(path15.basename(target)) ?? false);
7106
+ return watched.has(target) || (watched.get(path16.dirname(target))?.includes(path16.basename(target)) ?? false);
6966
7107
  }
6967
7108
  function nearestExistingPath(target) {
6968
7109
  let cursor = target;
6969
7110
  while (!existsSync4(cursor)) {
6970
- const parent = path15.dirname(cursor);
7111
+ const parent = path16.dirname(cursor);
6971
7112
  if (parent === cursor) return cursor;
6972
7113
  cursor = parent;
6973
7114
  }
@@ -6992,7 +7133,7 @@ function workspaceRoots(root) {
6992
7133
  roots.add(normalize5(realpathSync2.native(root)));
6993
7134
  } catch {
6994
7135
  }
6995
- return [...roots].filter((value) => value !== normalize5(path15.parse(value).root));
7136
+ return [...roots].filter((value) => value !== normalize5(path16.parse(value).root));
6996
7137
  }
6997
7138
  function watchesMissingPaths(watcher) {
6998
7139
  const options = watcher.options;
@@ -7446,7 +7587,24 @@ var ViteAdapterLifecycle = class {
7446
7587
  this.latestStarted = -1;
7447
7588
  this.hostUpdates = new HostUpdateCoordinator(
7448
7589
  () => this.server?.ws.send({ type: "full-reload" }),
7449
- (error2) => this.fail(error2)
7590
+ (error2) => this.fail(error2),
7591
+ {
7592
+ warn: (message4) => this.warn(message4),
7593
+ // Exactly the event Vite's watcher emits for the file: Vite invalidates its modules and
7594
+ // runs the hot update hooks, which the coordinator matches like the lost report.
7595
+ replay: (file, type) => {
7596
+ if (this.disposed) return;
7597
+ this.server?.watcher.emit(
7598
+ type === "create" ? "add" : type === "delete" ? "unlink" : "change",
7599
+ file
7600
+ );
7601
+ },
7602
+ rescan: () => {
7603
+ if (this.disposed) return;
7604
+ void this.session?.rescan().catch(() => {
7605
+ });
7606
+ }
7607
+ }
7450
7608
  );
7451
7609
  }
7452
7610
  get failure() {
@@ -7511,13 +7669,13 @@ var ViteAdapterLifecycle = class {
7511
7669
  const read = /* @__PURE__ */ new Set();
7512
7670
  for (const artifact of committed.snapshot.artifacts) {
7513
7671
  for (const dependency of artifact.dependencies) {
7514
- if (dependency.kind === "content" && DESCRIPTION_MODULE.test(path16.basename(dependency.path)))
7515
- read.add(canonicalDrive(path16.resolve(dependency.path)));
7672
+ if (dependency.kind === "content" && DESCRIPTION_MODULE.test(path17.basename(dependency.path)))
7673
+ read.add(canonicalDrive(path17.resolve(dependency.path)));
7516
7674
  }
7517
7675
  }
7518
7676
  committed.read = read;
7519
7677
  }
7520
- return committed.read.has(canonicalDrive(path16.resolve(file)));
7678
+ return committed.read.has(canonicalDrive(path17.resolve(file)));
7521
7679
  }
7522
7680
  hostUpdateStarted(file, type, read) {
7523
7681
  const change = { kind: type, path: file };
@@ -7562,7 +7720,7 @@ var ViteAdapterLifecycle = class {
7562
7720
  hostClaimsFilesystemChange(file) {
7563
7721
  if (this.disposed) return false;
7564
7722
  if (this.hostUpdates.generated(file)) return true;
7565
- if (!DESCRIPTION_MODULE.test(path16.basename(file)) || !(this.source?.matches({ kind: "update", path: file }) ?? false))
7723
+ if (!DESCRIPTION_MODULE.test(path17.basename(file)) || !(this.source?.matches({ kind: "update", path: file }) ?? false))
7566
7724
  return false;
7567
7725
  return !(this.committedDescriptionModule(file) && existsSync5(file));
7568
7726
  }
@@ -7717,6 +7875,14 @@ var ViteAdapterLifecycle = class {
7717
7875
  } catch {
7718
7876
  }
7719
7877
  }
7878
+ warn(message4) {
7879
+ if (!this.server) return;
7880
+ this.interrupt();
7881
+ try {
7882
+ this.server.config.logger.warn(message4);
7883
+ } catch {
7884
+ }
7885
+ }
7720
7886
  isCurrent(generation) {
7721
7887
  return !this.disposed && generation >= this.latestStarted && (!this.source || this.source.isCurrent(generation));
7722
7888
  }
@@ -7758,7 +7924,7 @@ var ViteAdapterLifecycle = class {
7758
7924
  var NG_DOC_SYNTAX_THEME_NAME = "css-variables";
7759
7925
 
7760
7926
  // libs/builder/generator/vite/options.ts
7761
- import path17 from "node:path";
7927
+ import path18 from "node:path";
7762
7928
 
7763
7929
  // libs/builder/generator/progress/settings.ts
7764
7930
  var PROGRESS_SETTINGS = Object.freeze([
@@ -7887,6 +8053,16 @@ var ENGINE_SWITCHES = Object.freeze([
7887
8053
  values: /^(0|false|off|no|verify)$/i,
7888
8054
  effect: "every code block is highlighted again"
7889
8055
  },
8056
+ {
8057
+ name: "NGDOC_FORMAT_CACHE",
8058
+ values: /^(0|false|off|no|verify)$/i,
8059
+ effect: "every signature and snippet is formatted again"
8060
+ },
8061
+ {
8062
+ name: "NGDOC_USED_GRAMMARS",
8063
+ values: /^(0|false|off|no)$/i,
8064
+ effect: "every Shiki grammar is loaded before code is highlighted"
8065
+ },
7890
8066
  {
7891
8067
  name: "NGDOC_PARALLEL_RENDER",
7892
8068
  values: /^(0|false|off|no|verify)$/i,
@@ -7942,7 +8118,7 @@ function resolveOptions(options) {
7942
8118
  );
7943
8119
  }
7944
8120
  const angularComponentProbe = text2(options.angularComponentProbe, "angularComponentProbe");
7945
- if (!path17.isAbsolute(angularComponentProbe) || !/\.ts$/.test(angularComponentProbe) || /\.d\.ts$/.test(angularComponentProbe) || angularComponentProbe.includes("?")) {
8121
+ if (!path18.isAbsolute(angularComponentProbe) || !/\.ts$/.test(angularComponentProbe) || /\.d\.ts$/.test(angularComponentProbe) || angularComponentProbe.includes("?")) {
7946
8122
  throw new TypeError(
7947
8123
  "angularComponentProbe must be an absolute application .ts component path."
7948
8124
  );
@@ -7961,8 +8137,8 @@ function resolveOptions(options) {
7961
8137
  if (options.progress !== void 0 && (!isProgressSetting(options.progress) || options.progress === "json")) {
7962
8138
  throw new TypeError("progress must be auto, live, plain, verbose, summary or off.");
7963
8139
  }
7964
- const workspace = path17.resolve(text2(options.generator.workspaceRoot, "workspaceRoot"));
7965
- const defaultRoot = path17.resolve(
8140
+ const workspace = path18.resolve(text2(options.generator.workspaceRoot, "workspaceRoot"));
8141
+ const defaultRoot = path18.resolve(
7966
8142
  text2(options.generator.defaults.outputRoot, "defaults.outputRoot")
7967
8143
  );
7968
8144
  return {
@@ -7972,7 +8148,7 @@ function resolveOptions(options) {
7972
8148
  themeModules,
7973
8149
  leaseIdentity: `${text2(options.generator.projectId, "projectId")}\0${workspace}\0${defaultRoot}`,
7974
8150
  angularPlugins: [...options.angularPlugins],
7975
- angularComponentProbe: path17.normalize(angularComponentProbe),
8151
+ angularComponentProbe: path18.normalize(angularComponentProbe),
7976
8152
  ...options.progress ? { progress: options.progress } : {}
7977
8153
  };
7978
8154
  }
@@ -7989,14 +8165,14 @@ function themeImports(options, configuration) {
7989
8165
  ].sort();
7990
8166
  }
7991
8167
  function within3(root, file) {
7992
- const relative = path17.relative(root, file);
7993
- return relative === "" || !relative.startsWith("..") && !path17.isAbsolute(relative);
8168
+ const relative = path18.relative(root, file);
8169
+ return relative === "" || !relative.startsWith("..") && !path18.isAbsolute(relative);
7994
8170
  }
7995
8171
  var CacheRootWatchIgnore = class {
7996
8172
  constructor(options) {
7997
8173
  this.options = options;
7998
8174
  const cacheRoot = options.generator.defaults.cacheRoot;
7999
- this.cacheRoot = typeof cacheRoot === "string" && path17.isAbsolute(cacheRoot) ? canonicalDrive(path17.resolve(cacheRoot)) : void 0;
8175
+ this.cacheRoot = typeof cacheRoot === "string" && path18.isAbsolute(cacheRoot) ? canonicalDrive(path18.resolve(cacheRoot)) : void 0;
8000
8176
  this.active = this.cacheRoot !== void 0;
8001
8177
  }
8002
8178
  get ignoring() {
@@ -8010,11 +8186,11 @@ var CacheRootWatchIgnore = class {
8010
8186
  workspaceRoot,
8011
8187
  defaults.outputRoot,
8012
8188
  defaults.docsRoot,
8013
- typeof defaults.tsConfig === "string" ? path17.dirname(defaults.tsConfig) : void 0
8189
+ typeof defaults.tsConfig === "string" ? path18.dirname(defaults.tsConfig) : void 0
8014
8190
  ]);
8015
8191
  const cacheRoot = this.cacheRoot;
8016
8192
  if (!this.active || !cacheRoot) return void 0;
8017
- return (file) => this.active && typeof file === "string" && within3(cacheRoot, path17.resolve(file));
8193
+ return (file) => this.active && typeof file === "string" && within3(cacheRoot, path18.resolve(file));
8018
8194
  }
8019
8195
  /** Re-checks the finally resolved Vite root (a later plugin can change `root`). */
8020
8196
  confirmRoot(viteRoot) {
@@ -8027,7 +8203,7 @@ var CacheRootWatchIgnore = class {
8027
8203
  admit(configuration) {
8028
8204
  const cacheRoot = this.cacheRoot;
8029
8205
  if (!this.active || !cacheRoot) return false;
8030
- const publishedCache = typeof configuration.cacheRoot === "string" ? canonicalDrive(path17.resolve(configuration.cacheRoot)) : void 0;
8206
+ const publishedCache = typeof configuration.cacheRoot === "string" ? canonicalDrive(path18.resolve(configuration.cacheRoot)) : void 0;
8031
8207
  if (publishedCache === cacheRoot && this.guard([configuration.outputRoot])) return false;
8032
8208
  this.active = false;
8033
8209
  return true;
@@ -8035,7 +8211,7 @@ var CacheRootWatchIgnore = class {
8035
8211
  guard(roots) {
8036
8212
  const cacheRoot = this.cacheRoot;
8037
8213
  if (!this.active || !cacheRoot || // Unvalidated roots are rejected by bootstrap later; never ignore when one is missing.
8038
- roots.some((value) => typeof value !== "string" || !value) || roots.some((value) => within3(cacheRoot, path17.resolve(value)))) {
8214
+ roots.some((value) => typeof value !== "string" || !value) || roots.some((value) => within3(cacheRoot, path18.resolve(value)))) {
8039
8215
  this.active = false;
8040
8216
  }
8041
8217
  return this.active;
@@ -8043,26 +8219,26 @@ var CacheRootWatchIgnore = class {
8043
8219
  };
8044
8220
  function metadataWatchIgnore(options, viteRoot) {
8045
8221
  const { workspaceRoot, defaults } = options.generator;
8046
- if (typeof workspaceRoot !== "string" || !path17.isAbsolute(workspaceRoot)) return void 0;
8047
- const idea = path17.resolve(workspaceRoot, ".idea");
8222
+ if (typeof workspaceRoot !== "string" || !path18.isAbsolute(workspaceRoot)) return void 0;
8223
+ const idea = path18.resolve(workspaceRoot, ".idea");
8048
8224
  const roots = [
8049
8225
  viteRoot,
8050
8226
  defaults.outputRoot,
8051
8227
  defaults.docsRoot,
8052
- typeof defaults.tsConfig === "string" ? path17.dirname(defaults.tsConfig) : void 0
8228
+ typeof defaults.tsConfig === "string" ? path18.dirname(defaults.tsConfig) : void 0
8053
8229
  ];
8054
8230
  const ignoreIdea = !roots.some(
8055
- (value) => typeof value === "string" && value && within3(idea, path17.resolve(value))
8231
+ (value) => typeof value === "string" && value && within3(idea, path18.resolve(value))
8056
8232
  );
8057
8233
  return (file) => {
8058
8234
  if (typeof file !== "string") return false;
8059
- const target = path17.resolve(file);
8060
- return path17.basename(target) === ".DS_Store" || ignoreIdea && within3(idea, target);
8235
+ const target = path18.resolve(file);
8236
+ return path18.basename(target) === ".DS_Store" || ignoreIdea && within3(idea, target);
8061
8237
  };
8062
8238
  }
8063
8239
  function staticViteConfig(options, viteRoot = process.cwd(), cacheWatch = new CacheRootWatchIgnore(options)) {
8064
- const cacheIgnore = cacheWatch.matcher(path17.resolve(viteRoot));
8065
- const metadataIgnore = metadataWatchIgnore(options, path17.resolve(viteRoot));
8240
+ const cacheIgnore = cacheWatch.matcher(path18.resolve(viteRoot));
8241
+ const metadataIgnore = metadataWatchIgnore(options, path18.resolve(viteRoot));
8066
8242
  const ignored = [GENERATED_STAGE_IGNORE];
8067
8243
  if (cacheIgnore) ignored.push(cacheIgnore);
8068
8244
  if (metadataIgnore) ignored.push(metadataIgnore);
@@ -8098,10 +8274,10 @@ function assertThemeModules(options, configuration) {
8098
8274
 
8099
8275
  // libs/builder/generator/vite/output-watch.ts
8100
8276
  import { realpathSync as realpathSync3 } from "node:fs";
8101
- import path18 from "node:path";
8277
+ import path19 from "node:path";
8102
8278
  var OUTPUT_WATCH_ATTACH_TIMEOUT_MS = 1e4;
8103
8279
  function normalize6(value) {
8104
- return canonicalDrive(path18.resolve(value).replace(/\\/g, "/"));
8280
+ return canonicalDrive(path19.resolve(value).replace(/\\/g, "/"));
8105
8281
  }
8106
8282
  function within4(target, root) {
8107
8283
  return target === root || target.startsWith(`${root}/`);
@@ -8133,9 +8309,9 @@ async function watchOutputRoot(watcher, viteRoot, outputRoot, files, options = {
8133
8309
  for (const file of files) {
8134
8310
  let target = `${root}/${file.replace(/\\/g, "/")}`;
8135
8311
  while (target !== root && within4(target, root)) {
8136
- const parent = path18.posix.dirname(target);
8312
+ const parent = path19.posix.dirname(target);
8137
8313
  const listed2 = entries.get(parent) ?? /* @__PURE__ */ new Set();
8138
- listed2.add(path18.posix.basename(target));
8314
+ listed2.add(path19.posix.basename(target));
8139
8315
  entries.set(parent, listed2);
8140
8316
  target = parent;
8141
8317
  }
@@ -9551,7 +9727,7 @@ function hotPayload(value, childToServer = false) {
9551
9727
  case "full-reload":
9552
9728
  return (value.path === void 0 || typeof value.path === "string") && (value.triggeredBy === void 0 || typeof value.triggeredBy === "string");
9553
9729
  case "prune":
9554
- return Array.isArray(value.paths) && value.paths.every((path24) => typeof path24 === "string");
9730
+ return Array.isArray(value.paths) && value.paths.every((path25) => typeof path25 === "string");
9555
9731
  case "update":
9556
9732
  return Array.isArray(value.updates) && value.updates.every(
9557
9733
  (update) => record2(update) && (update.type === "js-update" || update.type === "css-update") && typeof update.path === "string" && typeof update.acceptedPath === "string" && Number.isFinite(update.timestamp)
@@ -10367,10 +10543,10 @@ function getNgDocViteSsrRenderer(plugins, options) {
10367
10543
  }
10368
10544
 
10369
10545
  // libs/builder/generator/vite/theme-index.ts
10370
- import { readFile as readFile6 } from "node:fs/promises";
10546
+ import { readFile as readFile7 } from "node:fs/promises";
10371
10547
  var MARKER = "data-ng-doc-theme-restore";
10372
10548
  async function createThemeIndexTransformer(scriptUrl = new URL("./restore-theme.js", import.meta.url)) {
10373
- const source = await readFile6(scriptUrl, "utf8");
10549
+ const source = await readFile7(scriptUrl, "utf8");
10374
10550
  const tag = `<script ${MARKER}>${source}</script>`;
10375
10551
  return async (content) => {
10376
10552
  if (content.includes(MARKER)) return content;
@@ -10642,6 +10818,20 @@ function createPlugin(options) {
10642
10818
  ngDocSsrRenderer: Object.freeze({
10643
10819
  schemaVersion: 1,
10644
10820
  select: (entry) => ssrRenderer.select(entry)
10821
+ }),
10822
+ ngDocDemoApplication: Object.freeze({
10823
+ schemaVersion: 1,
10824
+ // A build decides its inputs before it starts, so the application plugin asks for the
10825
+ // generation early (its `options` hook); `buildStart` then finds it initialized.
10826
+ async resolve() {
10827
+ if (vite?.command === "build") await initializeBuild();
10828
+ const state = active ?? await initialized;
10829
+ return state ? readDemoApplication(state.configuration.outputRoot) : void 0;
10830
+ },
10831
+ async current() {
10832
+ const configuration = active?.lifecycle.configuration ?? active?.configuration;
10833
+ return configuration ? readDemoApplication(configuration.outputRoot) : void 0;
10834
+ }
10645
10835
  })
10646
10836
  },
10647
10837
  enforce: "pre",
@@ -10663,7 +10853,7 @@ function createPlugin(options) {
10663
10853
  "[NGDOC_VITE_BUILD_WATCH] Vite build watch is unsupported; use the development server."
10664
10854
  );
10665
10855
  }
10666
- return staticViteConfig(resolved, path19.resolve(config.root ?? process.cwd()), cacheWatch);
10856
+ return staticViteConfig(resolved, path20.resolve(config.root ?? process.cwd()), cacheWatch);
10667
10857
  },
10668
10858
  configResolved(config) {
10669
10859
  vite = config;
@@ -10844,13 +11034,13 @@ function createPlugin(options) {
10844
11034
  // libs/builder/generator/vite/application.ts
10845
11035
  import { minimatch as minimatch2 } from "minimatch";
10846
11036
  import { existsSync as existsSync6 } from "node:fs";
10847
- import { readdir, readFile as readFile7, stat as stat5 } from "node:fs/promises";
11037
+ import { mkdir as mkdir2, readdir, readFile as readFile8, stat as stat5, writeFile as writeFile2 } from "node:fs/promises";
10848
11038
  import { createRequire as createRequire2 } from "node:module";
10849
- import path22 from "node:path";
11039
+ import path23 from "node:path";
10850
11040
  import { escapePath, glob } from "tinyglobby";
10851
11041
 
10852
11042
  // libs/builder/generator/vite/dev-styles.ts
10853
- import path20 from "node:path";
11043
+ import path21 from "node:path";
10854
11044
  var GATE_TIMINGS = { sheet: 5e3, quiet: 1500, limit: 1e4 };
10855
11045
  function ngDocDevStyleGate(timings = GATE_TIMINGS) {
10856
11046
  return `(() => {
@@ -10909,8 +11099,8 @@ async function ngDocDevStyleTags(server, styles) {
10909
11099
  { tag: "script", children: NG_DOC_DEV_STYLE_GATE, injectTo: "head-prepend" }
10910
11100
  ];
10911
11101
  for (const style of styles) {
10912
- const relative = path20.relative(root, style);
10913
- const url = relative && !relative.startsWith("..") && !path20.isAbsolute(relative) ? `/${relative.split(path20.sep).join("/")}` : `/@fs/${style.split(path20.sep).join("/").replace(/^\/+/, "")}`;
11102
+ const relative = path21.relative(root, style);
11103
+ const url = relative && !relative.startsWith("..") && !path21.isAbsolute(relative) ? `/${relative.split(path21.sep).join("/")}` : `/@fs/${style.split(path21.sep).join("/").replace(/^\/+/, "")}`;
10914
11104
  try {
10915
11105
  const sheet = cssModuleSheet((await environment.transformRequest(url))?.code ?? "");
10916
11106
  if (!sheet) continue;
@@ -10942,7 +11132,7 @@ function cssModuleSheet(code) {
10942
11132
  }
10943
11133
 
10944
11134
  // libs/builder/generator/vite/mime.ts
10945
- import path21 from "node:path";
11135
+ import path22 from "node:path";
10946
11136
  var TYPES = {
10947
11137
  ".aac": "audio/aac",
10948
11138
  ".apng": "image/apng",
@@ -10992,14 +11182,21 @@ var TYPES = {
10992
11182
  ".zip": "application/zip"
10993
11183
  };
10994
11184
  function contentType(file) {
10995
- return TYPES[path21.extname(file).toLowerCase()] ?? "application/octet-stream";
11185
+ return TYPES[path22.extname(file).toLowerCase()] ?? "application/octet-stream";
10996
11186
  }
10997
11187
 
10998
11188
  // libs/builder/generator/vite/application.ts
10999
11189
  var NG_DOC_BROWSER_ENTRY = "/@ng-doc-application/browser.js";
11000
11190
  var NG_DOC_SERVER_ENTRY = "/@ng-doc-application/server.js";
11191
+ var NG_DOC_DEMO_ENTRY = "/@ng-doc-application/demo.js";
11192
+ var NG_DOC_DEMO_SERVER_ENTRY = "/@ng-doc-application/demo-server.js";
11193
+ var NG_DOC_DEMO_PAGE = "ng-doc-demo.html";
11194
+ var NG_DOC_DEMO_SERVER_NAME = "demo-server";
11001
11195
  var BROWSER_ID = "\0ng-doc-application:browser";
11002
11196
  var SERVER_ID = "\0ng-doc-application:server";
11197
+ var DEMO_ID = "\0ng-doc-application:demo";
11198
+ var DEMO_SERVER_ID = "\0ng-doc-application:demo-server";
11199
+ var DEMO_ROOT = "<ng-doc-demo-app></ng-doc-demo-app>";
11003
11200
  var ANGULAR_DEDUPE = [
11004
11201
  "@angular/common",
11005
11202
  "@angular/compiler",
@@ -11091,23 +11288,23 @@ function posix(value) {
11091
11288
  return value.replace(/\\/g, "/");
11092
11289
  }
11093
11290
  function specifier(workspaceRoot, value) {
11094
- const file = path22.resolve(workspaceRoot, value);
11095
- return value.startsWith(".") || path22.isAbsolute(value) || existsSync6(file) ? posix(file) : value;
11291
+ const file = path23.resolve(workspaceRoot, value);
11292
+ return value.startsWith(".") || path23.isAbsolute(value) || existsSync6(file) ? posix(file) : value;
11096
11293
  }
11097
11294
  async function resolveAssets(assets, workspaceRoot, sourceRoot) {
11098
11295
  const resolved = [];
11099
11296
  for (const [index, asset] of assets.entries()) {
11100
11297
  if (typeof asset === "string") {
11101
- const input = path22.resolve(workspaceRoot, text4(asset, `assets[${index}]`));
11298
+ const input = path23.resolve(workspaceRoot, text4(asset, `assets[${index}]`));
11102
11299
  const file = await stat5(input).then(
11103
11300
  (value) => value.isFile(),
11104
11301
  () => {
11105
11302
  throw optionError(`assets[${index}] does not exist: ${asset}`);
11106
11303
  }
11107
11304
  );
11108
- const directory = file ? path22.dirname(input) : input;
11109
- const relative = path22.relative(sourceRoot, directory);
11110
- if (relative.startsWith("..") || path22.isAbsolute(relative)) {
11305
+ const directory = file ? path23.dirname(input) : input;
11306
+ const relative = path23.relative(sourceRoot, directory);
11307
+ if (relative.startsWith("..") || path23.isAbsolute(relative)) {
11111
11308
  throw optionError(
11112
11309
  `assets[${index}] (${asset}) is outside sourceRoot (${sourceRoot}); use a { glob, input, output } entry.`
11113
11310
  );
@@ -11115,18 +11312,18 @@ async function resolveAssets(assets, workspaceRoot, sourceRoot) {
11115
11312
  resolved.push({
11116
11313
  input: directory,
11117
11314
  output: posix(relative),
11118
- glob: file ? path22.basename(input) : "**/*",
11315
+ glob: file ? path23.basename(input) : "**/*",
11119
11316
  ignore: []
11120
11317
  });
11121
11318
  continue;
11122
11319
  }
11123
11320
  if (!asset || typeof asset !== "object") throw optionError(`assets[${index}] is invalid.`);
11124
11321
  const output = posix(asset.output ?? "").replace(/^\/+|\/+$/g, "");
11125
- if (output.split("/").includes("..") || path22.isAbsolute(output)) {
11322
+ if (output.split("/").includes("..") || path23.isAbsolute(output)) {
11126
11323
  throw optionError(`assets[${index}].output must stay inside the output directory.`);
11127
11324
  }
11128
11325
  resolved.push({
11129
- input: path22.resolve(workspaceRoot, text4(asset.input, `assets[${index}].input`)),
11326
+ input: path23.resolve(workspaceRoot, text4(asset.input, `assets[${index}].input`)),
11130
11327
  output,
11131
11328
  glob: text4(asset.glob, `assets[${index}].glob`),
11132
11329
  ignore: [...asset.ignore ?? []]
@@ -11148,7 +11345,7 @@ async function collectAssets(assets) {
11148
11345
  expandDirectories: false
11149
11346
  });
11150
11347
  for (const relative of matches.map(posix).sort()) {
11151
- files.set(outputName(asset, relative), path22.join(asset.input, relative));
11348
+ files.set(outputName(asset, relative), path23.join(asset.input, relative));
11152
11349
  }
11153
11350
  }
11154
11351
  return files;
@@ -11168,14 +11365,14 @@ function matchAssets(assets, base, url) {
11168
11365
  for (const asset of [...assets].reverse()) {
11169
11366
  const relative = asset.output ? requested.startsWith(`${asset.output}/`) ? requested.slice(asset.output.length + 1) : void 0 : requested;
11170
11367
  if (relative && minimatch2(relative, asset.glob, { dot: true }) && !asset.ignore.some((pattern) => minimatch2(relative, pattern, { dot: true }))) {
11171
- candidates.push({ file: path22.join(asset.input, relative), relative });
11368
+ candidates.push({ file: path23.join(asset.input, relative), relative });
11172
11369
  }
11173
11370
  }
11174
11371
  return candidates;
11175
11372
  }
11176
11373
  function resolvable(root, name) {
11177
11374
  try {
11178
- createRequire2(path22.join(root, "package.json")).resolve(`${name}/package.json`);
11375
+ createRequire2(path23.join(root, "package.json")).resolve(`${name}/package.json`);
11179
11376
  return true;
11180
11377
  } catch {
11181
11378
  return false;
@@ -11183,17 +11380,17 @@ function resolvable(root, name) {
11183
11380
  }
11184
11381
  async function coreImports(root) {
11185
11382
  const found = /* @__PURE__ */ new Set();
11186
- const require2 = createRequire2(path22.join(root, "package.json"));
11383
+ const require2 = createRequire2(path23.join(root, "package.json"));
11187
11384
  for (const name of ["@ng-doc/app", "@ng-doc/ui-kit"]) {
11188
11385
  let directory;
11189
11386
  try {
11190
- directory = path22.join(path22.dirname(require2.resolve(`${name}/package.json`)), "fesm2022");
11387
+ directory = path23.join(path23.dirname(require2.resolve(`${name}/package.json`)), "fesm2022");
11191
11388
  } catch {
11192
11389
  continue;
11193
11390
  }
11194
11391
  const files = await readdir(directory).catch(() => []);
11195
11392
  for (const file of files.filter((item) => item.endsWith(".mjs")).sort()) {
11196
- for (const match of (await readFile7(path22.join(directory, file), "utf8")).matchAll(
11393
+ for (const match of (await readFile8(path23.join(directory, file), "utf8")).matchAll(
11197
11394
  CORE_IMPORT
11198
11395
  )) {
11199
11396
  found.add(match[1]);
@@ -11205,6 +11402,20 @@ async function coreImports(root) {
11205
11402
  function escapeAttribute(value) {
11206
11403
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
11207
11404
  }
11405
+ function demoDocument(index) {
11406
+ const body = /<body(\s[^>]*)?>[\s\S]*<\/body>/i;
11407
+ return body.test(index) ? index.replace(
11408
+ body,
11409
+ (_all, attributes) => `<body${attributes ?? ""}>${DEMO_ROOT}</body>`
11410
+ ) : `${index}${DEMO_ROOT}`;
11411
+ }
11412
+ function withDemoInput(input, key, id, next2) {
11413
+ const record3 = typeof input === "string" ? { [path23.parse(input).name]: input } : Array.isArray(input) ? Object.fromEntries(input.map((item) => [path23.parse(item).name, item])) : { ...input ?? {} };
11414
+ if (Object.values(record3).some((item) => item === id || posix(path23.resolve(item)) === id)) {
11415
+ record3[key] = next2;
11416
+ }
11417
+ return record3;
11418
+ }
11208
11419
  function withBase(html, base) {
11209
11420
  if (!base.startsWith("/")) return html;
11210
11421
  const href = escapeAttribute(base);
@@ -11216,30 +11427,38 @@ function withBase(html, base) {
11216
11427
  function createNgDocApplicationPlugin(options) {
11217
11428
  if (!options || typeof options !== "object") throw optionError("options must be an object.");
11218
11429
  const warnings = checkOptionKeys(options);
11219
- const workspaceRoot = path22.resolve(options.workspaceRoot ?? process.cwd());
11220
- const browser = path22.resolve(workspaceRoot, text4(options.browser, "browser"));
11221
- const server = options.server === void 0 ? void 0 : path22.resolve(workspaceRoot, text4(options.server, "server"));
11222
- const sourceRoot = path22.resolve(workspaceRoot, options.sourceRoot ?? path22.dirname(browser));
11430
+ const workspaceRoot = path23.resolve(options.workspaceRoot ?? process.cwd());
11431
+ const browser = path23.resolve(workspaceRoot, text4(options.browser, "browser"));
11432
+ const server = options.server === void 0 ? void 0 : path23.resolve(workspaceRoot, text4(options.server, "server"));
11433
+ const sourceRoot = path23.resolve(workspaceRoot, options.sourceRoot ?? path23.dirname(browser));
11223
11434
  const polyfills = (options.polyfills ?? []).map(
11224
11435
  (item, index) => specifier(workspaceRoot, text4(item, `polyfills[${index}]`))
11225
11436
  );
11226
11437
  const styles = (options.styles ?? []).map(
11227
- (item, index) => posix(path22.resolve(workspaceRoot, text4(item, `styles[${index}]`)))
11438
+ (item, index) => posix(path23.resolve(workspaceRoot, text4(item, `styles[${index}]`)))
11228
11439
  );
11229
11440
  const assetPatterns = [...options.assets ?? []];
11441
+ const zone = (options.polyfills ?? []).some(
11442
+ (item) => item === "zone.js" || item.startsWith("zone.js/")
11443
+ );
11444
+ let viteRoot = process.cwd();
11445
+ let demoApi;
11446
+ let demo;
11447
+ const demoPage = () => posix(path23.join(viteRoot, NG_DOC_DEMO_PAGE));
11448
+ const currentDemo = async () => config?.command === "build" ? demo : await demoApi?.current().catch(() => void 0);
11230
11449
  let assets;
11231
11450
  const resolvedAssets = () => assets ??= resolveAssets(assetPatterns, workspaceRoot, sourceRoot);
11232
11451
  let config;
11233
- const developmentPrebundles = async (viteRoot) => ({
11452
+ const developmentPrebundles = async (viteRoot2) => ({
11234
11453
  include: [
11235
11454
  .../* @__PURE__ */ new Set([
11236
- ...await coreImports(viteRoot),
11237
- ...polyfills.filter((item) => !path22.isAbsolute(item)),
11238
- ...resolvable(viteRoot, "tslib") ? ["tslib"] : []
11455
+ ...await coreImports(viteRoot2),
11456
+ ...polyfills.filter((item) => !path23.isAbsolute(item)),
11457
+ ...resolvable(viteRoot2, "tslib") ? ["tslib"] : []
11239
11458
  ])
11240
11459
  ].sort(),
11241
- entries: [...polyfills.filter((item) => path22.isAbsolute(item)), posix(browser)].map(
11242
- (file) => escapePath(posix(path22.relative(viteRoot, file)))
11460
+ entries: [...polyfills.filter((item) => path23.isAbsolute(item)), posix(browser)].map(
11461
+ (file) => escapePath(posix(path23.relative(viteRoot2, file)))
11243
11462
  )
11244
11463
  });
11245
11464
  const api = Object.freeze({
@@ -11251,6 +11470,7 @@ function createNgDocApplicationPlugin(options) {
11251
11470
  enforce: "pre",
11252
11471
  api: { ngDocApplication: api },
11253
11472
  async config(user, environment) {
11473
+ viteRoot = path23.resolve(user.root ?? process.cwd());
11254
11474
  return {
11255
11475
  resolve: { dedupe: ANGULAR_DEDUPE },
11256
11476
  // Every `vite build` is a production build of the application (the Angular CLI's
@@ -11260,7 +11480,7 @@ function createNgDocApplicationPlugin(options) {
11260
11480
  ["ngDevMode", "ngJitMode"].filter((name) => !(user.define && name in user.define)).map((name) => [name, "false"])
11261
11481
  )
11262
11482
  } : {},
11263
- ...environment.command === "serve" ? { optimizeDeps: await developmentPrebundles(path22.resolve(user.root ?? process.cwd())) } : {},
11483
+ ...environment.command === "serve" ? { optimizeDeps: await developmentPrebundles(viteRoot) } : {},
11264
11484
  // The server bundle is imported by a plain Node process: it must hold every dependency,
11265
11485
  // partially compiled Angular libraries included.
11266
11486
  ...environment.command === "build" && environment.isSsrBuild ? { ssr: { noExternal: true } } : {}
@@ -11268,11 +11488,42 @@ function createNgDocApplicationPlugin(options) {
11268
11488
  },
11269
11489
  configResolved(resolved) {
11270
11490
  config = resolved;
11491
+ viteRoot = resolved.root ?? viteRoot;
11492
+ const apis = (resolved.plugins ?? []).map((plugin) => plugin.api?.ngDocDemoApplication).filter((api2) => api2?.schemaVersion === 1);
11493
+ demoApi = apis.length === 1 ? apis[0] : void 0;
11271
11494
  for (const warning of warnings) resolved.logger.warn(warning);
11272
11495
  },
11496
+ // A build with demo pages builds the demo application too: its page is a second input of the
11497
+ // browser build and its server entry a second input of the server build. Whether there are
11498
+ // demo pages is known once NgDoc has generated, so the NgDoc plugin generates here, before
11499
+ // the build starts, instead of in its `buildStart`.
11500
+ async options(input) {
11501
+ if (config?.command !== "build" || !demoApi) return null;
11502
+ demo = await demoApi.resolve();
11503
+ if (!demo) return null;
11504
+ return {
11505
+ ...input,
11506
+ input: config.build.ssr ? withDemoInput(
11507
+ input.input,
11508
+ NG_DOC_DEMO_SERVER_NAME,
11509
+ NG_DOC_SERVER_ENTRY,
11510
+ NG_DOC_DEMO_SERVER_ENTRY
11511
+ ) : withDemoInput(
11512
+ input.input ?? path23.join(viteRoot, "index.html"),
11513
+ "ng-doc-demo",
11514
+ posix(path23.join(viteRoot, "index.html")),
11515
+ demoPage()
11516
+ )
11517
+ };
11518
+ },
11273
11519
  resolveId(source, importer, resolveOptions2) {
11274
11520
  if (source === NG_DOC_BROWSER_ENTRY) return BROWSER_ID;
11275
- if (importer === BROWSER_ID && config?.command === "build") {
11521
+ if (source === NG_DOC_DEMO_ENTRY) return DEMO_ID;
11522
+ if (source === NG_DOC_DEMO_SERVER_ENTRY) return DEMO_SERVER_ID;
11523
+ if (config?.command === "build" && demo && posix(path23.resolve(viteRoot, source)) === demoPage()) {
11524
+ return demoPage();
11525
+ }
11526
+ if ((importer === BROWSER_ID || importer === DEMO_ID) && config?.command === "build") {
11276
11527
  const { isEntry, custom } = resolveOptions2;
11277
11528
  return this.resolve(source, importer, { isEntry, custom, skipSelf: true }).then(
11278
11529
  (resolved) => resolved && !resolved.external ? { ...resolved, moduleSideEffects: true } : resolved
@@ -11287,6 +11538,25 @@ function createNgDocApplicationPlugin(options) {
11287
11538
  return SERVER_ID;
11288
11539
  },
11289
11540
  load(id) {
11541
+ if (config?.command === "build" && demo && id === demoPage()) {
11542
+ const index = path23.join(viteRoot, "index.html");
11543
+ this.addWatchFile(index);
11544
+ return readFile8(index, "utf8").then(demoDocument);
11545
+ }
11546
+ if (id === DEMO_ID || id === DEMO_SERVER_ID) {
11547
+ return currentDemo().then((current) => {
11548
+ if (!current) {
11549
+ throw new Error(
11550
+ "[NGDOC_VITE_DEMO_APPLICATION] The demo application was requested, but NgDoc generated no demo pages."
11551
+ );
11552
+ }
11553
+ return demoEntry(id === DEMO_SERVER_ID ? "server" : "browser", current.module, {
11554
+ polyfills: id === DEMO_SERVER_ID ? serverPolyfills(options.polyfills ?? []) : polyfills,
11555
+ styles: id === DEMO_SERVER_ID ? [] : styles,
11556
+ zone
11557
+ });
11558
+ });
11559
+ }
11290
11560
  if (id === BROWSER_ID) {
11291
11561
  return [...polyfills, ...styles, posix(browser)].map((item) => `import ${JSON.stringify(item)};`).join("\n");
11292
11562
  }
@@ -11318,6 +11588,7 @@ function createNgDocApplicationPlugin(options) {
11318
11588
  transformIndexHtml: {
11319
11589
  order: "pre",
11320
11590
  async handler(html, context) {
11591
+ const demoPageHtml = html.includes(DEMO_ROOT);
11321
11592
  return {
11322
11593
  html: withBase(html, config?.base ?? "/"),
11323
11594
  tags: [
@@ -11326,7 +11597,10 @@ function createNgDocApplicationPlugin(options) {
11326
11597
  ...context.server ? await ngDocDevStyleTags(context.server, styles) : [],
11327
11598
  {
11328
11599
  tag: "script",
11329
- attrs: { type: "module", src: NG_DOC_BROWSER_ENTRY },
11600
+ attrs: {
11601
+ type: "module",
11602
+ src: demoPageHtml ? NG_DOC_DEMO_ENTRY : NG_DOC_BROWSER_ENTRY
11603
+ },
11330
11604
  injectTo: "body"
11331
11605
  }
11332
11606
  ]
@@ -11335,6 +11609,43 @@ function createNgDocApplicationPlugin(options) {
11335
11609
  },
11336
11610
  configureServer(server2) {
11337
11611
  server2.middlewares.use(assetMiddleware(resolvedAssets, () => server2.config.base));
11612
+ server2.middlewares.use((request, response, next2) => {
11613
+ const accept = request.headers.accept;
11614
+ const url = request.url ?? "/";
11615
+ if (request.method !== "GET" || !(Array.isArray(accept) ? accept.join(",") : accept ?? "").includes("text/html")) {
11616
+ next2();
11617
+ return;
11618
+ }
11619
+ void currentDemo().then(async (current) => {
11620
+ const prefix = `${server2.config.base.replace(/\/?$/, "/")}${current?.path}/`;
11621
+ if (!current || !url.split(/[?#]/, 1)[0].startsWith(prefix)) {
11622
+ next2();
11623
+ return;
11624
+ }
11625
+ const index = await readFile8(path23.join(viteRoot, "index.html"), "utf8");
11626
+ const html = await server2.transformIndexHtml(
11627
+ url,
11628
+ demoDocument(index),
11629
+ request.originalUrl
11630
+ );
11631
+ response.statusCode = 200;
11632
+ response.setHeader("Content-Type", "text/html");
11633
+ response.end(html);
11634
+ }).catch((error2) => next2(error2));
11635
+ });
11636
+ },
11637
+ // Every demo page is a copy of the demo application's page, so a static host without a
11638
+ // fallback serves it, as with hash URLs (which are built without prerendering). Prerendering
11639
+ // replaces each copy with the rendered page.
11640
+ async writeBundle(output) {
11641
+ if (config?.command !== "build" || config.build.ssr || !demo || !output.dir) return;
11642
+ const page = await readFile8(path23.join(output.dir, NG_DOC_DEMO_PAGE), "utf8");
11643
+ for (const route of [...demo.pages].sort()) {
11644
+ if (unsafeSegment(route)) continue;
11645
+ const file = path23.join(output.dir, ...route.split("/"), "index.html");
11646
+ await mkdir2(path23.dirname(file), { recursive: true });
11647
+ await writeFile2(file, page);
11648
+ }
11338
11649
  },
11339
11650
  async generateBundle(_output, bundle) {
11340
11651
  if (config?.command !== "build" || config.build.ssr) return;
@@ -11344,7 +11655,7 @@ function createNgDocApplicationPlugin(options) {
11344
11655
  if (bundle[fileName]) {
11345
11656
  throw new Error(`[NGDOC_VITE_ASSET_COLLISION] Vite bundle already owns ${fileName}.`);
11346
11657
  }
11347
- this.emitFile({ type: "asset", fileName, source: await readFile7(file) });
11658
+ this.emitFile({ type: "asset", fileName, source: await readFile8(file) });
11348
11659
  }
11349
11660
  }
11350
11661
  };
@@ -11354,7 +11665,7 @@ function assetMiddleware(assets, base) {
11354
11665
  void assets().then(
11355
11666
  async (resolved) => {
11356
11667
  for (const match of matchAssets(resolved, base(), request.url ?? "/")) {
11357
- const source = await readFile7(match.file).catch(() => void 0);
11668
+ const source = await readFile8(match.file).catch(() => void 0);
11358
11669
  if (!source) continue;
11359
11670
  response.statusCode = 200;
11360
11671
  response.setHeader("Content-Type", contentType(match.relative));
@@ -11367,6 +11678,50 @@ function assetMiddleware(assets, base) {
11367
11678
  );
11368
11679
  };
11369
11680
  }
11681
+ function demoEntry(platform, module, options) {
11682
+ const imports = [...options.polyfills, ...options.styles].map(
11683
+ (item) => `import ${JSON.stringify(item)};`
11684
+ );
11685
+ const application = [
11686
+ `import { bootstrapApplication } from '@angular/platform-browser';`,
11687
+ `import { NgDocDemoAppComponent, \u0275ngDocDemoApplicationConfig } from '@ng-doc/app/demo-app';`,
11688
+ `import { NG_DOC_DEMO_PROVIDERS, NG_DOC_DEMO_ROUTES } from ${JSON.stringify(posix(module))};`
11689
+ ];
11690
+ if (platform === "browser") {
11691
+ return [
11692
+ ...imports,
11693
+ ...application,
11694
+ // A page the server rendered carries the serialized state (`<script id="ng-state">`, the
11695
+ // default APP_ID); the other pages (the copies of the demo page, the development server's)
11696
+ // start without hydration, as Angular warns (NG0505) when it finds no state.
11697
+ `\u0275ngDocDemoApplicationConfig(NG_DOC_DEMO_ROUTES, NG_DOC_DEMO_PROVIDERS, {`,
11698
+ ` zone: ${options.zone},`,
11699
+ ` hydration: document.getElementById('ng-state') !== null,`,
11700
+ `})`,
11701
+ ` .then((config) => bootstrapApplication(NgDocDemoAppComponent, config))`,
11702
+ ` .catch((error) => console.error(error));`
11703
+ ].join("\n");
11704
+ }
11705
+ return [
11706
+ ...imports,
11707
+ ...application,
11708
+ `import { provideServerRendering } from '@angular/platform-server';`,
11709
+ `export const bootstrap = async (context) =>`,
11710
+ ` bootstrapApplication(`,
11711
+ ` NgDocDemoAppComponent,`,
11712
+ ` await \u0275ngDocDemoApplicationConfig(NG_DOC_DEMO_ROUTES, NG_DOC_DEMO_PROVIDERS, {`,
11713
+ ` zone: ${options.zone},`,
11714
+ ` hydration: true,`,
11715
+ ` providers: [provideServerRendering()],`,
11716
+ ` }),`,
11717
+ ` context,`,
11718
+ ` );`,
11719
+ `export default bootstrap;`,
11720
+ `export { renderApplication } from '@angular/platform-server';`,
11721
+ `export { Router } from '@angular/router';`,
11722
+ `export { runInInjectionContext } from '@angular/core';`
11723
+ ].join("\n");
11724
+ }
11370
11725
 
11371
11726
  // libs/builder/generator/vite/prerender.ts
11372
11727
  import { fork as fork3 } from "node:child_process";
@@ -11385,7 +11740,8 @@ function prerenderNgDoc(options) {
11385
11740
  serverEntry: options.serverEntry,
11386
11741
  routes: [...options.routes ?? []],
11387
11742
  discoverRoutes: options.discoverRoutes ?? true,
11388
- ...options.routeTimeoutMs === void 0 ? {} : { routeTimeoutMs: options.routeTimeoutMs }
11743
+ ...options.routeTimeoutMs === void 0 ? {} : { routeTimeoutMs: options.routeTimeoutMs },
11744
+ ...options.demoServerEntry ? { demoServerEntry: options.demoServerEntry } : {}
11389
11745
  };
11390
11746
  if (request.routeTimeoutMs !== void 0 && (!Number.isSafeInteger(request.routeTimeoutMs) || request.routeTimeoutMs < 1)) {
11391
11747
  return Promise.reject(
@@ -11436,8 +11792,9 @@ function prerenderNgDoc(options) {
11436
11792
  }
11437
11793
 
11438
11794
  // libs/builder/generator/vite/production.ts
11439
- import { rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
11440
- import path23 from "node:path";
11795
+ import { existsSync as existsSync7 } from "node:fs";
11796
+ import { rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
11797
+ import path24 from "node:path";
11441
11798
  import { build as viteBuild } from "vite";
11442
11799
  var defaultDependencies = {
11443
11800
  build: viteBuild,
@@ -11474,11 +11831,11 @@ function applicationProbe() {
11474
11831
  async function buildNgDocViteApplication(options, dependencies = defaultDependencies) {
11475
11832
  assertSupportedViteVersion();
11476
11833
  assertConsistentAngularBuild();
11477
- const configFile = path23.resolve(options.configFile);
11478
- const outputPath = path23.resolve(options.outputPath);
11479
- const browser = path23.join(outputPath, "browser");
11480
- const serverDirectory = path23.join(outputPath, "server");
11481
- const serverEntry = path23.join(serverDirectory, "server.mjs");
11834
+ const configFile = path24.resolve(options.configFile);
11835
+ const outputPath = path24.resolve(options.outputPath);
11836
+ const browser = path24.join(outputPath, "browser");
11837
+ const serverDirectory = path24.join(outputPath, "server");
11838
+ const serverEntry = path24.join(serverDirectory, "server.mjs");
11482
11839
  const mode = options.mode ?? "production";
11483
11840
  const log = options.log ?? (() => {
11484
11841
  });
@@ -11515,7 +11872,7 @@ async function buildNgDocViteApplication(options, dependencies = defaultDependen
11515
11872
  ssr ??= entry !== void 0;
11516
11873
  if (!ssr) {
11517
11874
  await rm2(serverDirectory, { recursive: true, force: true });
11518
- await rm2(path23.join(outputPath, "prerendered-routes.json"), { force: true });
11875
+ await rm2(path24.join(outputPath, "prerendered-routes.json"), { force: true });
11519
11876
  if (options.prerender === true) throw noServer();
11520
11877
  } else {
11521
11878
  if (!entry) {
@@ -11550,23 +11907,26 @@ async function buildNgDocViteApplication(options, dependencies = defaultDependen
11550
11907
  }
11551
11908
  const prerender = options.prerender ?? (options.skipBuild ? true : ssr === true);
11552
11909
  if (!prerender) return { browser, ...ssr ? { server: serverEntry } : {} };
11910
+ const demoServerEntry = path24.join(serverDirectory, "demo-server.mjs");
11553
11911
  const report = await timed(
11554
11912
  "Prerendered the routes",
11555
11913
  () => dependencies.prerender({
11556
11914
  browserDir: browser,
11557
11915
  serverEntry,
11916
+ ...existsSync7(demoServerEntry) && existsSync7(path24.join(browser, "ng-doc-demo.html")) ? { demoServerEntry } : {},
11558
11917
  routes: options.routes ?? [],
11559
11918
  discoverRoutes: options.discoverRoutes ?? true,
11560
11919
  ...options.routeTimeoutMs === void 0 ? {} : { routeTimeoutMs: options.routeTimeoutMs },
11561
11920
  ...options.signal ? { signal: options.signal } : {}
11562
11921
  })
11563
11922
  );
11564
- await writeFile2(
11565
- path23.join(outputPath, "prerendered-routes.json"),
11923
+ await writeFile3(
11924
+ path24.join(outputPath, "prerendered-routes.json"),
11566
11925
  `${JSON.stringify({ routes: report.routes, excluded: report.excluded }, null, 2)}
11567
11926
  `
11568
11927
  );
11569
11928
  log(`Prerendered ${report.routes.length} route(s) into ${browser}`);
11929
+ for (const { route, message: message4 } of report.warnings ?? []) log(`Warning: ${route}: ${message4}`);
11570
11930
  if (report.errors.length) {
11571
11931
  log(`The application logged ${report.errors.length} error(s) while prerendering:`);
11572
11932
  for (const { route, message: message4 } of report.errors.slice(0, 10)) log(` ${route}: ${message4}`);