@abreen/tada 1.11.3 → 1.13.2

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 (79) hide show
  1. package/README.md +65 -64
  2. package/bin/tada.ts +61 -66
  3. package/build/build-manifest.ts +14 -3
  4. package/build/bundle.ts +0 -1
  5. package/build/config-files.ts +183 -0
  6. package/build/config-loader.ts +159 -0
  7. package/build/copy.ts +0 -83
  8. package/build/generate-content-assets.ts +75 -160
  9. package/build/globals.ts +48 -89
  10. package/build/log.ts +27 -18
  11. package/build/pagefind.ts +33 -26
  12. package/build/pipeline.ts +98 -79
  13. package/build/reachability.ts +51 -248
  14. package/build/serve.ts +35 -6
  15. package/build/site-variables.ts +20 -11
  16. package/build/source-model.ts +400 -0
  17. package/build/{watch/records.ts → source-records.ts} +111 -49
  18. package/build/template-globals.ts +91 -0
  19. package/build/templates.ts +50 -40
  20. package/build/test-helpers.ts +15 -0
  21. package/build/toc-plugin.ts +5 -1
  22. package/build/types.d.ts +9 -1
  23. package/build/util.ts +8 -0
  24. package/build/utils/code.ts +6 -16
  25. package/build/utils/content-files.ts +57 -126
  26. package/build/utils/final-html.ts +239 -0
  27. package/build/utils/markdown.ts +237 -17
  28. package/build/utils/paths.ts +2 -1
  29. package/build/utils/render.ts +108 -99
  30. package/build/validate-config-links.ts +91 -14
  31. package/build/watch/assets.ts +0 -2
  32. package/build/watch/build-full.ts +14 -15
  33. package/build/watch/build-incremental.ts +15 -8
  34. package/build/watch/compiler-types.d.ts +6 -1
  35. package/build/watch/compiler.ts +27 -15
  36. package/build/watch/index.ts +2 -7
  37. package/build/watch/planner.ts +32 -16
  38. package/build/watch/reload.ts +4 -0
  39. package/build/watch/runtime.ts +29 -45
  40. package/build/watch/snapshot.ts +50 -402
  41. package/build/watch/validation.ts +6 -3
  42. package/build/watch-reachability-state.ts +16 -23
  43. package/build/watch-reload-client.ts +11 -4
  44. package/init/content/index.md +1 -1
  45. package/init/content/labs/02/index.md +61 -0
  46. package/init/content/labs/02/selection_sort.py +23 -0
  47. package/init/content/labs/index.md +4 -2
  48. package/init/content/lectures/01/index.md +1 -1
  49. package/init/content/markdown.md +1 -1
  50. package/package.json +3 -4
  51. package/src/code/index.ts +46 -18
  52. package/src/code.scss +2 -1
  53. package/src/globals.ts +47 -0
  54. package/src/index.ts +1 -0
  55. package/src/navigate/index.ts +2 -5
  56. package/src/navigate/lifecycle.ts +3 -1
  57. package/src/navigate/runtime.ts +6 -5
  58. package/src/page-update/index.ts +20 -16
  59. package/src/search/index.ts +53 -25
  60. package/src/slides/index.ts +467 -0
  61. package/src/slides/style.scss +104 -0
  62. package/src/style.scss +8 -3
  63. package/src/timezone/index.ts +16 -20
  64. package/src/top/index.ts +2 -4
  65. package/src/trace/index.ts +91 -7
  66. package/src/trace/style.scss +1 -1
  67. package/src/util.ts +6 -2
  68. package/templates/_author.html +2 -2
  69. package/templates/_heading.html +1 -1
  70. package/templates/_nav.html +2 -2
  71. package/templates/_theme.scss +3 -3
  72. package/templates/_top.html +14 -14
  73. package/templates/default.html +8 -0
  74. package/types/dev.d.ts +1 -1
  75. package/types/site-variables.d.ts +4 -5
  76. package/watch/fs-commit.ts +63 -10
  77. package/build/apply-base-path-plugin.ts +0 -186
  78. package/build/content-watch.ts +0 -104
  79. package/build/validate-internal-links-plugin.ts +0 -207
