@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
@@ -151,6 +151,12 @@ export interface RemoteKeywordSnapshot {
151
151
  /** Optional upstream validator is evidence, never an assumed lifetime guarantee. */
152
152
  validator?: string;
153
153
  }
154
+ /** A Shiki (TextMate) language registration of `shiki.langs`: a grammar as plain JSON. */
155
+ export interface ShikiLanguage {
156
+ readonly name: string;
157
+ readonly scopeName: string;
158
+ readonly [key: string]: JsonValue;
159
+ }
154
160
  export interface GeneratorConfiguration {
155
161
  projectId: ProjectId;
156
162
  workspaceRoot: FilePath;
@@ -166,10 +172,35 @@ export interface GeneratorConfiguration {
166
172
  inlineStyleLanguage: 'CSS' | 'SCSS' | 'SASS' | 'LESS';
167
173
  anchorHeadings: Array<'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'>;
168
174
  headerTemplate?: FilePath;
175
+ /**
176
+ * `false` when `api.protectedMembers` is `false`: API templates then list only public class
177
+ * members. Absent otherwise, so every other configuration keeps its digest.
178
+ */
179
+ apiProtectedMembers?: false;
180
+ /**
181
+ * The demo pages (`isolatedDemos`, `demoApplication`, `demoProviders`). Absent when none of them
182
+ * is set, so every other configuration keeps its digest, and so is every default field:
183
+ * - `pages`: `all` builds a demo page for every demo, `none` for none; absent, only the guides
184
+ * with an isolated demo get them.
185
+ * - `isolated`: `true` when `isolatedDemos` is: every `demo` action shows an iframe by default.
186
+ * - `path`: the URL path of the demo pages, absent for `demo-preview`.
187
+ * - `providers`: the module that `demoProviders` imports.
188
+ */
189
+ demoApplication?: {
190
+ pages?: 'all' | 'none';
191
+ isolated?: true;
192
+ path?: string;
193
+ providers?: FilePath;
194
+ };
169
195
  themes: {
170
196
  light: string;
171
197
  dark: string;
172
198
  };
199
+ /**
200
+ * The Shiki language registrations of `shiki.langs`, flattened, as plain JSON in configuration
201
+ * order. Absent without any, so every other configuration keeps its digest.
202
+ */
203
+ shikiLangs?: ShikiLanguage[];
173
204
  repo?: {
174
205
  url: string;
175
206
  platform: 'github' | 'gitlab';
@@ -608,6 +639,14 @@ export interface RouteRecord {
608
639
  * for the root list), which its route carries as `ngDocApiListSegment` data.
609
640
  */
610
641
  apiListSegment?: string;
642
+ /**
643
+ * The guide's demo routes module (relative to the output root), when the guide has demo pages.
644
+ * The aggregate lists it in `demo-app.ts`. Optional and only written for such guides, so a cache
645
+ * written without it stays valid: every other route record is unchanged.
646
+ */
647
+ demoModulePath?: string;
648
+ /** The names of the guide's demo pages, in the order of `demos`, with `demoModulePath`. */
649
+ demoNames?: string[];
611
650
  }
612
651
  export interface ApiListRecord {
613
652
  apiEntryId: EntryId;
@@ -69,6 +69,8 @@ export declare function createDiscoveryServices(options?: DiscoveryOptions): {
69
69
  values: GuideValueReader;
70
70
  runtime: DiscoveryServiceImpl;
71
71
  };
72
+ /** The default URL path of the demo pages. */
73
+ export declare const DEFAULT_DEMO_PATH = "demo-preview";
72
74
  /**
73
75
  * The `evaluated` digest of every entry of a discovery snapshot: the refresh source of the
74
76
  * `evaluated` dependencies a generation recorded against it.
@@ -34,7 +34,9 @@ export declare const TRACKED_PROGRAM_REUSE_FLAG = "NGDOC_TRACKED_PROGRAM_REUSE";
34
34
  export declare const FAST_START_FLAG = "NGDOC_FAST_START";
35
35
  export declare const PARALLEL_WRITES_FLAG = "NGDOC_PARALLEL_WRITES";
36
36
  export declare const HIGHLIGHT_CACHE_FLAG = "NGDOC_HIGHLIGHT_CACHE";
37
+ export declare const FORMAT_CACHE_FLAG = "NGDOC_FORMAT_CACHE";
37
38
  export declare const PARALLEL_RENDER_FLAG = "NGDOC_PARALLEL_RENDER";
39
+ export declare const USED_GRAMMARS_FLAG = "NGDOC_USED_GRAMMARS";
38
40
  /** The registry, in the order the engine applies the switches. */
39
41
  export declare const FLAGS: readonly FlagDefinition[];
40
42
  /**
@@ -56,10 +56,23 @@ export declare class OutputAssemblerImpl implements OutputAssembler {
56
56
  assemblePage(request: PageAssemblyRequest): PageAssembly;
57
57
  aggregate(request: AggregateRequest): AggregateAssembly;
58
58
  private guide;
59
+ /**
60
+ * Writes the guide's demo routes module (one route per demo, under the page's own providers)
61
+ * when the guide gets demo pages: with `pages: 'all'`, or by default when its content has a demo
62
+ * with `isolated: true`. Returns its path and the names of the demos that got a page, or
63
+ * undefined.
64
+ */
65
+ private demoRoutes;
59
66
  private apiEntry;
60
67
  private apiDeclaration;
61
68
  private apiLists;
62
69
  private addContentSource;
70
+ /**
71
+ * Writes `demo-app.ts`, the routes of the demo application (one lazy route per guide with demo
72
+ * pages, under its categories' providers) and its `demoProviders` import, when a guide has demo
73
+ * pages. Returns whether it did.
74
+ */
75
+ private demoApplication;
63
76
  private renderOutput;
64
77
  }
65
78
  export declare function createOutputAssembler(options?: {
@@ -2,8 +2,28 @@ import { type ObjectLiteralExpression, type SourceFile, Node, ts } from 'ts-morp
2
2
  import type { GeneratorConfiguration, GuideSemantics, JsonValue } from '../contracts.js';
3
3
  import { type TrackedFiles } from './dependencies.js';
4
4
  import type { JsDoc } from './rendering.js';
5
- /** Resolves wrappers and local constants without executing source code. */
5
+ /**
6
+ * Resolves wrappers and local constants without executing source code.
7
+ * @param node
8
+ * @param seen
9
+ */
6
10
  export declare function unwrap(node: Node | undefined, seen?: Set<Node<ts.Node>>): Node | undefined;
11
+ /**
12
+ *
13
+ * @param source
14
+ */
7
15
  export declare function entryObject(source: SourceFile): ObjectLiteralExpression;
16
+ /**
17
+ *
18
+ * @param node
19
+ */
8
20
  export declare function literal(node: Node | undefined): JsonValue;
21
+ /**
22
+ *
23
+ * @param source
24
+ * @param files
25
+ * @param config
26
+ * @param docs
27
+ * @param values
28
+ */
9
29
  export declare function guideSemantics(source: SourceFile, files: TrackedFiles, config: GeneratorConfiguration, docs: JsDoc, values?: JsonValue): GuideSemantics;
@@ -0,0 +1,27 @@
1
+ import type { Options } from 'prettier';
2
+ /**
3
+ * The cache `formatCode` consults: `FormatCodeCache` of `helpers/format-cache.ts`, restated here
4
+ * because the published declarations reach this module and cannot import from outside the
5
+ * generator. TypeScript checks that the two agree where `formatting` installs it.
6
+ */
7
+ export interface GenerationFormatCache {
8
+ config(configDirectory: string | undefined, resolve: () => Options | null): Options | null;
9
+ format(input: {
10
+ code: string;
11
+ parser: string;
12
+ config: Options | null;
13
+ }, format: () => string): string;
14
+ }
15
+ /**
16
+ * Makes `cache` the one {@link formatting} installs (undefined: none). A runtime compiles one
17
+ * generation at a time; the compiler sets it when it plans a generation and clears it when the
18
+ * compile ends, so a later generation never sees an earlier one's resolved configuration.
19
+ * @param cache The generation's cache (`content/format-cache.ts`).
20
+ */
21
+ export declare function useFormatCache(cache: GenerationFormatCache | undefined): void;
22
+ /**
23
+ * Runs synchronous formatting work (API templates, demo snippets) with the current generation's
24
+ * format cache, if any.
25
+ * @param run The work; `formatCode` calls it makes consult the cache.
26
+ */
27
+ export declare function formatting<T>(run: () => T): T;
@@ -22,7 +22,16 @@ export interface FragmentContext extends QueryContext {
22
22
  /** The API template root (the option, or the shipped templates). */
23
23
  templateRoot(): string;
24
24
  }
25
- /** The recorder scope of a fragment request: its phase kind and its unit key. */
25
+ /**
26
+ * The recorder scope of a fragment request: its phase kind and its unit key.
27
+ * @param request
28
+ */
26
29
  export declare function fragmentScope(request: SemanticFragmentRequest): [FootprintScopeKind, string];
27
- /** Renders one fragment request against the snapshot, recording its reads in `files`. */
30
+ /**
31
+ * Renders one fragment request against the snapshot, recording its reads in `files`.
32
+ * @param state
33
+ * @param request
34
+ * @param files
35
+ * @param context
36
+ */
28
37
  export declare function renderFragment(state: Snapshot, request: SemanticFragmentRequest, files: TrackedFiles, context: FragmentContext): SemanticFragment;
@@ -7,10 +7,12 @@
7
7
  {% import "helpers/modifier.html.nunj" as modifierMacro %}
8
8
 
9
9
  {% block overview %}
10
+ {#- The new engine sets `hideProtectedMembers` for `api.protectedMembers: false`; the legacy engine never does. #}
11
+ {%- set memberScopes = ["public"] if hideProtectedMembers else ["public", "protected"] -%}
10
12
  {%- set constructors = declaration.getConstructors() | filterByScope("public") | excludeByJsDocTags('internal') -%}
11
- {%- set allProperties = declaration | getClassProperties | excludeByJsDocTags('internal') | filterByScope(["public", "protected"]) | filterUselessMembers | sortByNodesName -%}
12
- {%- set allMethods = declaration | getClassMethods | excludeByJsDocTags('internal') | filterByScope(["public", "protected"]) | filterUselessMembers | sortByNodesName -%}
13
- {%- set allAccessors = declaration | getClassAccessors | excludeByJsDocTags('internal') | filterByScope(["public", "protected"]) | filterUselessMembers | sortByNodesName -%}
13
+ {%- set allProperties = declaration | getClassProperties | excludeByJsDocTags('internal') | filterByScope(memberScopes) | filterUselessMembers | sortByNodesName -%}
14
+ {%- set allMethods = declaration | getClassMethods | excludeByJsDocTags('internal') | filterByScope(memberScopes) | filterUselessMembers | sortByNodesName -%}
15
+ {%- set allAccessors = declaration | getClassAccessors | excludeByJsDocTags('internal') | filterByScope(memberScopes) | filterUselessMembers | sortByNodesName -%}
14
16
 
15
17
  {%- set staticProperties = allProperties | filterByStatic(true) -%}
16
18
  {%- set staticAccessors = allAccessors | filterByStatic(true) -%}
@@ -0,0 +1,37 @@
1
+ // @ts-nocheck
2
+ import {Routes} from '@angular/router';
3
+ {%- for category in categories %}
4
+ import {{ category[0] }} from '{{ category[1] }}';
5
+ {%- endfor %}
6
+
7
+ // The URL path of the demo pages, which the Vite host serves the demo application's page under.
8
+ export const NG_DOC_DEMO_PATH = '{{ demoPath }}';
9
+
10
+ // Every demo page, which a build without prerendering writes as a copy of the demo application's
11
+ // page, so that static hosts serve it without a fallback.
12
+ export const NG_DOC_DEMO_PAGES: string[] = {{ demoPages | dump }};
13
+
14
+ // The routes of the demo application: one lazy route per guide with demo pages, under the
15
+ // providers of its categories, as on the documentation pages.
16
+ export const NG_DOC_DEMO_ROUTES: Routes = [
17
+ {%- for page in pages %}
18
+ {
19
+ path: '{{ page.path | replace("\\", "\\\\") | replace("'", "\\'") | replace("\n", "\\n") }}',
20
+ {%- if page.categories.length %}
21
+ providers: [
22
+ {%- for id in page.categories %}
23
+ ...({{ id }}.providers ?? []),
24
+ {%- endfor %}
25
+ ],
26
+ {%- endif %}
27
+ loadChildren: () => import('{{ page.importPath }}'),
28
+ },
29
+ {%- endfor %}
30
+ ];
31
+
32
+ // The module of `demoProviders`, which only the demo application imports.
33
+ {%- if providersImport %}
34
+ export const NG_DOC_DEMO_PROVIDERS = () => import('{{ providersImport }}');
35
+ {%- else %}
36
+ export const NG_DOC_DEMO_PROVIDERS = undefined;
37
+ {%- endif %}
@@ -0,0 +1,23 @@
1
+ // @ts-nocheck
2
+ import {Routes} from '@angular/router';
3
+ import {NgDocDemoHostComponent} from '@ng-doc/app/demo-app';
4
+ import entry from '{{ entryImport }}';
5
+
6
+ // The demo pages of one guide: one route per demo, under the page's providers, as the demos have
7
+ // them on the page.
8
+ const routes: Routes = [{
9
+ path: '',
10
+ providers: [...(entry.providers ?? [])],
11
+ children: [
12
+ {%- for demo in demos %}
13
+ {
14
+ path: '{{ demo.name }}',
15
+ component: NgDocDemoHostComponent,
16
+ title: `{{ demo.title | toTemplateString }}`,
17
+ data: {ngDocDemo: entry.demos?.['{{ demo.name }}']},
18
+ },
19
+ {%- endfor %}
20
+ ],
21
+ }];
22
+
23
+ export default routes;
@@ -1,2 +1,7 @@
1
1
  export * from './context';
2
2
  export * from './routes';
3
+ {% if demoApplication -%}
4
+ // Keeps the demo application's modules, the demo routes and the module of `demoProviders`, in the
5
+ // application's Angular compilation, which compiles them: only the demo application imports them.
6
+ import type {} from './demo-app';
7
+ {% endif -%}
@@ -42,7 +42,11 @@ const routes: Routes = [{
42
42
  {%- endif %}
43
43
  path: '',
44
44
  component: PageWrapperComponent,
45
- title: `{{ metadata.title | toTemplateString }}`,
45
+ {#- No title: the route inherits the page's title from the route that loads it, and a second
46
+ copy of it would repeat it in a title strategy that joins the titles of the route. #}
47
+ data: {
48
+ ngDocPageType: '{{ pageType }}',
49
+ },
46
50
  children: [
47
51
  {%- for importPath, entry in entries %}
48
52
  {
@@ -51,6 +55,7 @@ const routes: Routes = [{
51
55
  title: `{{ entry.title | toTemplateString }}`,
52
56
  data: {
53
57
  icon: '{{ entry.entry.icon }}',
58
+ ngDocTitle: 'tab',
54
59
  }
55
60
  },
56
61
  {%- endfor %}
@@ -64,11 +64,20 @@ export class PageComponent extends NgDocRootPage {
64
64
  {%- if demoAssetsPath %}
65
65
  readonly demoAssets: NgDocDemoAssets = demoAssets;
66
66
  {%- endif %}
67
+ {#- The demo pages of the guide (Vite engine), and whether its demos are isolated by default. #}
68
+ {%- if demoRoute %}
69
+ readonly demoRoute: string = '{{ demoRoute | replace("\\", "\\\\") | replace("'", "\\'") | replace("\n", "\\n") }}';
70
+ {%- if isolatedDemos %}
71
+ readonly isolatedDemos: boolean = true;
72
+ {%- endif %}
73
+ {%- endif %}
67
74
 
68
75
  constructor() {
69
76
  super();
70
77
  }
71
78
  }
79
+ {#- The route has no title of its own: it inherits the title of its tab route, so a page's own
80
+ `route.title` is the only title here and wins over the generated one. #}
72
81
  {%- if contentSourceImport %}
73
82
 
74
83
  // The router shows the page only once its content has loaded, so the previous page stays on
@@ -79,7 +88,6 @@ const routes: Routes = [ɵwithNgDocContent({
79
88
  {%- endif %}
80
89
  path: '',
81
90
  component: PageComponent,
82
- title: `{{ metadata.title | toTemplateString }}`,
83
91
  }, pageContentSource)]
84
92
  {%- else %}
85
93
 
@@ -89,7 +97,6 @@ const routes: Routes = [{
89
97
  {%- endif %}
90
98
  path: '',
91
99
  component: PageComponent,
92
- title: `{{ metadata.title | toTemplateString }}`,
93
100
  }]
94
101
  {%- endif %}
95
102
 
@@ -20,9 +20,13 @@ export const NG_DOC_ROUTING: Routes = [
20
20
  {
21
21
  path: '{{ entry.item.route | replace("\\", "\\\\") | replace("'", "\\'") | replace("\n", "\\n") }}',
22
22
  title: `{{ entry.item.title | toTemplateString }}`,
23
- {%- if entry.item.apiListSegment is defined %}
24
- data: {ngDocApiListSegment: '{{ entry.item.apiListSegment | replace("\\", "\\\\") | replace("'", "\\'") | replace("\n", "\\n") }}'},
25
- {%- endif %}
23
+ {#- `ngDocTitle` names what the title belongs to, so a title strategy can compose the titles of
24
+ the route without repeating one (`provideNgDocTitle`). #}
25
+ data: {ngDocTitle: '{{ 'category' if entry.item.isCategory else 'page' }}'
26
+ {%- if entry.item.apiListSegment is defined -%}
27
+ , ngDocApiListSegment: '{{ entry.item.apiListSegment | replace("\\", "\\\\") | replace("'", "\\'") | replace("\n", "\\n") }}'
28
+ {%- endif -%}
29
+ },
26
30
  {%- if entry.item.isCategory %}
27
31
  providers: [
28
32
  ...({{ entry.item.id }}.providers ?? []),
@@ -197,12 +197,14 @@
197
197
  </tr>
198
198
  {%- endmacro %}
199
199
 
200
- {% macro render(declaration) %}
200
+ {#- `hideProtected` (the `api.protectedMembers: false` option) lists public class members only. #}
201
+ {% macro render(declaration, hideProtected=false) %}
202
+ {%- set memberScopes = ["public"] if hideProtected else ["public", "protected"] -%}
201
203
  {%- if Node.isClassDeclaration(declaration) %}
202
204
  {%- set constructors = declaration.getConstructors() | filterByScope("public") | excludeByJsDocTags('internal') -%}
203
- {%- set allProperties = declaration | getClassProperties | excludeByJsDocTags('internal') | filterByScope(["public", "protected"]) | filterUselessMembers | sortByNodesName -%}
204
- {%- set allMethods = declaration | getClassMethods | excludeByJsDocTags('internal') | filterByScope(["public", "protected"]) | filterUselessMembers | sortByNodesName -%}
205
- {%- set allAccessors = declaration | getClassAccessors | excludeByJsDocTags('internal') | filterByScope(["public", "protected"]) | filterUselessMembers | sortByNodesName -%}
205
+ {%- set allProperties = declaration | getClassProperties | excludeByJsDocTags('internal') | filterByScope(memberScopes) | filterUselessMembers | sortByNodesName -%}
206
+ {%- set allMethods = declaration | getClassMethods | excludeByJsDocTags('internal') | filterByScope(memberScopes) | filterUselessMembers | sortByNodesName -%}
207
+ {%- set allAccessors = declaration | getClassAccessors | excludeByJsDocTags('internal') | filterByScope(memberScopes) | filterUselessMembers | sortByNodesName -%}
206
208
  {%- set staticProperties = allProperties | filterByStatic(true) -%}
207
209
  {%- set properties = allProperties | filterByStatic(false) -%}
208
210
  {%- set staticAccessors = allAccessors | filterByStatic(true) | groupAccessors -%}
@@ -69,7 +69,7 @@
69
69
  {{ overloadMacro.render(overloads) }}
70
70
  {% endif %}
71
71
  {% else %}
72
- {{ membersMacro.render(declaration) }}
72
+ {{ membersMacro.render(declaration, hideProtectedMembers) }}
73
73
  {% endif %}
74
74
 
75
75
  {% include "parts/examples.html.nunj" %}
@@ -7,6 +7,7 @@ import { Observable } from "rxjs";
7
7
  import { createServer as viteCreateServer } from "vite";
8
8
 
9
9
  // libs/builder/generator/vite/production.ts
10
+ import { existsSync } from "node:fs";
10
11
  import { rm, writeFile } from "node:fs/promises";
11
12
  import path from "node:path";
12
13
  import { build as viteBuild } from "vite";
@@ -75,7 +76,8 @@ function prerenderNgDoc(options) {
75
76
  serverEntry: options.serverEntry,
76
77
  routes: [...options.routes ?? []],
77
78
  discoverRoutes: options.discoverRoutes ?? true,
78
- ...options.routeTimeoutMs === void 0 ? {} : { routeTimeoutMs: options.routeTimeoutMs }
79
+ ...options.routeTimeoutMs === void 0 ? {} : { routeTimeoutMs: options.routeTimeoutMs },
80
+ ...options.demoServerEntry ? { demoServerEntry: options.demoServerEntry } : {}
79
81
  };
80
82
  if (request.routeTimeoutMs !== void 0 && (!Number.isSafeInteger(request.routeTimeoutMs) || request.routeTimeoutMs < 1)) {
81
83
  return Promise.reject(
@@ -256,11 +258,13 @@ async function buildNgDocViteApplication(options, dependencies = defaultDependen
256
258
  }
257
259
  const prerender = options.prerender ?? (options.skipBuild ? true : ssr === true);
258
260
  if (!prerender) return { browser, ...ssr ? { server: serverEntry } : {} };
261
+ const demoServerEntry = path.join(serverDirectory, "demo-server.mjs");
259
262
  const report = await timed(
260
263
  "Prerendered the routes",
261
264
  () => dependencies.prerender({
262
265
  browserDir: browser,
263
266
  serverEntry,
267
+ ...existsSync(demoServerEntry) && existsSync(path.join(browser, "ng-doc-demo.html")) ? { demoServerEntry } : {},
264
268
  routes: options.routes ?? [],
265
269
  discoverRoutes: options.discoverRoutes ?? true,
266
270
  ...options.routeTimeoutMs === void 0 ? {} : { routeTimeoutMs: options.routeTimeoutMs },
@@ -273,6 +277,7 @@ async function buildNgDocViteApplication(options, dependencies = defaultDependen
273
277
  `
274
278
  );
275
279
  log(`Prerendered ${report.routes.length} route(s) into ${browser}`);
280
+ for (const { route, message: message2 } of report.warnings ?? []) log(`Warning: ${route}: ${message2}`);
276
281
  if (report.errors.length) {
277
282
  log(`The application logged ${report.errors.length} error(s) while prerendering:`);
278
283
  for (const { route, message: message2 } of report.errors.slice(0, 10)) log(` ${route}: ${message2}`);
@@ -41,6 +41,14 @@ export interface NgDocViteApplicationOptions {
41
41
  export declare const NG_DOC_BROWSER_ENTRY = "/@ng-doc-application/browser.js";
42
42
  /** The module id of the server entry; `buildNgDocViteApplication` builds it into `server.mjs`. */
43
43
  export declare const NG_DOC_SERVER_ENTRY = "/@ng-doc-application/server.js";
44
+ /** The URL of the demo application's browser entry, which its page loads. */
45
+ export declare const NG_DOC_DEMO_ENTRY = "/@ng-doc-application/demo.js";
46
+ /** The module id of the demo application's server entry, built next to `server.mjs`. */
47
+ export declare const NG_DOC_DEMO_SERVER_ENTRY = "/@ng-doc-application/demo-server.js";
48
+ /** The page of the demo application, next to index.html in the build output. */
49
+ export declare const NG_DOC_DEMO_PAGE = "ng-doc-demo.html";
50
+ /** The name of the demo application's server entry in the server build (`demo-server.mjs`). */
51
+ export declare const NG_DOC_DEMO_SERVER_NAME = "demo-server";
44
52
  interface ResolvedAsset {
45
53
  input: string;
46
54
  output: string;
@@ -77,6 +85,12 @@ export declare function matchAssets(assets: readonly ResolvedAsset[], base: stri
77
85
  file: string;
78
86
  relative: string;
79
87
  }>;
88
+ /**
89
+ * The page of the demo application: the application's index.html with the demo application's
90
+ * root as its body, so it has the same head (base href, fonts, icons, theme script).
91
+ * @param index - The content of index.html.
92
+ */
93
+ export declare function demoDocument(index: string): string;
80
94
  /** Sets `<base href>` to an absolute Vite base, or adds it at the start of `<head>`. */
81
95
  export declare function withBase(html: string, base: string): string;
82
96
  /**
@@ -0,0 +1,34 @@
1
+ /** The generated module of the demo application, in the output root (`demo-app.ts.nunj`). */
2
+ export declare const DEMO_APPLICATION_MODULE = "demo-app.ts";
3
+ /**
4
+ * The demo application of a generation: its generated module and the URL path of its pages.
5
+ * The NgDoc plugin publishes it to the application plugin (`NgDocDemoApplicationApi`).
6
+ */
7
+ export interface NgDocDemoApplication {
8
+ /** The generated module (absolute), which exports the routes and the providers import. */
9
+ readonly module: string;
10
+ /** The URL path of the demo pages, relative to the base href, without slashes around it. */
11
+ readonly path: string;
12
+ /** Every demo page, relative to the base href (`<path>/<page route>/<demo name>`). */
13
+ readonly pages: readonly string[];
14
+ }
15
+ /**
16
+ * What the NgDoc plugin offers the application plugin through `api.ngDocDemoApplication`.
17
+ * @internal
18
+ */
19
+ export interface NgDocDemoApplicationApi {
20
+ readonly schemaVersion: 1;
21
+ /**
22
+ * The demo application of the build's generation, generating first if the build has not yet: a
23
+ * build adds the demo page's input before it starts, when there is one.
24
+ */
25
+ resolve(): Promise<NgDocDemoApplication | undefined>;
26
+ /** The demo application of the current generation of the development server, if any. */
27
+ current(): Promise<NgDocDemoApplication | undefined>;
28
+ }
29
+ /**
30
+ * Reads the demo application from a committed output root: a generation writes `demo-app.ts`
31
+ * only when a guide has demo pages, and that module names the URL path of the pages.
32
+ * @param outputRoot - The output root of the published generation.
33
+ */
34
+ export declare function readDemoApplication(outputRoot: string): Promise<NgDocDemoApplication | undefined>;
@@ -7,6 +7,7 @@ import { Observable } from "rxjs";
7
7
  import { createServer as viteCreateServer } from "vite";
8
8
 
9
9
  // libs/builder/generator/vite/production.ts
10
+ import { existsSync } from "node:fs";
10
11
  import { rm, writeFile } from "node:fs/promises";
11
12
  import path from "node:path";
12
13
  import { build as viteBuild } from "vite";
@@ -75,7 +76,8 @@ function prerenderNgDoc(options) {
75
76
  serverEntry: options.serverEntry,
76
77
  routes: [...options.routes ?? []],
77
78
  discoverRoutes: options.discoverRoutes ?? true,
78
- ...options.routeTimeoutMs === void 0 ? {} : { routeTimeoutMs: options.routeTimeoutMs }
79
+ ...options.routeTimeoutMs === void 0 ? {} : { routeTimeoutMs: options.routeTimeoutMs },
80
+ ...options.demoServerEntry ? { demoServerEntry: options.demoServerEntry } : {}
79
81
  };
80
82
  if (request.routeTimeoutMs !== void 0 && (!Number.isSafeInteger(request.routeTimeoutMs) || request.routeTimeoutMs < 1)) {
81
83
  return Promise.reject(
@@ -256,11 +258,13 @@ async function buildNgDocViteApplication(options, dependencies = defaultDependen
256
258
  }
257
259
  const prerender = options.prerender ?? (options.skipBuild ? true : ssr === true);
258
260
  if (!prerender) return { browser, ...ssr ? { server: serverEntry } : {} };
261
+ const demoServerEntry = path.join(serverDirectory, "demo-server.mjs");
259
262
  const report = await timed(
260
263
  "Prerendered the routes",
261
264
  () => dependencies.prerender({
262
265
  browserDir: browser,
263
266
  serverEntry,
267
+ ...existsSync(demoServerEntry) && existsSync(path.join(browser, "ng-doc-demo.html")) ? { demoServerEntry } : {},
264
268
  routes: options.routes ?? [],
265
269
  discoverRoutes: options.discoverRoutes ?? true,
266
270
  ...options.routeTimeoutMs === void 0 ? {} : { routeTimeoutMs: options.routeTimeoutMs },
@@ -273,6 +277,7 @@ async function buildNgDocViteApplication(options, dependencies = defaultDependen
273
277
  `
274
278
  );
275
279
  log(`Prerendered ${report.routes.length} route(s) into ${browser}`);
280
+ for (const { route, message: message2 } of report.warnings ?? []) log(`Warning: ${route}: ${message2}`);
276
281
  if (report.errors.length) {
277
282
  log(`The application logged ${report.errors.length} error(s) while prerendering:`);
278
283
  for (const { route, message: message2 } of report.errors.slice(0, 10)) log(` ${route}: ${message2}`);
@@ -15,13 +15,53 @@ export interface InitialCompilationInventory {
15
15
  }>;
16
16
  isCurrent(): boolean;
17
17
  }
18
+ /**
19
+ * What the host does for the coordinator when a burst ends (see {@link HostUpdateCoordinator}).
20
+ */
21
+ export interface HostUpdateRecovery {
22
+ /** Logs a warning: the summary of the host update timeouts a burst held back. */
23
+ warn(message: string): void;
24
+ /**
25
+ * Reports `file` again as its watcher would have: a generated output whose report never
26
+ * completed its generation's expectation (lost, or its hook timed out and threw).
27
+ */
28
+ replay(file: string, type: 'create' | 'update' | 'delete'): void;
29
+ /** Re-observes every recorded input before the next generation (`BuildSession.rescan`). */
30
+ rescan(): void;
31
+ }
32
+ /** How long a host update waits for the publication's host obligations to drain. */
33
+ export declare const HOST_COMPLETION_TIMEOUT_MS = 30000;
34
+ /** Hot updates within {@link HOST_BURST_WINDOW_MS} that open a burst of file events. */
35
+ export declare const HOST_BURST_EVENTS = 1000;
36
+ /** The window in which {@link HOST_BURST_EVENTS} hot updates open a burst. */
37
+ export declare const HOST_BURST_WINDOW_MS = 1000;
38
+ /** A burst ends after this long without a hot update or a host update timeout. */
39
+ export declare const HOST_BURST_QUIET_MS = 2000;
18
40
  export declare function observePhysicalState(file: string, read?: () => string | Promise<string>): Promise<PhysicalState>;
19
41
  /** A synchronous metadata-only stamp preserves the event-time identity without starting I/O queues. */
20
42
  export declare function observePhysicalVersion(file: string): string | undefined;
21
- /** Coordinates committed generator versions with Vite's client hot-update pipeline. */
43
+ /**
44
+ * Coordinates committed generator versions with Vite's client hot-update pipeline.
45
+ *
46
+ * Bursts: thousands of file events at once (a script or a checkout writing many files) flood
47
+ * Vite's hot-update pipeline and the watcher. Host updates then wait longer than
48
+ * {@link HOST_COMPLETION_TIMEOUT_MS}, a hook that timed out leaves its generation's obligation
49
+ * open (nothing reports that output again, so no reload is ever sent), and chokidar may lose
50
+ * events without any signal. So a burst is tracked: it opens with {@link HOST_BURST_EVENTS} hot
51
+ * updates of files outside the output root (once the first generation is published) within
52
+ * {@link HOST_BURST_WINDOW_MS}, or with a host update timeout, and ends after
53
+ * {@link HOST_BURST_QUIET_MS} without either. Only its first timeout is reported as it happens;
54
+ * the others are summed up in one warning when it ends. Then it takes one more pass: every
55
+ * generated output whose report never completed its expectation, and whose file is still what
56
+ * its generation wrote, is reported again (`HostUpdateRecovery.replay`), and every recorded
57
+ * input is re-observed (`HostUpdateRecovery.rescan`), so the newest edit is published even if
58
+ * its event was lost or its generation failed under the load. A replayed report is matched like
59
+ * any other: one that arrives twice is a repeat, so an edit still reloads once.
60
+ */
22
61
  export declare class HostUpdateCoordinator {
23
62
  private readonly notify;
24
63
  private readonly fail;
64
+ private readonly recovery?;
25
65
  private outputRoot?;
26
66
  private manifest?;
27
67
  private announcedManifest?;
@@ -43,7 +83,10 @@ export declare class HostUpdateCoordinator {
43
83
  private pendingGeneration?;
44
84
  private sequence;
45
85
  private disposed;
46
- constructor(notify: () => void, fail: (error: Error) => void);
86
+ private burst?;
87
+ /** The start and the hot updates of the current burst-detection window. */
88
+ private window;
89
+ constructor(notify: () => void, fail: (error: Error) => void, recovery?: HostUpdateRecovery | undefined);
47
90
  seed(outputRoot: string, manifest: OutputManifest): void;
48
91
  initialCompilationInventory(): InitialCompilationInventory;
49
92
  started(generation: number, changes: FileChange[]): void;
@@ -141,5 +184,29 @@ export declare class HostUpdateCoordinator {
141
184
  private delta;
142
185
  private prune;
143
186
  private withTimeout;
187
+ /** Counts a hot update towards a burst of file events (see the class description). */
188
+ private hotUpdate;
189
+ /**
190
+ * A host update did not settle in time. The first of a burst is reported as it happens (as
191
+ * every timeout was before); the others only count towards the burst's summary, so a flood of
192
+ * file events logs two lines instead of one per event.
193
+ * @param failure The timeout, thrown to its waiter as well.
194
+ */
195
+ private timedOut;
196
+ private openBurst;
197
+ /**
198
+ * One timer per burst, re-armed for the rest of the quiet period when activity moved `last`.
199
+ * @param burst The open burst.
200
+ * @param delay When to check it next.
201
+ */
202
+ private armBurst;
203
+ /**
204
+ * The pass after a burst: the summary of its held-back timeouts, then every open output
205
+ * obligation whose file is still what its generation wrote is reported again, in path order,
206
+ * and every recorded input is re-observed once no generation is in flight. An output a newer generation rewrote since is left
207
+ * to that generation's own report, which supersedes the older expectation.
208
+ * @param burst The burst that ended.
209
+ */
210
+ private recover;
144
211
  }
145
212
  export {};