@abreen/tada 1.13.4 → 1.13.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,305 +0,0 @@
1
- import { collectOutgoingHtmlAssetPaths } from './reachability';
2
- import type { HtmlOutputAnalysis } from './types';
3
-
4
- type ReadAnalysisForAsset = (assetPath: string) => HtmlOutputAnalysis;
5
-
6
- function cloneSet(values: Iterable<string> | undefined): Set<string> {
7
- return new Set(values || []);
8
- }
9
-
10
- interface IncrementalOptions {
11
- changedAssetPaths?: Iterable<string>;
12
- removedAssetPaths?: Iterable<string>;
13
- readAnalysisForAsset: ReadAnalysisForAsset;
14
- }
15
-
16
- class WatchReachabilityState {
17
- private rootPath: string;
18
- private knownAssets: Set<string>;
19
- private outgoingBySource: Map<string, Set<string>>;
20
- private incomingByTarget: Map<string, Set<string>>;
21
- private reachable: Set<string>;
22
- private initialized: boolean;
23
-
24
- constructor({ rootPath = 'index.html' }: { rootPath?: string } = {}) {
25
- this.rootPath = rootPath;
26
- this.knownAssets = new Set();
27
- this.outgoingBySource = new Map();
28
- this.incomingByTarget = new Map();
29
- this.reachable = new Set();
30
- this.initialized = false;
31
- }
32
-
33
- reset(): void {
34
- this.knownAssets = new Set();
35
- this.outgoingBySource = new Map();
36
- this.incomingByTarget = new Map();
37
- this.reachable = new Set();
38
- this.initialized = false;
39
- }
40
-
41
- setKnownAssets(assetPaths: Iterable<string>): void {
42
- this.knownAssets = cloneSet(assetPaths);
43
- this.reachable = new Set(
44
- [...this.reachable].filter(assetPath => this.knownAssets.has(assetPath)),
45
- );
46
-
47
- const nextOutgoingBySource = new Map<string, Set<string>>();
48
- for (const [sourcePath, targets] of this.outgoingBySource) {
49
- if (!this.knownAssets.has(sourcePath)) {
50
- continue;
51
- }
52
-
53
- nextOutgoingBySource.set(
54
- sourcePath,
55
- new Set(
56
- [...targets].filter(targetPath => this.knownAssets.has(targetPath)),
57
- ),
58
- );
59
- }
60
-
61
- this.outgoingBySource = nextOutgoingBySource;
62
- this.rebuildIncomingByTarget();
63
- }
64
-
65
- rebuild(readAnalysisForAsset: ReadAnalysisForAsset): void {
66
- if (!this.knownAssets.has(this.rootPath)) {
67
- throw new Error(`Pagefind reachability root not found: ${this.rootPath}`);
68
- }
69
-
70
- this.outgoingBySource = new Map();
71
- this.incomingByTarget = new Map();
72
- this.reachable = new Set();
73
-
74
- const knownAssetPaths = [...this.knownAssets].sort();
75
- for (const sourcePath of knownAssetPaths) {
76
- const outgoingPaths = this.readOutgoingPaths(
77
- sourcePath,
78
- readAnalysisForAsset,
79
- );
80
- this.outgoingBySource.set(sourcePath, outgoingPaths);
81
- this.addIncomingEdges(sourcePath, outgoingPaths);
82
- }
83
-
84
- this.reachable = this.collectClosure(new Set([this.rootPath]));
85
- this.initialized = true;
86
- }
87
-
88
- applyIncremental({
89
- changedAssetPaths,
90
- removedAssetPaths,
91
- readAnalysisForAsset,
92
- }: IncrementalOptions): void {
93
- if (!this.initialized) {
94
- this.rebuild(readAnalysisForAsset);
95
- return;
96
- }
97
-
98
- const normalizedChanged = new Set<string>();
99
- for (const assetPath of changedAssetPaths || []) {
100
- if (this.knownAssets.has(assetPath)) {
101
- normalizedChanged.add(assetPath);
102
- }
103
- }
104
-
105
- const normalizedRemoved = new Set<string>();
106
- for (const assetPath of removedAssetPaths || []) {
107
- if (
108
- !this.knownAssets.has(assetPath) &&
109
- !normalizedChanged.has(assetPath)
110
- ) {
111
- normalizedRemoved.add(assetPath);
112
- }
113
- }
114
-
115
- const previouslyReachableRoots = new Set<string>();
116
- for (const assetPath of normalizedChanged) {
117
- if (this.reachable.has(assetPath)) {
118
- previouslyReachableRoots.add(assetPath);
119
- }
120
- }
121
- for (const assetPath of normalizedRemoved) {
122
- if (this.reachable.has(assetPath)) {
123
- previouslyReachableRoots.add(assetPath);
124
- }
125
- }
126
-
127
- const oldAffectedClosure = this.collectClosure(previouslyReachableRoots);
128
-
129
- for (const assetPath of normalizedRemoved) {
130
- this.removeAsset(assetPath);
131
- }
132
-
133
- const changedAssetList = [...normalizedChanged].sort();
134
- for (const assetPath of changedAssetList) {
135
- const outgoingPaths = this.readOutgoingPaths(
136
- assetPath,
137
- readAnalysisForAsset,
138
- );
139
- this.replaceOutgoing(assetPath, outgoingPaths);
140
- }
141
-
142
- const stillKnownRoots = new Set(
143
- [...previouslyReachableRoots].filter(assetPath =>
144
- this.knownAssets.has(assetPath),
145
- ),
146
- );
147
- const newAffectedClosure = this.collectClosure(stillKnownRoots);
148
- const affected = new Set([...oldAffectedClosure, ...newAffectedClosure]);
149
-
150
- if (affected.size === 0) {
151
- this.initialized = true;
152
- return;
153
- }
154
-
155
- for (const assetPath of affected) {
156
- this.reachable.delete(assetPath);
157
- }
158
-
159
- const seedPaths = new Set<string>();
160
- for (const assetPath of stillKnownRoots) {
161
- if (affected.has(assetPath)) {
162
- seedPaths.add(assetPath);
163
- }
164
- }
165
-
166
- if (this.knownAssets.has(this.rootPath) && affected.has(this.rootPath)) {
167
- seedPaths.add(this.rootPath);
168
- }
169
-
170
- for (const assetPath of affected) {
171
- const incomingPaths = this.incomingByTarget.get(assetPath);
172
- if (!incomingPaths) {
173
- continue;
174
- }
175
-
176
- for (const sourcePath of incomingPaths) {
177
- if (this.reachable.has(sourcePath) && !affected.has(sourcePath)) {
178
- seedPaths.add(assetPath);
179
- break;
180
- }
181
- }
182
- }
183
-
184
- const restoredReachable = this.collectClosure(seedPaths, affected);
185
- for (const assetPath of restoredReachable) {
186
- this.reachable.add(assetPath);
187
- }
188
-
189
- this.initialized = true;
190
- }
191
-
192
- getReachablePaths(): string[] {
193
- return [...this.reachable].sort();
194
- }
195
-
196
- private readOutgoingPaths(
197
- sourcePath: string,
198
- readAnalysisForAsset: ReadAnalysisForAsset,
199
- ): Set<string> {
200
- if (!this.knownAssets.has(sourcePath)) {
201
- return new Set();
202
- }
203
-
204
- const analysis = readAnalysisForAsset(sourcePath);
205
- return new Set(collectOutgoingHtmlAssetPaths(analysis, this.knownAssets));
206
- }
207
-
208
- private addIncomingEdges(
209
- sourcePath: string,
210
- outgoingPaths: Set<string>,
211
- ): void {
212
- for (const targetPath of outgoingPaths) {
213
- let incomingPaths = this.incomingByTarget.get(targetPath);
214
- if (!incomingPaths) {
215
- incomingPaths = new Set();
216
- this.incomingByTarget.set(targetPath, incomingPaths);
217
- }
218
- incomingPaths.add(sourcePath);
219
- }
220
- }
221
-
222
- private rebuildIncomingByTarget(): void {
223
- this.incomingByTarget = new Map();
224
- for (const [sourcePath, outgoingPaths] of this.outgoingBySource) {
225
- this.addIncomingEdges(sourcePath, outgoingPaths);
226
- }
227
- }
228
-
229
- private removeOutgoingEdges(sourcePath: string): void {
230
- const outgoingPaths = this.outgoingBySource.get(sourcePath);
231
- if (!outgoingPaths) {
232
- return;
233
- }
234
-
235
- for (const targetPath of outgoingPaths) {
236
- const incomingPaths = this.incomingByTarget.get(targetPath);
237
- if (!incomingPaths) {
238
- continue;
239
- }
240
-
241
- incomingPaths.delete(sourcePath);
242
- if (incomingPaths.size === 0) {
243
- this.incomingByTarget.delete(targetPath);
244
- }
245
- }
246
- }
247
-
248
- private replaceOutgoing(
249
- sourcePath: string,
250
- outgoingPaths: Set<string>,
251
- ): void {
252
- this.removeOutgoingEdges(sourcePath);
253
- if (!this.knownAssets.has(sourcePath)) {
254
- this.outgoingBySource.delete(sourcePath);
255
- return;
256
- }
257
-
258
- this.outgoingBySource.set(sourcePath, outgoingPaths);
259
- this.addIncomingEdges(sourcePath, outgoingPaths);
260
- }
261
-
262
- private removeAsset(assetPath: string): void {
263
- this.removeOutgoingEdges(assetPath);
264
- this.outgoingBySource.delete(assetPath);
265
- this.incomingByTarget.delete(assetPath);
266
- this.reachable.delete(assetPath);
267
- }
268
-
269
- private collectClosure(
270
- rootPaths: Set<string>,
271
- allowedPaths: Set<string> | null = null,
272
- ): Set<string> {
273
- const pending = [...rootPaths];
274
- const visited = new Set<string>();
275
-
276
- while (pending.length > 0) {
277
- const currentPath = pending.pop()!;
278
- if (visited.has(currentPath)) {
279
- continue;
280
- }
281
- if (allowedPaths && !allowedPaths.has(currentPath)) {
282
- continue;
283
- }
284
-
285
- visited.add(currentPath);
286
- const outgoingPaths = this.outgoingBySource.get(currentPath);
287
- if (!outgoingPaths) {
288
- continue;
289
- }
290
-
291
- for (const targetPath of outgoingPaths) {
292
- if (allowedPaths && !allowedPaths.has(targetPath)) {
293
- continue;
294
- }
295
- if (!visited.has(targetPath)) {
296
- pending.push(targetPath);
297
- }
298
- }
299
- }
300
-
301
- return visited;
302
- }
303
- }
304
-
305
- export default WatchReachabilityState;
package/watch/engine.ts DELETED
@@ -1,296 +0,0 @@
1
- import chokidar from 'chokidar';
2
- import { applyCommitPlan } from './fs-commit';
3
- import { emitWatchEvent } from './events';
4
- import type {
5
- ChangeBatch,
6
- CompilerBuildResult,
7
- CompilerPlanResult,
8
- WatchEngineOptions,
9
- WatchEventKind,
10
- } from './types';
11
-
12
- function normalizeBatch(changes: Map<string, WatchEventKind>): ChangeBatch {
13
- return {
14
- changes: [...changes.entries()]
15
- .sort(([a], [b]) => a.localeCompare(b))
16
- .map(([path, kind]) => ({ path, kind })),
17
- };
18
- }
19
-
20
- function mergeChangeKind(
21
- previous: WatchEventKind | undefined,
22
- next: WatchEventKind,
23
- ): WatchEventKind {
24
- if (!previous) {
25
- return next;
26
- }
27
- if (previous === 'add' && next === 'unlink') {
28
- return 'change';
29
- }
30
- if (previous === 'unlink' && next === 'add') {
31
- return 'change';
32
- }
33
- if (previous === 'add' || next === 'add') {
34
- return 'add';
35
- }
36
- if (previous === 'unlink' || next === 'unlink') {
37
- return 'unlink';
38
- }
39
- return 'change';
40
- }
41
-
42
- function toDiagnostics(error: unknown): { message: string }[] {
43
- if (error instanceof Error) {
44
- return [{ message: error.message }];
45
- }
46
- return [{ message: String(error) }];
47
- }
48
-
49
- function isNoopCommit(commit: {
50
- kind: string;
51
- mutations?: unknown[];
52
- }): boolean {
53
- return (
54
- commit.kind === 'apply-mutations' && (commit.mutations?.length ?? 0) === 0
55
- );
56
- }
57
-
58
- type BuildAttemptOutcome<Snapshot, Meta> =
59
- | { kind: 'skipped'; batch: ChangeBatch; initial: boolean }
60
- | {
61
- kind: 'succeeded';
62
- batch?: ChangeBatch;
63
- initial: boolean;
64
- result: Extract<CompilerBuildResult<Snapshot, Meta>, { ok: true }>;
65
- }
66
- | {
67
- kind: 'failed';
68
- batch?: ChangeBatch;
69
- initial: boolean;
70
- diagnostics: { message: string }[];
71
- meta?: Meta;
72
- };
73
-
74
- export async function runWatchEngine<Snapshot, Plan, Meta>(
75
- options: WatchEngineOptions<Snapshot, Plan, Meta>,
76
- ): Promise<void> {
77
- const debounceMs = options.debounceMs ?? 300;
78
- let snapshot: Snapshot | undefined;
79
- let pending = new Map<string, WatchEventKind>();
80
- let blocked = new Map<string, WatchEventKind>();
81
- let timer: ReturnType<typeof setTimeout> | null = null;
82
- let running = false;
83
-
84
- async function waitForQuietPeriod(): Promise<void> {
85
- await new Promise(resolve => setTimeout(resolve, debounceMs));
86
- }
87
-
88
- async function runBuild(
89
- batch?: ChangeBatch,
90
- initial: boolean = false,
91
- ): Promise<BuildAttemptOutcome<Snapshot, Meta>> {
92
- await emitWatchEvent(options, { kind: 'build-started', batch, initial });
93
- try {
94
- let result: CompilerBuildResult<Snapshot, Meta> | null = null;
95
- if (initial) {
96
- result = await options.compiler.buildInitial();
97
- } else if (batch) {
98
- const nextPlan: CompilerPlanResult<Plan> = await options.compiler.plan(
99
- snapshot,
100
- batch,
101
- );
102
- if (nextPlan.kind === 'skip') {
103
- return { kind: 'skipped', batch, initial };
104
- }
105
- result = await options.compiler.run(nextPlan.plan, snapshot);
106
- }
107
- if (!result) {
108
- return { kind: 'skipped', batch: batch!, initial };
109
- }
110
-
111
- if (result.ok) {
112
- return { kind: 'succeeded', batch, initial, result };
113
- }
114
-
115
- return {
116
- kind: 'failed',
117
- batch,
118
- initial,
119
- diagnostics: result.diagnostics,
120
- meta: result.meta,
121
- };
122
- } catch (error) {
123
- return {
124
- kind: 'failed',
125
- batch,
126
- initial,
127
- diagnostics: toDiagnostics(error),
128
- };
129
- }
130
- }
131
-
132
- async function flush(): Promise<void> {
133
- if (running) {
134
- return;
135
- }
136
- running = true;
137
- try {
138
- let finalSuccess:
139
- | {
140
- batch?: ChangeBatch;
141
- initial: boolean;
142
- result: Extract<CompilerBuildResult<Snapshot, Meta>, { ok: true }>;
143
- changed: boolean;
144
- }
145
- | undefined;
146
-
147
- while (pending.size > 0) {
148
- const batch = normalizeBatch(pending);
149
- pending = new Map();
150
- const outcome = await runBuild(batch, false);
151
-
152
- if (outcome.kind === 'skipped') {
153
- if (pending.size === 0 && !finalSuccess) {
154
- await emitWatchEvent(options, {
155
- kind: 'build-skipped',
156
- batch: outcome.batch,
157
- });
158
- }
159
- continue;
160
- }
161
-
162
- if (outcome.kind === 'failed') {
163
- await emitWatchEvent(options, {
164
- kind: 'build-failed',
165
- initial: outcome.initial,
166
- batch: outcome.batch,
167
- diagnostics: outcome.diagnostics,
168
- meta: outcome.meta,
169
- });
170
- if (outcome.batch) {
171
- for (const change of outcome.batch.changes) {
172
- blocked.set(
173
- change.path,
174
- mergeChangeKind(blocked.get(change.path), change.kind),
175
- );
176
- }
177
- }
178
- return;
179
- }
180
-
181
- const changed = !isNoopCommit(outcome.result.commit);
182
- if (changed) {
183
- applyCommitPlan(outcome.result.commit);
184
- }
185
- snapshot = outcome.result.snapshot;
186
- finalSuccess = {
187
- batch: outcome.batch,
188
- initial: outcome.initial,
189
- result: outcome.result,
190
- changed: (finalSuccess?.changed ?? false) || changed,
191
- };
192
-
193
- if (pending.size === 0) {
194
- await waitForQuietPeriod();
195
- }
196
- }
197
-
198
- if (finalSuccess) {
199
- await emitWatchEvent(options, {
200
- kind: 'build-succeeded',
201
- initial: finalSuccess.initial,
202
- batch: finalSuccess.batch,
203
- changed: finalSuccess.changed,
204
- snapshot: finalSuccess.result.snapshot,
205
- meta: finalSuccess.result.meta,
206
- diagnostics: finalSuccess.result.diagnostics || [],
207
- });
208
- }
209
- } finally {
210
- running = false;
211
- if (pending.size > 0) {
212
- schedule();
213
- }
214
- }
215
- }
216
-
217
- function schedule(): void {
218
- if (timer) {
219
- clearTimeout(timer);
220
- }
221
- timer = setTimeout(() => {
222
- timer = null;
223
- void flush();
224
- }, debounceMs);
225
- }
226
-
227
- function onFileChange(filePath: string, kind: WatchEventKind): void {
228
- if (blocked.size > 0) {
229
- for (const [blockedPath, blockedKind] of blocked) {
230
- pending.set(
231
- blockedPath,
232
- mergeChangeKind(pending.get(blockedPath), blockedKind),
233
- );
234
- }
235
- blocked = new Map();
236
- }
237
- pending.set(filePath, mergeChangeKind(pending.get(filePath), kind));
238
- schedule();
239
- }
240
-
241
- const initialOutcome = await runBuild(undefined, true);
242
- if (initialOutcome.kind === 'failed') {
243
- await emitWatchEvent(options, {
244
- kind: 'build-failed',
245
- initial: true,
246
- diagnostics: initialOutcome.diagnostics,
247
- meta: initialOutcome.meta,
248
- });
249
- } else if (initialOutcome.kind === 'succeeded') {
250
- applyCommitPlan(initialOutcome.result.commit);
251
- snapshot = initialOutcome.result.snapshot;
252
- await emitWatchEvent(options, {
253
- kind: 'build-succeeded',
254
- initial: true,
255
- changed: !isNoopCommit(initialOutcome.result.commit),
256
- snapshot: initialOutcome.result.snapshot,
257
- meta: initialOutcome.result.meta,
258
- diagnostics: initialOutcome.result.diagnostics || [],
259
- });
260
- }
261
-
262
- const watcherEntries = options.compiler
263
- .getWatchTargets()
264
- .map(target => ({
265
- target,
266
- watcher: chokidar.watch(target.path, {
267
- ignoreInitial: true,
268
- atomic: true,
269
- awaitWriteFinish: { stabilityThreshold: 100 },
270
- depth: target.chokidar?.depth,
271
- }),
272
- }));
273
-
274
- let readyCount = 0;
275
- const readyGoal = watcherEntries.length;
276
- const onReady = async () => {
277
- readyCount += 1;
278
- if (readyCount === readyGoal) {
279
- await emitWatchEvent(options, { kind: 'watching' });
280
- }
281
- };
282
-
283
- for (const { watcher, target } of watcherEntries) {
284
- const emitIfIncluded = (filePath: string, kind: WatchEventKind) => {
285
- if (!target.filter || target.filter(filePath)) {
286
- onFileChange(filePath, kind);
287
- }
288
- };
289
- watcher.on('add', filePath => emitIfIncluded(filePath, 'add'));
290
- watcher.on('change', filePath => emitIfIncluded(filePath, 'change'));
291
- watcher.on('unlink', filePath => emitIfIncluded(filePath, 'unlink'));
292
- watcher.on('ready', () => void onReady());
293
- }
294
-
295
- return new Promise(() => {});
296
- }
package/watch/events.ts DELETED
@@ -1,10 +0,0 @@
1
- import type { WatchEngineOptions, WatchLifecycleEvent } from './types';
2
-
3
- export async function emitWatchEvent<Snapshot, Plan, Meta>(
4
- options: WatchEngineOptions<Snapshot, Plan, Meta>,
5
- event: WatchLifecycleEvent<Snapshot, Meta>,
6
- ): Promise<void> {
7
- if (options.onEvent) {
8
- await options.onEvent(event);
9
- }
10
- }
package/watch/types.d.ts DELETED
@@ -1,103 +0,0 @@
1
- export type WatchEventKind = 'add' | 'change' | 'unlink';
2
-
3
- export interface FileChange {
4
- path: string;
5
- kind: WatchEventKind;
6
- }
7
-
8
- export interface ChangeBatch {
9
- changes: FileChange[];
10
- }
11
-
12
- export interface WatchTarget {
13
- path: string;
14
- chokidar?: { depth?: number };
15
- filter?: (filePath: string) => boolean;
16
- }
17
-
18
- export interface WatchDiagnostic {
19
- message: string;
20
- }
21
-
22
- export interface WriteFileMutation {
23
- kind: 'write';
24
- path: string;
25
- content: string | Buffer;
26
- }
27
-
28
- export interface DeleteFileMutation {
29
- kind: 'delete';
30
- path: string;
31
- }
32
-
33
- export type FileMutation = WriteFileMutation | DeleteFileMutation;
34
-
35
- export interface ReplaceRootCommitPlan {
36
- kind: 'replace-root';
37
- stagedPath: string;
38
- targetPath: string;
39
- }
40
-
41
- export interface ApplyMutationsCommitPlan {
42
- kind: 'apply-mutations';
43
- rootDir: string;
44
- mutations: FileMutation[];
45
- }
46
-
47
- export type CommitPlan = ReplaceRootCommitPlan | ApplyMutationsCommitPlan;
48
-
49
- export type CompilerPlanResult<Plan> =
50
- | { kind: 'skip' }
51
- | { kind: 'build'; plan: Plan };
52
-
53
- export type CompilerBuildResult<Snapshot, Meta> =
54
- | {
55
- ok: true;
56
- snapshot: Snapshot;
57
- commit: CommitPlan;
58
- diagnostics?: WatchDiagnostic[];
59
- meta?: Meta;
60
- }
61
- | { ok: false; diagnostics: WatchDiagnostic[]; meta?: Meta };
62
-
63
- export interface WatchCompiler<Snapshot, Plan, Meta> {
64
- getWatchTargets(): WatchTarget[];
65
- buildInitial(): Promise<CompilerBuildResult<Snapshot, Meta>>;
66
- plan(
67
- snapshot: Snapshot | undefined,
68
- batch: ChangeBatch,
69
- ): Promise<CompilerPlanResult<Plan>>;
70
- run(
71
- plan: Plan,
72
- snapshot: Snapshot | undefined,
73
- ): Promise<CompilerBuildResult<Snapshot, Meta>>;
74
- }
75
-
76
- export type WatchLifecycleEvent<Snapshot, Meta> =
77
- | { kind: 'watching' }
78
- | { kind: 'build-started'; batch?: ChangeBatch; initial: boolean }
79
- | {
80
- kind: 'build-succeeded';
81
- initial: boolean;
82
- batch?: ChangeBatch;
83
- changed: boolean;
84
- snapshot: Snapshot;
85
- meta?: Meta;
86
- diagnostics: WatchDiagnostic[];
87
- }
88
- | {
89
- kind: 'build-failed';
90
- initial: boolean;
91
- batch?: ChangeBatch;
92
- diagnostics: WatchDiagnostic[];
93
- meta?: Meta;
94
- }
95
- | { kind: 'build-skipped'; batch: ChangeBatch };
96
-
97
- export interface WatchEngineOptions<Snapshot, Plan, Meta> {
98
- compiler: WatchCompiler<Snapshot, Plan, Meta>;
99
- debounceMs?: number;
100
- onEvent?: (
101
- event: WatchLifecycleEvent<Snapshot, Meta>,
102
- ) => void | Promise<void>;
103
- }