@@ -1,14 +1,16 @@
1
- import { compileTemplates, json } from '../templates';
1
+ import { compileTemplates, config } from '../templates';
2
2
  import { getDevSiteVariables } from '../site-variables';
3
+ import { createContentRecord, createPublicRecord } from '../source-records';
3
4
  import { getDistDir } from '../util';
4
5
  import type { CompilerBuildResult } from '../../watch/types';
5
6
  import {
7
+ collectHtmlAnalysisByPath,
6
8
  collectHtmlAssetsByPath,
7
9
  createSnapshot,
8
- scanProject,
9
10
  type TadaSourceRecord,
10
11
  type TadaSnapshot,
11
12
  } from './snapshot';
13
+ import { scanProject } from '../source-model';
12
14
  import type {
13
15
  TadaBuildMeta,
14
16
  TraceCache,
@@ -22,19 +24,16 @@ import {
22
24
  removeDirIfExists,
23
25
  writeAssets,
24
26
  } from './assets';
25
- import { createPublicRecord, renderContentRecord } from './records';
26
27
  import {
27
28
  diagnosticsFromMessages,
28
29
  validateConfig,
29
- validateJsonLinks,
30
+ validateProjectConfigLinks,
30
31
  } from './validation';
31
32
 
32
33
  export async function buildFull({
33
- wsPort,
34
34
  traceCache,
35
35
  traceOptions,
36
36
  }: {
37
- wsPort: number;
38
37
  traceCache: TraceCache;
39
38
  traceOptions: WatchTraceOptions;
40
39
  }): Promise<CompilerBuildResult<TadaSnapshot, TadaBuildMeta>> {
@@ -52,16 +51,12 @@ export async function buildFull({
52
51
  compileTemplates(siteVariables);
53
52
  await ensureHighlighter(siteVariables);
54
53
 
55
- const assetFiles = await bundleWatchAssets(
56
- outputDir,
57
- siteVariables,
58
- wsPort,
59
- );
54
+ const assetFiles = await bundleWatchAssets(outputDir, siteVariables);
60
55
  await populateStaticAssets(outputDir, siteVariables);
61
56
 
62
57
  const contentRecords = new Map<string, TadaSourceRecord>();
63
58
  for (const filePath of scan.contentFiles) {
64
- const record = renderContentRecord({
59
+ const record = createContentRecord({
65
60
  filePath,
66
61
  siteVariables,
67
62
  scan,
@@ -72,6 +67,7 @@ export async function buildFull({
72
67
  cachedTraceSourceDir: distDir,
73
68
  });
74
69
  if (record.outputs.size > 0) {
70
+ writeAssets(outputDir, record.outputs);
75
71
  contentRecords.set(filePath, record);
76
72
  }
77
73
  }
@@ -83,7 +79,7 @@ export async function buildFull({
83
79
  publicRecords.set(filePath, record);
84
80
  }
85
81
 
86
- const linkDiagnostics = validateJsonLinks(scan.validTargets);
82
+ const linkDiagnostics = validateProjectConfigLinks(scan.validTargets);
87
83
  if (linkDiagnostics.length > 0) {
88
84
  removeDirIfExists(outputDir);
89
85
  return { ok: false, diagnostics: linkDiagnostics };
@@ -92,8 +88,8 @@ export async function buildFull({
92
88
  const nextSnapshot = createSnapshot({
93
89
  siteVariables,
94
90
  assetFiles,
95
- navData: json('nav.json'),
96
- authorsData: json('authors.json'),
91
+ navData: config('nav'),
92
+ authorsData: config('authors'),
97
93
  scan,
98
94
  contentRecords,
99
95
  publicRecords,
@@ -109,6 +105,9 @@ export async function buildFull({
109
105
  },
110
106
  meta: {
111
107
  htmlAssetsByPath: collectHtmlAssetsByPath(nextSnapshot.contentRecords),
108
+ htmlAnalysisByPath: collectHtmlAnalysisByPath(
109
+ nextSnapshot.contentRecords,
110
+ ),
112
111
  siteVariables,
113
112
  },
114
113
  };
@@ -1,5 +1,6 @@
1
- import { compileTemplates, json } from '../templates';
1
+ import { compileTemplates, config } from '../templates';
2
2
  import { getDistDir } from '../util';
3
+ import { createContentRecord, createPublicRecord } from '../source-records';
3
4
  import type { CompilerBuildResult } from '../../watch/types';
4
5
  import type {
5
6
  TadaBuildMeta,
@@ -8,6 +9,7 @@ import type {
8
9
  } from './compiler-types';
9
10
  import type { TadaWatchPlan } from './planner';
10
11
  import {
12
+ collectHtmlAnalysisByPath,
11
13
  collectHtmlAssetsByPath,
12
14
  createSnapshot,
13
15
  type TadaSnapshot,
@@ -17,13 +19,13 @@ import {
17
19
  ensureHighlighter,
18
20
  makeTempBuildDir,
19
21
  removeDirIfExists,
22
+ writeAssets,
20
23
  } from './assets';
21
24
  import { computeMutations } from './mutations';
22
- import { createPublicRecord, renderContentRecord } from './records';
23
25
  import {
24
26
  diagnosticsFromMessages,
25
27
  validateConfig,
26
- validateJsonLinks,
28
+ validateProjectConfigLinks,
27
29
  } from './validation';
28
30
 
29
31
  export async function buildIncremental({
@@ -62,7 +64,7 @@ export async function buildIncremental({
62
64
 
63
65
  for (const sourcePath of plan.contentToRender) {
64
66
  nextContentRecords.delete(sourcePath);
65
- const record = renderContentRecord({
67
+ const record = createContentRecord({
66
68
  filePath: sourcePath,
67
69
  siteVariables,
68
70
  scan: plan.scan,
@@ -71,9 +73,9 @@ export async function buildIncremental({
71
73
  traceCache,
72
74
  traceToolAvailability: traceOptions.toolAvailability,
73
75
  cachedTraceSourceDir: getDistDir(),
74
- persistOutputs: false,
75
76
  });
76
77
  if (record.outputs.size > 0) {
78
+ writeAssets(outputDir, record.outputs);
77
79
  nextContentRecords.set(sourcePath, record);
78
80
  }
79
81
  }
@@ -87,14 +89,16 @@ export async function buildIncremental({
87
89
  const nextSnapshot = createSnapshot({
88
90
  siteVariables,
89
91
  assetFiles: snapshot.assetFiles,
90
- navData: json('nav.json'),
91
- authorsData: json('authors.json'),
92
+ navData: config('nav'),
93
+ authorsData: config('authors'),
92
94
  scan: plan.scan,
93
95
  contentRecords: nextContentRecords,
94
96
  publicRecords: nextPublicRecords,
95
97
  });
96
98
 
97
- const linkDiagnostics = validateJsonLinks(nextSnapshot.validTargets);
99
+ const linkDiagnostics = validateProjectConfigLinks(
100
+ nextSnapshot.validTargets,
101
+ );
98
102
  if (linkDiagnostics.length > 0) {
99
103
  removeDirIfExists(outputDir);
100
104
  return { ok: false, diagnostics: linkDiagnostics };
@@ -124,6 +128,9 @@ export async function buildIncremental({
124
128
  commit: { kind: 'apply-mutations', rootDir: getDistDir(), mutations },
125
129
  meta: {
126
130
  htmlAssetsByPath: collectHtmlAssetsByPath(nextSnapshot.contentRecords),
131
+ htmlAnalysisByPath: collectHtmlAnalysisByPath(
132
+ nextSnapshot.contentRecords,
133
+ ),
127
134
  siteVariables,
128
135
  },
129
136
  };
@@ -1,4 +1,8 @@
1
- import type { SiteVariables, TraceToolAvailability } from '../types';
1
+ import type {
2
+ HtmlOutputAnalysis,
3
+ SiteVariables,
4
+ TraceToolAvailability,
5
+ } from '../types';
2
6
 
3
7
  export interface TraceCacheEntry {
4
8
  manifestUrl: string;
@@ -11,6 +15,7 @@ export type TraceCache = Map<string, TraceCacheEntry>;
11
15
 
12
16
  export interface TadaBuildMeta {
13
17
  htmlAssetsByPath: Map<string, string>;
18
+ htmlAnalysisByPath: Map<string, HtmlOutputAnalysis>;
14
19
  siteVariables: SiteVariables;
15
20
  }
16
21
 
@@ -1,6 +1,11 @@
1
1
  import path from 'path';
2
+ import {
3
+ getProjectConfigBaseName,
4
+ getSiteConfigBaseName,
5
+ getSupportedConfigFilePaths,
6
+ } from '../config-files';
2
7
  import { getDevSiteVariables } from '../site-variables';
3
- import { getJsonDataDir } from '../templates';
8
+ import { getProjectConfigDir } from '../templates';
4
9
  import { getContentDir, getPublicDir } from '../util';
5
10
  import type {
6
11
  ChangeBatch,
@@ -18,7 +23,8 @@ import type {
18
23
  } from './compiler-types';
19
24
  import { checkTraceToolAvailability, isTraceSourceFile } from '../utils/trace';
20
25
  import { createTadaWatchPlan, type TadaWatchPlan } from './planner';
21
- import { scanProject, type TadaSnapshot, updateProjectScan } from './snapshot';
26
+ import { scanProject, updateProjectScan } from '../source-model';
27
+ import type { TadaSnapshot } from './snapshot';
22
28
 
23
29
  export function invalidateTraceCacheForBatch(
24
30
  traceCache: TraceCache,
@@ -32,15 +38,15 @@ export function invalidateTraceCacheForBatch(
32
38
  }
33
39
  }
34
40
 
35
- export class TadaWatchCompiler
36
- implements WatchCompiler<TadaSnapshot, TadaWatchPlan, TadaBuildMeta>
37
- {
38
- private wsPort: number;
41
+ export class TadaWatchCompiler implements WatchCompiler<
42
+ TadaSnapshot,
43
+ TadaWatchPlan,
44
+ TadaBuildMeta
45
+ > {
39
46
  private traceCache: TraceCache;
40
47
  private traceOptions: WatchTraceOptions;
41
48
 
42
- constructor({ wsPort }: { wsPort: number }) {
43
- this.wsPort = wsPort;
49
+ constructor() {
44
50
  this.traceCache = new Map();
45
51
  this.traceOptions = { toolAvailability: checkTraceToolAvailability() };
46
52
  }
@@ -48,18 +54,26 @@ export class TadaWatchCompiler
48
54
  getWatchTargets(): WatchTarget[] {
49
55
  const contentDir = getContentDir();
50
56
  const publicDir = getPublicDir();
51
- const jsonDataDir = path.resolve(getJsonDataDir());
57
+ const projectConfigDir = path.resolve(getProjectConfigDir());
52
58
  const configFilePaths = new Set([
53
- path.resolve('site.dev.json'),
54
- path.resolve(jsonDataDir, 'nav.json'),
55
- path.resolve(jsonDataDir, 'authors.json'),
59
+ ...getSupportedConfigFilePaths('.', getSiteConfigBaseName('dev')).map(
60
+ filePath => path.resolve(filePath),
61
+ ),
62
+ ...getSupportedConfigFilePaths(
63
+ projectConfigDir,
64
+ getProjectConfigBaseName('nav'),
65
+ ).map(filePath => path.resolve(filePath)),
66
+ ...getSupportedConfigFilePaths(
67
+ projectConfigDir,
68
+ getProjectConfigBaseName('authors'),
69
+ ).map(filePath => path.resolve(filePath)),
56
70
  ]);
57
71
 
58
72
  return [
59
73
  { path: contentDir },
60
74
  { path: publicDir },
61
75
  {
62
- path: jsonDataDir,
76
+ path: projectConfigDir,
63
77
  chokidar: { depth: 0 },
64
78
  filter: filePath => configFilePaths.has(path.resolve(filePath)),
65
79
  },
@@ -70,7 +84,6 @@ export class TadaWatchCompiler
70
84
  CompilerBuildResult<TadaSnapshot, TadaBuildMeta>
71
85
  > {
72
86
  return buildFull({
73
- wsPort: this.wsPort,
74
87
  traceCache: this.traceCache,
75
88
  traceOptions: this.traceOptions,
76
89
  });
@@ -96,7 +109,6 @@ export class TadaWatchCompiler
96
109
  ): Promise<CompilerBuildResult<TadaSnapshot, TadaBuildMeta>> {
97
110
  if (plan.kind === 'full' || !snapshot) {
98
111
  return buildFull({
99
- wsPort: this.wsPort,
100
112
  traceCache: this.traceCache,
101
113
  traceOptions: this.traceOptions,
102
114
  });
@@ -3,19 +3,14 @@ import { runWatchEngine } from '../../watch/engine';
3
3
  import { TadaWatchCompiler } from './compiler';
4
4
  import { TadaWatchRuntime } from './runtime';
5
5
 
6
- export async function runWatch(options: {
7
- httpPort?: number;
8
- wsPort?: number;
9
- }): Promise<void> {
10
- const wsPort = options.wsPort ?? 35729;
6
+ export async function runWatch(options: { httpPort?: number }): Promise<void> {
11
7
  const runtime = new TadaWatchRuntime({
12
- wsPort,
13
8
  httpPort: options.httpPort,
14
9
  distDir: getDistDir(),
15
10
  });
16
11
 
17
12
  await runWatchEngine({
18
- compiler: new TadaWatchCompiler({ wsPort }),
13
+ compiler: new TadaWatchCompiler(),
19
14
  onEvent: event => runtime.onEvent(event),
20
15
  debounceMs: 300,
21
16
  });
@@ -1,6 +1,11 @@
1
- import fs from 'fs';
2
1
  import path from 'path';
3
- import { getJsonDataDir } from '../templates';
2
+ import {
3
+ getProjectConfigBaseName,
4
+ getSiteConfigBaseName,
5
+ getSupportedConfigFilePaths,
6
+ } from '../config-files';
7
+ import { loadProjectConfig } from '../config-loader';
8
+ import { getProjectConfigDir } from '../templates';
4
9
  import type { ChangeBatch } from '../../watch/types';
5
10
  import type { TadaProjectScan, TadaSnapshot } from './snapshot';
6
11
 
@@ -13,13 +18,6 @@ export interface TadaWatchPlan {
13
18
  publicToRemove: Set<string>;
14
19
  }
15
20
 
16
- function readJsonFile(filePath: string): unknown {
17
- if (!fs.existsSync(filePath)) {
18
- return undefined;
19
- }
20
- return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
21
- }
22
-
23
21
  export function diffAuthorKeys(previous: unknown, next: unknown): Set<string> {
24
22
  const previousMap =
25
23
  previous && typeof previous === 'object'
@@ -70,10 +68,24 @@ export function createTadaWatchPlan({
70
68
  const publicToRender = new Set<string>();
71
69
  const contentToRemove = new Set<string>();
72
70
  const publicToRemove = new Set<string>();
73
- const jsonDataDir = getJsonDataDir();
74
- const siteConfigPath = path.resolve('site.dev.json');
75
- const navPath = path.resolve(jsonDataDir, 'nav.json');
76
- const authorsPath = path.resolve(jsonDataDir, 'authors.json');
71
+ const projectConfigDir = getProjectConfigDir();
72
+ const siteConfigPaths = new Set(
73
+ getSupportedConfigFilePaths('.', getSiteConfigBaseName('dev')).map(
74
+ filePath => path.resolve(filePath),
75
+ ),
76
+ );
77
+ const navPaths = new Set(
78
+ getSupportedConfigFilePaths(
79
+ projectConfigDir,
80
+ getProjectConfigBaseName('nav'),
81
+ ).map(filePath => path.resolve(filePath)),
82
+ );
83
+ const authorsPaths = new Set(
84
+ getSupportedConfigFilePaths(
85
+ projectConfigDir,
86
+ getProjectConfigBaseName('authors'),
87
+ ).map(filePath => path.resolve(filePath)),
88
+ );
77
89
 
78
90
  if (!snapshot) {
79
91
  return {
@@ -88,7 +100,7 @@ export function createTadaWatchPlan({
88
100
 
89
101
  for (const change of batch.changes) {
90
102
  const resolvedPath = path.resolve(change.path);
91
- if (resolvedPath === siteConfigPath || resolvedPath === navPath) {
103
+ if (siteConfigPaths.has(resolvedPath) || navPaths.has(resolvedPath)) {
92
104
  return {
93
105
  kind: 'full',
94
106
  scan,
@@ -102,8 +114,12 @@ export function createTadaWatchPlan({
102
114
 
103
115
  for (const change of batch.changes) {
104
116
  const resolvedPath = path.resolve(change.path);
105
- if (resolvedPath === authorsPath) {
106
- const nextAuthorsData = readJsonFile(authorsPath);
117
+ if (authorsPaths.has(resolvedPath)) {
118
+ const nextAuthorsData = loadProjectConfig(
119
+ projectConfigDir,
120
+ 'authors',
121
+ snapshot.siteVariables,
122
+ )?.value;
107
123
  if (snapshot.authorsData === undefined || nextAuthorsData === undefined) {
108
124
  return {
109
125
  kind: 'full',
@@ -0,0 +1,4 @@
1
+ export const WATCH_RELOAD_PATH = '/__tada_watch';
2
+ export const WATCH_RELOAD_TOPIC = 'tada-watch';
3
+ export const WATCH_RELOAD_MESSAGE_REBUILDING = 'rebuilding';
4
+ export const WATCH_RELOAD_MESSAGE_RELOAD = 'reload';
@@ -1,5 +1,4 @@
1
1
  import path from 'path';
2
- import WebSocket, { WebSocketServer } from 'ws';
3
2
  import { B } from '../colors';
4
3
  import { makeLogger, printFlair } from '../log';
5
4
  import { startServer } from '../serve';
@@ -7,6 +6,12 @@ import { WatchPagefindRunner } from '../pagefind';
7
6
  import type { TadaBuildMeta } from './compiler-types';
8
7
  import type { TadaSnapshot } from './snapshot';
9
8
  import type { WatchLifecycleEvent } from '../../watch/types';
9
+ import {
10
+ WATCH_RELOAD_MESSAGE_REBUILDING,
11
+ WATCH_RELOAD_MESSAGE_RELOAD,
12
+ WATCH_RELOAD_PATH,
13
+ WATCH_RELOAD_TOPIC,
14
+ } from './reload';
10
15
 
11
16
  const log = makeLogger(import.meta.url);
12
17
  const wslog = makeLogger('WebSocket');
@@ -22,65 +27,45 @@ function describeChange(kind: 'add' | 'change' | 'unlink'): string {
22
27
  }
23
28
 
24
29
  export class TadaWatchRuntime {
25
- private wsPort: number;
26
30
  private httpPort: number | undefined;
27
31
  private distDir: string;
28
- private wss: WebSocketServer | null;
32
+ private server: Bun.Server<undefined> | null;
29
33
  private lastBuildFailed: boolean;
30
- private serveStarted: boolean;
31
34
  private pagefindRunner: WatchPagefindRunner | undefined;
32
35
  private pagefindSiteVariablesKey: string | undefined;
33
36
 
34
- constructor({
35
- wsPort,
36
- httpPort,
37
- distDir,
38
- }: {
39
- wsPort: number;
40
- httpPort?: number;
41
- distDir: string;
42
- }) {
43
- this.wsPort = wsPort;
37
+ constructor({ httpPort, distDir }: { httpPort?: number; distDir: string }) {
44
38
  this.httpPort = httpPort;
45
39
  this.distDir = distDir;
40
+ this.server = null;
46
41
  this.lastBuildFailed = false;
47
- this.serveStarted = false;
48
42
  this.pagefindSiteVariablesKey = undefined;
49
- this.wss = new WebSocketServer({ port: wsPort });
50
-
51
- this.wss.on('connection', conn => {
52
- wslog.debug`WebSocket client connected`;
53
- conn.on('close', () => {
54
- wslog.debug`WebSocket client disconnected`;
55
- });
56
- });
57
-
58
- this.wss.on('listening', () => {
59
- wslog.debug`WebSocket server listening at ws://localhost:${this.wsPort}`;
60
- });
61
-
62
- this.wss.on('error', err => {
63
- wslog.error`WebSocket server error: ${err.message}`;
64
- });
65
43
  }
66
44
 
67
45
  private broadcast(message: string): void {
68
- if (!this.wss) {
46
+ if (!this.server) {
69
47
  return;
70
48
  }
71
- for (const client of this.wss.clients) {
72
- if (client.readyState === WebSocket.OPEN) {
73
- client.send(message);
74
- }
75
- }
49
+ this.server.publish(WATCH_RELOAD_TOPIC, message);
76
50
  }
77
51
 
78
52
  private ensureServerStarted(): void {
79
- if (this.serveStarted) {
53
+ if (this.server) {
80
54
  return;
81
55
  }
82
- this.serveStarted = true;
83
- startServer({ port: this.httpPort, distDir: this.distDir });
56
+ this.server = startServer({
57
+ port: this.httpPort,
58
+ distDir: this.distDir,
59
+ watchReload: {
60
+ onClientOpen: () => {
61
+ wslog.debug`WebSocket client connected`;
62
+ },
63
+ onClientClose: () => {
64
+ wslog.debug`WebSocket client disconnected`;
65
+ },
66
+ },
67
+ });
68
+ wslog.debug`WebSocket server listening at ws://localhost:${this.server.port}${WATCH_RELOAD_PATH}`;
84
69
  }
85
70
 
86
71
  async onEvent(
@@ -92,7 +77,7 @@ export class TadaWatchRuntime {
92
77
  for (const change of event.batch?.changes || []) {
93
78
  log.event`${B`${path.basename(change.path)}`} ${describeChange(change.kind)}`;
94
79
  }
95
- this.broadcast('rebuilding');
80
+ this.broadcast(WATCH_RELOAD_MESSAGE_REBUILDING);
96
81
  }
97
82
  return;
98
83
  case 'build-succeeded': {
@@ -101,7 +86,7 @@ export class TadaWatchRuntime {
101
86
  printFlair();
102
87
  this.ensureServerStarted();
103
88
  if (!event.initial && (event.changed || recoveredFromFailure)) {
104
- this.broadcast('reload');
89
+ this.broadcast(WATCH_RELOAD_MESSAGE_RELOAD);
105
90
  }
106
91
  if (event.meta && event.changed) {
107
92
  if (event.meta.siteVariables.features.search !== false) {
@@ -110,14 +95,13 @@ export class TadaWatchRuntime {
110
95
  !this.pagefindRunner ||
111
96
  this.pagefindSiteVariablesKey !== siteVariablesKey
112
97
  ) {
113
- this.pagefindRunner = new WatchPagefindRunner(
114
- event.meta.siteVariables,
115
- );
98
+ this.pagefindRunner = new WatchPagefindRunner();
116
99
  this.pagefindSiteVariablesKey = siteVariablesKey;
117
100
  }
118
101
  this.pagefindRunner.update(
119
102
  this.distDir,
120
103
  event.meta.htmlAssetsByPath,
104
+ event.meta.htmlAnalysisByPath,
121
105
  );
122
106
  setImmediate(() => this.pagefindRunner!.run());
123
107
  } else {