@abreen/tada 1.13.7 → 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.
- package/build/generate-content-assets.ts +37 -175
- package/build/pipeline.ts +1 -1
- package/build/source-model.ts +2 -14
- package/build/types.d.ts +0 -12
- package/build/watch/build-full.ts +26 -52
- package/build/watch/build-helpers.ts +66 -0
- package/build/watch/build-incremental.ts +32 -60
- package/build/watch/build-result.ts +19 -0
- package/build/watch/compiler.ts +25 -61
- package/build/watch/config-paths.ts +56 -0
- package/build/watch/engine.ts +199 -0
- package/{watch → build/watch}/fs-commit.ts +1 -3
- package/build/watch/index.ts +1 -1
- package/build/watch/mutations.ts +23 -25
- package/build/watch/planner.ts +14 -78
- package/build/watch/runtime.ts +26 -38
- package/build/watch/snapshot.ts +26 -47
- package/build/watch/types.d.ts +84 -0
- package/build/watch/validation.ts +1 -1
- package/package.json +1 -2
- package/build/watch-reachability-state.ts +0 -305
- package/watch/engine.ts +0 -296
- package/watch/events.ts +0 -10
- package/watch/types.d.ts +0 -103
|
@@ -1,32 +1,23 @@
|
|
|
1
1
|
import { compileTemplates, config } from '../templates';
|
|
2
2
|
import { getDistDir } from '../util';
|
|
3
|
-
import {
|
|
4
|
-
import type {
|
|
5
|
-
import type {
|
|
6
|
-
|
|
7
|
-
TraceCache,
|
|
8
|
-
WatchTraceOptions,
|
|
9
|
-
} from './compiler-types';
|
|
10
|
-
import type { TadaWatchPlan } from './planner';
|
|
11
|
-
import {
|
|
12
|
-
collectHtmlAnalysisByPath,
|
|
13
|
-
collectHtmlAssetsByPath,
|
|
14
|
-
createSnapshot,
|
|
15
|
-
type TadaSnapshot,
|
|
16
|
-
} from './snapshot';
|
|
3
|
+
import type { CompilerBuildResult } from './types';
|
|
4
|
+
import type { TraceCache, WatchTraceOptions } from './compiler-types';
|
|
5
|
+
import type { TadaIncrementalWatchPlan } from './planner';
|
|
6
|
+
import { createSnapshot, type TadaSnapshot } from './snapshot';
|
|
17
7
|
import {
|
|
18
8
|
copyExistingBuildAssets,
|
|
19
9
|
ensureHighlighter,
|
|
20
10
|
makeTempBuildDir,
|
|
21
11
|
removeDirIfExists,
|
|
22
|
-
writeAssets,
|
|
23
12
|
} from './assets';
|
|
24
13
|
import { computeMutations } from './mutations';
|
|
14
|
+
import { validateConfig, validateProjectConfigLinks } from './validation';
|
|
15
|
+
import { buildFailedFromError, buildSucceeded } from './build-result';
|
|
25
16
|
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
} from './
|
|
17
|
+
buildFailedWithDiagnostics,
|
|
18
|
+
renderContentRecord,
|
|
19
|
+
renderPublicRecord,
|
|
20
|
+
} from './build-helpers';
|
|
30
21
|
|
|
31
22
|
export async function buildIncremental({
|
|
32
23
|
plan,
|
|
@@ -34,23 +25,23 @@ export async function buildIncremental({
|
|
|
34
25
|
traceCache,
|
|
35
26
|
traceOptions,
|
|
36
27
|
}: {
|
|
37
|
-
plan:
|
|
28
|
+
plan: TadaIncrementalWatchPlan;
|
|
38
29
|
snapshot: TadaSnapshot;
|
|
39
30
|
traceCache: TraceCache;
|
|
40
31
|
traceOptions: WatchTraceOptions;
|
|
41
|
-
}): Promise<CompilerBuildResult
|
|
42
|
-
const
|
|
32
|
+
}): Promise<CompilerBuildResult> {
|
|
33
|
+
const distDir = getDistDir();
|
|
34
|
+
const outputDir = makeTempBuildDir(distDir);
|
|
43
35
|
try {
|
|
44
36
|
const siteVariables = snapshot.siteVariables;
|
|
45
37
|
const configDiagnostics = validateConfig(plan.scan);
|
|
46
38
|
if (configDiagnostics.length > 0) {
|
|
47
|
-
|
|
48
|
-
return { ok: false, diagnostics: configDiagnostics };
|
|
39
|
+
return buildFailedWithDiagnostics(outputDir, configDiagnostics);
|
|
49
40
|
}
|
|
50
41
|
|
|
51
42
|
compileTemplates(siteVariables);
|
|
52
43
|
await ensureHighlighter(siteVariables);
|
|
53
|
-
copyExistingBuildAssets(
|
|
44
|
+
copyExistingBuildAssets(distDir, outputDir, snapshot.assetFiles);
|
|
54
45
|
|
|
55
46
|
const nextContentRecords = new Map(snapshot.contentRecords);
|
|
56
47
|
const nextPublicRecords = new Map(snapshot.publicRecords);
|
|
@@ -64,25 +55,27 @@ export async function buildIncremental({
|
|
|
64
55
|
|
|
65
56
|
for (const sourcePath of plan.contentToRender) {
|
|
66
57
|
nextContentRecords.delete(sourcePath);
|
|
67
|
-
const record =
|
|
58
|
+
const record = renderContentRecord({
|
|
68
59
|
filePath: sourcePath,
|
|
69
60
|
siteVariables,
|
|
70
61
|
scan: plan.scan,
|
|
71
62
|
assetFiles: snapshot.assetFiles,
|
|
72
63
|
outputDir,
|
|
73
64
|
traceCache,
|
|
74
|
-
|
|
75
|
-
cachedTraceSourceDir:
|
|
65
|
+
traceOptions,
|
|
66
|
+
cachedTraceSourceDir: distDir,
|
|
76
67
|
});
|
|
77
|
-
if (record
|
|
78
|
-
writeAssets(outputDir, record.outputs);
|
|
68
|
+
if (record) {
|
|
79
69
|
nextContentRecords.set(sourcePath, record);
|
|
80
70
|
}
|
|
81
71
|
}
|
|
82
72
|
|
|
83
73
|
for (const sourcePath of plan.publicToRender) {
|
|
84
74
|
nextPublicRecords.delete(sourcePath);
|
|
85
|
-
const record =
|
|
75
|
+
const record = renderPublicRecord({
|
|
76
|
+
filePath: sourcePath,
|
|
77
|
+
publicDir: plan.scan.publicDir,
|
|
78
|
+
});
|
|
86
79
|
nextPublicRecords.set(sourcePath, record);
|
|
87
80
|
}
|
|
88
81
|
|
|
@@ -97,11 +90,10 @@ export async function buildIncremental({
|
|
|
97
90
|
});
|
|
98
91
|
|
|
99
92
|
const linkDiagnostics = validateProjectConfigLinks(
|
|
100
|
-
nextSnapshot.validTargets,
|
|
93
|
+
nextSnapshot.scan.validTargets,
|
|
101
94
|
);
|
|
102
95
|
if (linkDiagnostics.length > 0) {
|
|
103
|
-
|
|
104
|
-
return { ok: false, diagnostics: linkDiagnostics };
|
|
96
|
+
return buildFailedWithDiagnostics(outputDir, linkDiagnostics);
|
|
105
97
|
}
|
|
106
98
|
|
|
107
99
|
const forceSourcePaths = new Set([
|
|
@@ -112,35 +104,15 @@ export async function buildIncremental({
|
|
|
112
104
|
snapshot,
|
|
113
105
|
nextSnapshot,
|
|
114
106
|
forceSourcePaths,
|
|
115
|
-
).map(mutation =>
|
|
116
|
-
mutation.kind === 'write'
|
|
117
|
-
? {
|
|
118
|
-
kind: 'write' as const,
|
|
119
|
-
path: mutation.path,
|
|
120
|
-
content: mutation.content!,
|
|
121
|
-
}
|
|
122
|
-
: { kind: 'delete' as const, path: mutation.path },
|
|
123
107
|
);
|
|
124
108
|
removeDirIfExists(outputDir);
|
|
125
|
-
return {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
htmlAssetsByPath: collectHtmlAssetsByPath(nextSnapshot.contentRecords),
|
|
131
|
-
htmlAnalysisByPath: collectHtmlAnalysisByPath(
|
|
132
|
-
nextSnapshot.contentRecords,
|
|
133
|
-
),
|
|
134
|
-
siteVariables,
|
|
135
|
-
},
|
|
136
|
-
};
|
|
109
|
+
return buildSucceeded(nextSnapshot, {
|
|
110
|
+
kind: 'apply-mutations',
|
|
111
|
+
rootDir: distDir,
|
|
112
|
+
mutations,
|
|
113
|
+
});
|
|
137
114
|
} catch (error) {
|
|
138
115
|
removeDirIfExists(outputDir);
|
|
139
|
-
return
|
|
140
|
-
ok: false,
|
|
141
|
-
diagnostics: diagnosticsFromMessages([
|
|
142
|
-
error instanceof Error ? error.message : String(error),
|
|
143
|
-
]),
|
|
144
|
-
};
|
|
116
|
+
return buildFailedFromError(error);
|
|
145
117
|
}
|
|
146
118
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CommitPlan, CompilerBuildResult } from './types';
|
|
2
|
+
import { createBuildMeta, type TadaSnapshot } from './snapshot';
|
|
3
|
+
import { diagnosticsFromMessages } from './validation';
|
|
4
|
+
|
|
5
|
+
export function buildSucceeded(
|
|
6
|
+
snapshot: TadaSnapshot,
|
|
7
|
+
commit: CommitPlan,
|
|
8
|
+
): CompilerBuildResult {
|
|
9
|
+
return { ok: true, snapshot, commit, meta: createBuildMeta(snapshot) };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function buildFailedFromError(error: unknown): CompilerBuildResult {
|
|
13
|
+
return {
|
|
14
|
+
ok: false,
|
|
15
|
+
diagnostics: diagnosticsFromMessages([
|
|
16
|
+
error instanceof Error ? error.message : String(error),
|
|
17
|
+
]),
|
|
18
|
+
};
|
|
19
|
+
}
|
package/build/watch/compiler.ts
CHANGED
|
@@ -1,30 +1,20 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import {
|
|
3
|
-
getProjectConfigBaseName,
|
|
4
|
-
getSiteConfigBaseName,
|
|
5
|
-
getSupportedConfigFilePaths,
|
|
6
|
-
} from '../config-files';
|
|
7
|
-
import { getDevSiteVariables } from '../site-variables';
|
|
8
2
|
import { getProjectConfigDir } from '../templates';
|
|
9
3
|
import { getContentDir, getPublicDir } from '../util';
|
|
10
4
|
import type {
|
|
11
5
|
ChangeBatch,
|
|
12
6
|
CompilerBuildResult,
|
|
13
|
-
CompilerPlanResult,
|
|
14
7
|
WatchCompiler,
|
|
15
8
|
WatchTarget,
|
|
16
|
-
} from '
|
|
9
|
+
} from './types';
|
|
17
10
|
import { buildFull } from './build-full';
|
|
18
11
|
import { buildIncremental } from './build-incremental';
|
|
19
|
-
import type {
|
|
20
|
-
TadaBuildMeta,
|
|
21
|
-
TraceCache,
|
|
22
|
-
WatchTraceOptions,
|
|
23
|
-
} from './compiler-types';
|
|
12
|
+
import type { TraceCache, WatchTraceOptions } from './compiler-types';
|
|
24
13
|
import { checkTraceToolAvailability, isTraceSourceFile } from '../utils/trace';
|
|
25
|
-
import { createTadaWatchPlan
|
|
26
|
-
import {
|
|
14
|
+
import { createTadaWatchPlan } from './planner';
|
|
15
|
+
import { updateProjectScan } from '../source-model';
|
|
27
16
|
import type { TadaSnapshot } from './snapshot';
|
|
17
|
+
import { getWatchConfigFilePaths } from './config-paths';
|
|
28
18
|
|
|
29
19
|
export function invalidateTraceCacheForBatch(
|
|
30
20
|
traceCache: TraceCache,
|
|
@@ -38,11 +28,7 @@ export function invalidateTraceCacheForBatch(
|
|
|
38
28
|
}
|
|
39
29
|
}
|
|
40
30
|
|
|
41
|
-
export class TadaWatchCompiler implements WatchCompiler
|
|
42
|
-
TadaSnapshot,
|
|
43
|
-
TadaWatchPlan,
|
|
44
|
-
TadaBuildMeta
|
|
45
|
-
> {
|
|
31
|
+
export class TadaWatchCompiler implements WatchCompiler {
|
|
46
32
|
private traceCache: TraceCache;
|
|
47
33
|
private traceOptions: WatchTraceOptions;
|
|
48
34
|
|
|
@@ -55,23 +41,11 @@ export class TadaWatchCompiler implements WatchCompiler<
|
|
|
55
41
|
const contentDir = getContentDir();
|
|
56
42
|
const publicDir = getPublicDir();
|
|
57
43
|
const projectConfigDir = path.resolve(getProjectConfigDir());
|
|
58
|
-
const configFilePaths =
|
|
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)),
|
|
70
|
-
]);
|
|
44
|
+
const configFilePaths = getWatchConfigFilePaths();
|
|
71
45
|
|
|
72
46
|
return [
|
|
73
|
-
{ path: contentDir },
|
|
74
|
-
{ path: publicDir },
|
|
47
|
+
{ path: contentDir, chokidar: { usePolling: true } },
|
|
48
|
+
{ path: publicDir, chokidar: { usePolling: true } },
|
|
75
49
|
{
|
|
76
50
|
path: projectConfigDir,
|
|
77
51
|
chokidar: { depth: 0 },
|
|
@@ -80,34 +54,24 @@ export class TadaWatchCompiler implements WatchCompiler<
|
|
|
80
54
|
];
|
|
81
55
|
}
|
|
82
56
|
|
|
83
|
-
async
|
|
84
|
-
CompilerBuildResult<TadaSnapshot, TadaBuildMeta>
|
|
85
|
-
> {
|
|
86
|
-
return buildFull({
|
|
87
|
-
traceCache: this.traceCache,
|
|
88
|
-
traceOptions: this.traceOptions,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async plan(
|
|
57
|
+
async build(
|
|
93
58
|
snapshot: TadaSnapshot | undefined,
|
|
94
|
-
batch
|
|
95
|
-
): Promise<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
: scanProject(getDevSiteVariables());
|
|
100
|
-
return {
|
|
101
|
-
kind: 'build',
|
|
102
|
-
plan: createTadaWatchPlan({ snapshot, batch, scan }),
|
|
103
|
-
};
|
|
104
|
-
}
|
|
59
|
+
batch?: ChangeBatch,
|
|
60
|
+
): Promise<CompilerBuildResult> {
|
|
61
|
+
if (batch) {
|
|
62
|
+
invalidateTraceCacheForBatch(this.traceCache, batch);
|
|
63
|
+
}
|
|
105
64
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
65
|
+
if (!snapshot || !batch) {
|
|
66
|
+
return buildFull({
|
|
67
|
+
traceCache: this.traceCache,
|
|
68
|
+
traceOptions: this.traceOptions,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const scan = updateProjectScan(snapshot.scan, batch);
|
|
73
|
+
const plan = createTadaWatchPlan({ snapshot, batch, scan });
|
|
74
|
+
if (plan.kind === 'full') {
|
|
111
75
|
return buildFull({
|
|
112
76
|
traceCache: this.traceCache,
|
|
113
77
|
traceOptions: this.traceOptions,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import {
|
|
3
|
+
getProjectConfigBaseName,
|
|
4
|
+
getSiteConfigBaseName,
|
|
5
|
+
getSupportedConfigFilePaths,
|
|
6
|
+
} from '../config-files';
|
|
7
|
+
import { getProjectConfigDir } from '../templates';
|
|
8
|
+
|
|
9
|
+
export type WatchConfigPathKind = 'site' | 'nav' | 'authors';
|
|
10
|
+
|
|
11
|
+
function getConfigPathEntries(): Array<{
|
|
12
|
+
kind: WatchConfigPathKind;
|
|
13
|
+
paths: string[];
|
|
14
|
+
}> {
|
|
15
|
+
const projectConfigDir = getProjectConfigDir();
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
kind: 'site',
|
|
19
|
+
paths: getSupportedConfigFilePaths('.', getSiteConfigBaseName('dev')),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
kind: 'nav',
|
|
23
|
+
paths: getSupportedConfigFilePaths(
|
|
24
|
+
projectConfigDir,
|
|
25
|
+
getProjectConfigBaseName('nav'),
|
|
26
|
+
),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
kind: 'authors',
|
|
30
|
+
paths: getSupportedConfigFilePaths(
|
|
31
|
+
projectConfigDir,
|
|
32
|
+
getProjectConfigBaseName('authors'),
|
|
33
|
+
),
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getWatchConfigPathKinds(): Map<string, WatchConfigPathKind> {
|
|
39
|
+
return new Map(
|
|
40
|
+
getConfigPathEntries().flatMap(entry =>
|
|
41
|
+
entry.paths.map(
|
|
42
|
+
filePath => [path.resolve(filePath), entry.kind] as const,
|
|
43
|
+
),
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getWatchConfigFilePaths(): Set<string> {
|
|
49
|
+
return new Set(getWatchConfigPathKinds().keys());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function classifyWatchConfigPath(
|
|
53
|
+
filePath: string,
|
|
54
|
+
): WatchConfigPathKind | undefined {
|
|
55
|
+
return getWatchConfigPathKinds().get(path.resolve(filePath));
|
|
56
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import chokidar from 'chokidar';
|
|
2
|
+
import { applyCommitPlan } from './fs-commit';
|
|
3
|
+
import type {
|
|
4
|
+
ChangeBatch,
|
|
5
|
+
CompilerBuildResult,
|
|
6
|
+
WatchEngineOptions,
|
|
7
|
+
WatchEventKind,
|
|
8
|
+
WatchLifecycleEvent,
|
|
9
|
+
} from './types';
|
|
10
|
+
import type { TadaSnapshot } from './snapshot';
|
|
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 mapBatch(batch: ChangeBatch): Map<string, WatchEventKind> {
|
|
21
|
+
return new Map(batch.changes.map(change => [change.path, change.kind]));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function mergeChangeKind(
|
|
25
|
+
previous: WatchEventKind | undefined,
|
|
26
|
+
next: WatchEventKind,
|
|
27
|
+
): WatchEventKind {
|
|
28
|
+
if (!previous) {
|
|
29
|
+
return next;
|
|
30
|
+
}
|
|
31
|
+
if (previous === 'add' && next === 'unlink') {
|
|
32
|
+
return 'change';
|
|
33
|
+
}
|
|
34
|
+
if (previous === 'unlink' && next === 'add') {
|
|
35
|
+
return 'change';
|
|
36
|
+
}
|
|
37
|
+
if (previous === 'add' || next === 'add') {
|
|
38
|
+
return 'add';
|
|
39
|
+
}
|
|
40
|
+
if (previous === 'unlink' || next === 'unlink') {
|
|
41
|
+
return 'unlink';
|
|
42
|
+
}
|
|
43
|
+
return 'change';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function toDiagnostics(error: unknown): { message: string }[] {
|
|
47
|
+
if (error instanceof Error) {
|
|
48
|
+
return [{ message: error.message }];
|
|
49
|
+
}
|
|
50
|
+
return [{ message: String(error) }];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function runWatchEngine(
|
|
54
|
+
options: WatchEngineOptions,
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
const debounceMs = options.debounceMs ?? 300;
|
|
57
|
+
let snapshot: TadaSnapshot | undefined;
|
|
58
|
+
let uncommitted = new Map<string, WatchEventKind>();
|
|
59
|
+
let pending = new Map<string, WatchEventKind>();
|
|
60
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
61
|
+
let running = false;
|
|
62
|
+
|
|
63
|
+
async function emit(event: WatchLifecycleEvent): Promise<void> {
|
|
64
|
+
await options.onEvent?.(event);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function waitForQuietPeriod(): Promise<void> {
|
|
68
|
+
await new Promise(resolve => setTimeout(resolve, debounceMs));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function runBuild(batch?: ChangeBatch): Promise<CompilerBuildResult> {
|
|
72
|
+
await emit({ kind: 'build-started', batch });
|
|
73
|
+
try {
|
|
74
|
+
return await options.compiler.build(snapshot, batch);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return { ok: false, diagnostics: toDiagnostics(error) };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function flush(): Promise<void> {
|
|
81
|
+
if (running) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
running = true;
|
|
85
|
+
try {
|
|
86
|
+
let finalSuccess:
|
|
87
|
+
| {
|
|
88
|
+
batch?: ChangeBatch;
|
|
89
|
+
result: Extract<CompilerBuildResult, { ok: true }>;
|
|
90
|
+
}
|
|
91
|
+
| undefined;
|
|
92
|
+
|
|
93
|
+
while (pending.size > 0) {
|
|
94
|
+
const changes = new Map(uncommitted);
|
|
95
|
+
for (const [filePath, kind] of pending) {
|
|
96
|
+
changes.set(filePath, mergeChangeKind(changes.get(filePath), kind));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const batch = normalizeBatch(changes);
|
|
100
|
+
pending = new Map();
|
|
101
|
+
const outcome = await runBuild(batch);
|
|
102
|
+
|
|
103
|
+
if (!outcome.ok) {
|
|
104
|
+
uncommitted = mapBatch(batch);
|
|
105
|
+
await emit({
|
|
106
|
+
kind: 'build-failed',
|
|
107
|
+
batch,
|
|
108
|
+
diagnostics: outcome.diagnostics,
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
applyCommitPlan(outcome.commit);
|
|
114
|
+
snapshot = outcome.snapshot;
|
|
115
|
+
uncommitted = new Map();
|
|
116
|
+
finalSuccess = { batch, result: outcome };
|
|
117
|
+
|
|
118
|
+
if (pending.size === 0) {
|
|
119
|
+
await waitForQuietPeriod();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (finalSuccess) {
|
|
124
|
+
await emit({
|
|
125
|
+
kind: 'build-succeeded',
|
|
126
|
+
batch: finalSuccess.batch,
|
|
127
|
+
meta: finalSuccess.result.meta,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
} finally {
|
|
131
|
+
running = false;
|
|
132
|
+
if (pending.size > 0) {
|
|
133
|
+
schedule();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function schedule(): void {
|
|
139
|
+
if (timer) {
|
|
140
|
+
clearTimeout(timer);
|
|
141
|
+
}
|
|
142
|
+
timer = setTimeout(() => {
|
|
143
|
+
timer = null;
|
|
144
|
+
void flush();
|
|
145
|
+
}, debounceMs);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function onFileChange(filePath: string, kind: WatchEventKind): void {
|
|
149
|
+
pending.set(filePath, mergeChangeKind(pending.get(filePath), kind));
|
|
150
|
+
schedule();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const startupOutcome = await runBuild();
|
|
154
|
+
if (!startupOutcome.ok) {
|
|
155
|
+
await emit({
|
|
156
|
+
kind: 'build-failed',
|
|
157
|
+
diagnostics: startupOutcome.diagnostics,
|
|
158
|
+
});
|
|
159
|
+
} else {
|
|
160
|
+
applyCommitPlan(startupOutcome.commit);
|
|
161
|
+
snapshot = startupOutcome.snapshot;
|
|
162
|
+
await emit({ kind: 'build-succeeded', meta: startupOutcome.meta });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const watcherEntries = options.compiler
|
|
166
|
+
.getWatchTargets()
|
|
167
|
+
.map(target => ({
|
|
168
|
+
target,
|
|
169
|
+
watcher: chokidar.watch(target.path, {
|
|
170
|
+
ignoreInitial: true,
|
|
171
|
+
atomic: true,
|
|
172
|
+
awaitWriteFinish: { stabilityThreshold: 100 },
|
|
173
|
+
...target.chokidar,
|
|
174
|
+
}),
|
|
175
|
+
}));
|
|
176
|
+
|
|
177
|
+
let readyCount = 0;
|
|
178
|
+
const readyGoal = watcherEntries.length;
|
|
179
|
+
const onReady = async () => {
|
|
180
|
+
readyCount += 1;
|
|
181
|
+
if (readyCount === readyGoal) {
|
|
182
|
+
await emit({ kind: 'watching' });
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
for (const { watcher, target } of watcherEntries) {
|
|
187
|
+
const emitIfIncluded = (filePath: string, kind: WatchEventKind) => {
|
|
188
|
+
if (!target.filter || target.filter(filePath)) {
|
|
189
|
+
onFileChange(filePath, kind);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
watcher.on('add', filePath => emitIfIncluded(filePath, 'add'));
|
|
193
|
+
watcher.on('change', filePath => emitIfIncluded(filePath, 'change'));
|
|
194
|
+
watcher.on('unlink', filePath => emitIfIncluded(filePath, 'unlink'));
|
|
195
|
+
watcher.on('ready', () => void onReady());
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return new Promise(() => {});
|
|
199
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { globals, type Globals } from '../
|
|
3
|
+
import { globals, type Globals } from '../globals';
|
|
4
4
|
import type {
|
|
5
5
|
ApplyMutationsCommitPlan,
|
|
6
6
|
CommitPlan,
|
|
@@ -87,8 +87,6 @@ function applyFileMutation(
|
|
|
87
87
|
const stagedPath = path.join(stagedRoot, mutation.path);
|
|
88
88
|
|
|
89
89
|
if (mutation.kind === 'write') {
|
|
90
|
-
ensureParentDir(stagedPath);
|
|
91
|
-
fs.writeFileSync(stagedPath, mutation.content);
|
|
92
90
|
ensureParentDir(targetPath);
|
|
93
91
|
renameWithRetry(stagedPath, targetPath, globals);
|
|
94
92
|
return;
|
package/build/watch/index.ts
CHANGED
package/build/watch/mutations.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FileMutation } from './types';
|
|
2
|
+
import type { TadaOutputOwner, TadaSnapshot } from './snapshot';
|
|
2
3
|
|
|
3
4
|
function outputsEqual(
|
|
4
5
|
left: string | Buffer | undefined,
|
|
@@ -15,16 +16,25 @@ function outputsEqual(
|
|
|
15
16
|
return Buffer.compare(leftBuffer, rightBuffer) === 0;
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
function getOutputContent(
|
|
20
|
+
snapshot: TadaSnapshot,
|
|
21
|
+
owner: TadaOutputOwner | undefined,
|
|
22
|
+
outputPath: string,
|
|
23
|
+
): string | Buffer | undefined {
|
|
24
|
+
if (!owner) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const records =
|
|
28
|
+
owner.kind === 'content' ? snapshot.contentRecords : snapshot.publicRecords;
|
|
29
|
+
return records.get(owner.sourcePath)?.outputs.get(outputPath);
|
|
30
|
+
}
|
|
31
|
+
|
|
18
32
|
export function computeMutations(
|
|
19
33
|
previous: TadaSnapshot,
|
|
20
34
|
next: TadaSnapshot,
|
|
21
35
|
forceSourcePaths: Set<string> = new Set(),
|
|
22
|
-
):
|
|
23
|
-
const mutations:
|
|
24
|
-
path: string;
|
|
25
|
-
kind: 'write' | 'delete';
|
|
26
|
-
content?: string | Buffer;
|
|
27
|
-
}[] = [];
|
|
36
|
+
): FileMutation[] {
|
|
37
|
+
const mutations: FileMutation[] = [];
|
|
28
38
|
const allPaths = new Set([
|
|
29
39
|
...previous.outputOwners.keys(),
|
|
30
40
|
...next.outputOwners.keys(),
|
|
@@ -34,24 +44,12 @@ export function computeMutations(
|
|
|
34
44
|
const previousOwner = previous.outputOwners.get(outputPath);
|
|
35
45
|
const nextOwner = next.outputOwners.get(outputPath);
|
|
36
46
|
|
|
37
|
-
const previousContent =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
? previous.publicRecords
|
|
44
|
-
.get(previousOwner.sourcePath)
|
|
45
|
-
?.outputs.get(outputPath)
|
|
46
|
-
: undefined;
|
|
47
|
-
const nextContent =
|
|
48
|
-
nextOwner?.kind === 'content'
|
|
49
|
-
? next.contentRecords.get(nextOwner.sourcePath)?.outputs.get(outputPath)
|
|
50
|
-
: nextOwner
|
|
51
|
-
? next.publicRecords
|
|
52
|
-
.get(nextOwner.sourcePath)
|
|
53
|
-
?.outputs.get(outputPath)
|
|
54
|
-
: undefined;
|
|
47
|
+
const previousContent = getOutputContent(
|
|
48
|
+
previous,
|
|
49
|
+
previousOwner,
|
|
50
|
+
outputPath,
|
|
51
|
+
);
|
|
52
|
+
const nextContent = getOutputContent(next, nextOwner, outputPath);
|
|
55
53
|
|
|
56
54
|
if (!nextOwner) {
|
|
57
55
|
mutations.push({ path: outputPath, kind: 'delete' });
|