@abreen/tada 1.17.1 → 1.18.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.
- package/README.md +37 -6
- package/bin/tada.ts +16 -7
- package/bin/validators.ts +33 -0
- package/build/branding.ts +41 -0
- package/build/build-manifest.ts +9 -4
- package/build/{watch/compiler-types.d.ts → build-types.d.ts} +8 -5
- package/build/build-validation.ts +176 -0
- package/build/bundle.ts +75 -65
- package/build/config-loader.ts +3 -3
- package/build/custom-fonts.ts +8 -5
- package/build/generate-favicon.ts +7 -6
- package/build/generate-fonts.ts +8 -15
- package/build/generate-katex-assets.ts +19 -17
- package/build/generate-web-app-manifest.ts +3 -11
- package/build/lodash-template.ts +14 -0
- package/build/output-publication.ts +238 -0
- package/build/pagefind.ts +49 -21
- package/build/pdf-text.ts +41 -1
- package/build/pipeline.ts +95 -101
- package/build/site-assets.ts +46 -0
- package/build/site-build.ts +267 -0
- package/build/source-model.ts +223 -244
- package/build/source-records.ts +69 -81
- package/build/template-globals.ts +3 -0
- package/build/templates.ts +5 -2
- package/build/toc-plugin.ts +21 -21
- package/build/types.d.ts +38 -38
- package/build/util.ts +0 -4
- package/build/utils/code.ts +38 -101
- package/build/utils/final-html.ts +171 -47
- package/build/utils/literate-java.ts +11 -7
- package/build/utils/markdown-partials.ts +4 -3
- package/build/utils/markdown.ts +6 -17
- package/build/utils/paths.ts +3 -0
- package/build/utils/plain-text.ts +18 -0
- package/build/utils/render.ts +197 -154
- package/build/utils/shiki-highlighter.ts +11 -0
- package/build/utils/source-template.ts +2 -2
- package/build/utils/trace-core.ts +8 -83
- package/build/utils/trace.ts +38 -95
- package/build/validate-config-links.ts +5 -5
- package/build/watch/build-incremental.ts +46 -103
- package/build/watch/compiler.ts +158 -37
- package/build/watch/engine.ts +207 -176
- package/build/watch/index.ts +9 -2
- package/build/watch/planner.ts +88 -144
- package/build/watch/runtime.ts +21 -34
- package/build/watch/types.d.ts +32 -64
- package/package.json +4 -4
- package/schema/site.schema.json +18 -2
- package/src/_content.scss +447 -0
- package/src/_fonts.scss +90 -0
- package/src/_layout.scss +50 -35
- package/src/appearance-picker/{style.scss → _index.scss} +1 -1
- package/src/code/{style.scss → _index.scss} +61 -0
- package/src/header/{_base.scss → _index.scss} +98 -6
- package/src/index.ts +1 -17
- package/src/navigate/{style.scss → _index.scss} +20 -0
- package/src/navigate/eligible.ts +0 -40
- package/src/navigate/index.ts +38 -4
- package/src/navigate/lifecycle.ts +19 -2
- package/src/navigate/runtime.ts +20 -4
- package/src/page-update/index.ts +33 -20
- package/src/print/{style.scss → _index.scss} +3 -1
- package/src/search/index.ts +7 -0
- package/src/slides/{style.scss → _index.scss} +5 -0
- package/src/style.scss +24 -536
- package/src/toc/index.ts +2 -2
- package/templates/_author.html +2 -2
- package/templates/_download.html +1 -1
- package/templates/_footer.html +1 -1
- package/templates/_heading.html +3 -3
- package/templates/_nav.html +2 -2
- package/templates/_page-bottom.html +6 -0
- package/templates/_top.html +18 -14
- package/templates/code.html +22 -21
- package/templates/default.html +12 -11
- package/templates/literate.html +6 -5
- package/build/copy.ts +0 -82
- package/build/generate-content-assets.ts +0 -121
- package/build/utils/content-files.ts +0 -170
- package/build/watch/assets.ts +0 -103
- package/build/watch/build-full.ts +0 -97
- package/build/watch/build-helpers.ts +0 -66
- package/build/watch/build-result.ts +0 -19
- package/build/watch/fs-commit.ts +0 -218
- package/build/watch/mutations.ts +0 -73
- package/build/watch/snapshot.ts +0 -201
- package/build/watch/validation.ts +0 -57
- package/src/code.scss +0 -60
- package/src/critical.scss +0 -5
- package/src/header/style.scss +0 -91
- /package/src/{literate.scss → _literate.scss} +0 -0
- /package/src/{material-symbols.scss → _material-symbols.scss} +0 -0
- /package/src/anchor/{style.scss → _index.scss} +0 -0
- /package/src/page-update/{style.scss → _index.scss} +0 -0
- /package/src/question/{style.scss → _index.scss} +0 -0
- /package/src/search/{style.scss → _index.scss} +0 -0
- /package/src/timezone/{style.scss → _index.scss} +0 -0
- /package/src/toc/{style.scss → _index.scss} +0 -0
- /package/src/trace/{style.scss → _index.scss} +0 -0
package/build/source-model.ts
CHANGED
|
@@ -15,34 +15,227 @@ import {
|
|
|
15
15
|
normalizeOutputPath,
|
|
16
16
|
toPosix,
|
|
17
17
|
} from './utils/paths';
|
|
18
|
-
import type { ChangeBatch } from './watch/types';
|
|
19
18
|
import type { SiteVariables } from './types';
|
|
20
19
|
|
|
20
|
+
export type TadaSourceRenderKind =
|
|
21
|
+
| 'skip'
|
|
22
|
+
| 'plain-text-page'
|
|
23
|
+
| 'literate-java'
|
|
24
|
+
| 'code-page'
|
|
25
|
+
| 'content-copy'
|
|
26
|
+
| 'public-copy';
|
|
27
|
+
|
|
28
|
+
export interface SourceEntry {
|
|
29
|
+
readonly kind: 'content' | 'public';
|
|
30
|
+
readonly renderKind: TadaSourceRenderKind;
|
|
31
|
+
readonly outputs: ReadonlySet<string>;
|
|
32
|
+
readonly targets: ReadonlySet<string>;
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
export interface TadaProjectScan {
|
|
22
|
-
contentDir: string;
|
|
23
|
-
publicDir: string;
|
|
24
|
-
distDir: string;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
readonly contentDir: string;
|
|
37
|
+
readonly publicDir: string;
|
|
38
|
+
readonly distDir: string;
|
|
39
|
+
readonly processedExts: ReadonlySet<string>;
|
|
40
|
+
readonly sources: ReadonlyMap<string, SourceEntry>;
|
|
41
|
+
readonly outputProducers: ReadonlyMap<string, ReadonlySet<string>>;
|
|
42
|
+
readonly codePageSourceTargets: ReadonlySet<string>;
|
|
43
|
+
readonly generatedPageTargets: ReadonlySet<string>;
|
|
44
|
+
readonly validTargets: ReadonlySet<string>;
|
|
45
|
+
readonly literateJavaOutputPaths: ReadonlySet<string>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function* sourcePaths(
|
|
49
|
+
scan: TadaProjectScan,
|
|
50
|
+
kind?: SourceEntry['kind'],
|
|
51
|
+
pagesOnly = false,
|
|
52
|
+
): Iterable<string> {
|
|
53
|
+
for (const [filePath, entry] of scan.sources) {
|
|
54
|
+
if (kind && entry.kind !== kind) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (
|
|
58
|
+
pagesOnly &&
|
|
59
|
+
['skip', 'content-copy', 'public-copy'].includes(entry.renderKind)
|
|
60
|
+
) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
yield filePath;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getSourceRenderKind(
|
|
68
|
+
filePath: string,
|
|
69
|
+
kind: SourceEntry['kind'],
|
|
70
|
+
processedExts: ReadonlySet<string>,
|
|
71
|
+
buildContent: boolean,
|
|
72
|
+
): TadaSourceRenderKind {
|
|
73
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
74
|
+
if (kind === 'public') {
|
|
75
|
+
return 'public-copy';
|
|
76
|
+
}
|
|
77
|
+
if (!processedExts.has(ext.slice(1))) {
|
|
78
|
+
return 'content-copy';
|
|
79
|
+
}
|
|
80
|
+
if (!buildContent) {
|
|
81
|
+
return 'skip';
|
|
82
|
+
}
|
|
83
|
+
if (isLiterateJava(filePath)) {
|
|
84
|
+
return 'literate-java';
|
|
85
|
+
}
|
|
86
|
+
return extensionIsMarkdown(ext) || ext === '.html'
|
|
87
|
+
? 'plain-text-page'
|
|
88
|
+
: 'code-page';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function createSourceEntry(
|
|
92
|
+
scan: Pick<TadaProjectScan, 'contentDir' | 'publicDir' | 'processedExts'>,
|
|
93
|
+
filePath: string,
|
|
94
|
+
kind: SourceEntry['kind'],
|
|
95
|
+
): SourceEntry {
|
|
96
|
+
const buildContent =
|
|
97
|
+
kind === 'content' && isBuildContentSource(filePath, scan.processedExts);
|
|
98
|
+
const renderKind = getSourceRenderKind(
|
|
99
|
+
filePath,
|
|
100
|
+
kind,
|
|
101
|
+
scan.processedExts,
|
|
102
|
+
buildContent,
|
|
103
|
+
);
|
|
104
|
+
const rootDir = kind === 'content' ? scan.contentDir : scan.publicDir;
|
|
105
|
+
return {
|
|
106
|
+
kind,
|
|
107
|
+
renderKind,
|
|
108
|
+
outputs:
|
|
109
|
+
kind === 'public'
|
|
110
|
+
? new Set([toPosix(path.relative(rootDir, filePath))])
|
|
111
|
+
: getSourceOutputPaths({
|
|
112
|
+
contentDir: rootDir,
|
|
113
|
+
filePath,
|
|
114
|
+
processedExts: scan.processedExts,
|
|
115
|
+
buildContent,
|
|
116
|
+
}),
|
|
117
|
+
targets: getSourceTargetPaths({
|
|
118
|
+
kind,
|
|
119
|
+
rootDir,
|
|
120
|
+
filePath,
|
|
121
|
+
processedExts: scan.processedExts,
|
|
122
|
+
buildContent,
|
|
123
|
+
}),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function indexSources(
|
|
128
|
+
roots: Pick<
|
|
129
|
+
TadaProjectScan,
|
|
130
|
+
'contentDir' | 'publicDir' | 'distDir' | 'processedExts'
|
|
131
|
+
>,
|
|
132
|
+
sources: ReadonlyMap<string, SourceEntry>,
|
|
133
|
+
): TadaProjectScan {
|
|
134
|
+
const outputProducers = new Map<string, Set<string>>();
|
|
135
|
+
const generatedPageTargets = new Set<string>();
|
|
136
|
+
const codePageSourceTargets = new Set<string>();
|
|
137
|
+
const validTargets = new Set<string>();
|
|
138
|
+
const literateJavaOutputPaths = new Set<string>();
|
|
139
|
+
for (const [filePath, entry] of sources) {
|
|
140
|
+
for (const output of entry.outputs) {
|
|
141
|
+
if (!outputProducers.has(output)) {
|
|
142
|
+
outputProducers.set(output, new Set());
|
|
143
|
+
}
|
|
144
|
+
outputProducers.get(output)!.add(filePath);
|
|
145
|
+
}
|
|
146
|
+
if (
|
|
147
|
+
['plain-text-page', 'code-page', 'literate-java'].includes(
|
|
148
|
+
entry.renderKind,
|
|
149
|
+
)
|
|
150
|
+
) {
|
|
151
|
+
for (const output of entry.outputs) {
|
|
152
|
+
if (output.endsWith('.html')) {
|
|
153
|
+
addGeneratedRouteAliases(generatedPageTargets, `/${output}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
for (const target of entry.targets) {
|
|
158
|
+
validTargets.add(target);
|
|
159
|
+
}
|
|
160
|
+
if (entry.renderKind === 'code-page') {
|
|
161
|
+
codePageSourceTargets.add(
|
|
162
|
+
normalizeOutputPath(
|
|
163
|
+
`/${toPosix(path.relative(roots.contentDir, filePath))}`,
|
|
164
|
+
),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (entry.renderKind === 'literate-java') {
|
|
168
|
+
const parsed = path.parse(path.relative(roots.contentDir, filePath));
|
|
169
|
+
literateJavaOutputPaths.add(
|
|
170
|
+
`/${toPosix(path.join(parsed.dir, parsed.name))}`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
...roots,
|
|
176
|
+
sources,
|
|
177
|
+
outputProducers,
|
|
178
|
+
validTargets,
|
|
179
|
+
generatedPageTargets,
|
|
180
|
+
codePageSourceTargets,
|
|
181
|
+
literateJavaOutputPaths,
|
|
182
|
+
};
|
|
35
183
|
}
|
|
36
184
|
|
|
185
|
+
export function scanProject(siteVariables: SiteVariables): TadaProjectScan {
|
|
186
|
+
const roots = {
|
|
187
|
+
contentDir: getContentDir(),
|
|
188
|
+
publicDir: getPublicDir(),
|
|
189
|
+
distDir: getDistDir(),
|
|
190
|
+
processedExts: getProcessedExts(
|
|
191
|
+
Object.keys(getExtensionToShikiLanguage(siteVariables)),
|
|
192
|
+
),
|
|
193
|
+
};
|
|
194
|
+
const sources = new Map<string, SourceEntry>();
|
|
195
|
+
for (const kind of ['content', 'public'] as const) {
|
|
196
|
+
const dir = kind === 'content' ? roots.contentDir : roots.publicDir;
|
|
197
|
+
for (const filePath of walkFiles(dir).sort()) {
|
|
198
|
+
sources.set(filePath, createSourceEntry(roots, filePath, kind));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return indexSources(roots, sources);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function assertNoOutputPathConflicts(scan: TadaProjectScan): string[] {
|
|
205
|
+
return [...scan.outputProducers]
|
|
206
|
+
.filter(([, producers]) => producers.size > 1)
|
|
207
|
+
.map(([output]) => output)
|
|
208
|
+
.sort();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Front matter `skip` results, reused while a file's size and modification
|
|
212
|
+
// time are unchanged, so watch rescans read only edited pages.
|
|
213
|
+
const skipCache = new Map<
|
|
214
|
+
string,
|
|
215
|
+
{ size: number; mtimeMs: number; skip: boolean }
|
|
216
|
+
>();
|
|
217
|
+
|
|
37
218
|
export function shouldSkipContentFile(filePath: string): boolean {
|
|
38
219
|
const ext = path.extname(filePath).toLowerCase();
|
|
39
220
|
if (!(extensionIsMarkdown(ext) || ext === '.html')) {
|
|
40
221
|
return false;
|
|
41
222
|
}
|
|
42
223
|
|
|
224
|
+
const { size, mtimeMs } = fs.statSync(filePath);
|
|
225
|
+
const cached = skipCache.get(filePath);
|
|
226
|
+
if (cached && cached.size === size && cached.mtimeMs === mtimeMs) {
|
|
227
|
+
return cached.skip;
|
|
228
|
+
}
|
|
229
|
+
|
|
43
230
|
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
44
|
-
|
|
45
|
-
|
|
231
|
+
let skip = false;
|
|
232
|
+
try {
|
|
233
|
+
skip = parseFrontMatterAndContent(raw, ext).pageVariables?.skip === true;
|
|
234
|
+
} catch {
|
|
235
|
+
// Not skipped: rendering the page reports the error for this file.
|
|
236
|
+
}
|
|
237
|
+
skipCache.set(filePath, { size, mtimeMs, skip });
|
|
238
|
+
return skip;
|
|
46
239
|
}
|
|
47
240
|
|
|
48
241
|
export function getProcessedExts(codeExtensions: string[]): Set<string> {
|
|
@@ -53,7 +246,7 @@ export function getProcessedExts(codeExtensions: string[]): Set<string> {
|
|
|
53
246
|
|
|
54
247
|
export function isBuildContentSource(
|
|
55
248
|
filePath: string,
|
|
56
|
-
processedExts:
|
|
249
|
+
processedExts: ReadonlySet<string>,
|
|
57
250
|
): boolean {
|
|
58
251
|
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
59
252
|
if (!processedExts.has(ext) || isPartial(filePath)) {
|
|
@@ -98,114 +291,6 @@ function walkFiles(dir: string): string[] {
|
|
|
98
291
|
});
|
|
99
292
|
}
|
|
100
293
|
|
|
101
|
-
function cloneSetMap(
|
|
102
|
-
source: Map<string, Set<string>>,
|
|
103
|
-
): Map<string, Set<string>> {
|
|
104
|
-
return new Map(
|
|
105
|
-
[...source.entries()].map(([key, values]) => [key, new Set(values)]),
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function addContentSourceToScan({
|
|
110
|
-
scan,
|
|
111
|
-
filePath,
|
|
112
|
-
}: {
|
|
113
|
-
scan: TadaProjectScan;
|
|
114
|
-
filePath: string;
|
|
115
|
-
}): void {
|
|
116
|
-
const buildContent = isBuildContentSource(filePath, scan.processedExts);
|
|
117
|
-
|
|
118
|
-
scan.contentFiles.add(filePath);
|
|
119
|
-
if (buildContent) {
|
|
120
|
-
scan.buildContentFiles.add(filePath);
|
|
121
|
-
}
|
|
122
|
-
if (buildContent && isLiterateJava(filePath)) {
|
|
123
|
-
const parsed = path.parse(path.relative(scan.contentDir, filePath));
|
|
124
|
-
scan.literateJavaOutputPaths.add(
|
|
125
|
-
`/${toPosix(path.join(parsed.dir, parsed.name))}`,
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const outputs = getSourceOutputPaths({
|
|
130
|
-
contentDir: scan.contentDir,
|
|
131
|
-
filePath,
|
|
132
|
-
processedExts: scan.processedExts,
|
|
133
|
-
buildContent,
|
|
134
|
-
});
|
|
135
|
-
scan.sourceOutputPaths.set(filePath, outputs);
|
|
136
|
-
for (const outputPath of outputs) {
|
|
137
|
-
scan.contentOwners.set(outputPath, filePath);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const targets = getSourceTargetPaths({
|
|
141
|
-
kind: 'content',
|
|
142
|
-
rootDir: scan.contentDir,
|
|
143
|
-
filePath,
|
|
144
|
-
processedExts: scan.processedExts,
|
|
145
|
-
buildContent,
|
|
146
|
-
});
|
|
147
|
-
scan.sourceTargetPaths.set(filePath, targets);
|
|
148
|
-
for (const target of targets) {
|
|
149
|
-
scan.validTargets.add(target);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function addPublicSourceToScan({
|
|
154
|
-
scan,
|
|
155
|
-
filePath,
|
|
156
|
-
}: {
|
|
157
|
-
scan: TadaProjectScan;
|
|
158
|
-
filePath: string;
|
|
159
|
-
}): void {
|
|
160
|
-
const relPath = toPosix(path.relative(scan.publicDir, filePath));
|
|
161
|
-
|
|
162
|
-
scan.publicFiles.add(filePath);
|
|
163
|
-
scan.publicOwners.set(relPath, filePath);
|
|
164
|
-
scan.sourceOutputPaths.set(filePath, new Set([relPath]));
|
|
165
|
-
|
|
166
|
-
const targets = getSourceTargetPaths({
|
|
167
|
-
kind: 'public',
|
|
168
|
-
rootDir: scan.publicDir,
|
|
169
|
-
filePath,
|
|
170
|
-
processedExts: scan.processedExts,
|
|
171
|
-
buildContent: false,
|
|
172
|
-
});
|
|
173
|
-
scan.sourceTargetPaths.set(filePath, targets);
|
|
174
|
-
for (const target of targets) {
|
|
175
|
-
scan.validTargets.add(target);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function collectValidTargets(
|
|
180
|
-
sourceTargetPaths: Map<string, Set<string>>,
|
|
181
|
-
): Set<string> {
|
|
182
|
-
const validTargets = new Set<string>();
|
|
183
|
-
for (const targets of sourceTargetPaths.values()) {
|
|
184
|
-
for (const target of targets) {
|
|
185
|
-
validTargets.add(target);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return validTargets;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function createEmptyScan(processedExts: Set<string>): TadaProjectScan {
|
|
192
|
-
return {
|
|
193
|
-
contentDir: getContentDir(),
|
|
194
|
-
publicDir: getPublicDir(),
|
|
195
|
-
distDir: getDistDir(),
|
|
196
|
-
contentFiles: new Set(),
|
|
197
|
-
buildContentFiles: new Set(),
|
|
198
|
-
publicFiles: new Set(),
|
|
199
|
-
validTargets: new Set(),
|
|
200
|
-
literateJavaOutputPaths: new Set(),
|
|
201
|
-
processedExts,
|
|
202
|
-
contentOwners: new Map(),
|
|
203
|
-
publicOwners: new Map(),
|
|
204
|
-
sourceOutputPaths: new Map(),
|
|
205
|
-
sourceTargetPaths: new Map(),
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
294
|
export function getSourceOutputPaths({
|
|
210
295
|
contentDir,
|
|
211
296
|
filePath,
|
|
@@ -214,7 +299,7 @@ export function getSourceOutputPaths({
|
|
|
214
299
|
}: {
|
|
215
300
|
contentDir: string;
|
|
216
301
|
filePath: string;
|
|
217
|
-
processedExts:
|
|
302
|
+
processedExts: ReadonlySet<string>;
|
|
218
303
|
buildContent: boolean;
|
|
219
304
|
}): Set<string> {
|
|
220
305
|
const relPath = toPosix(path.relative(contentDir, filePath));
|
|
@@ -260,7 +345,7 @@ export function getSourceTargetPaths({
|
|
|
260
345
|
kind: 'content' | 'public';
|
|
261
346
|
rootDir: string;
|
|
262
347
|
filePath: string;
|
|
263
|
-
processedExts:
|
|
348
|
+
processedExts: ReadonlySet<string>;
|
|
264
349
|
buildContent: boolean;
|
|
265
350
|
}): Set<string> {
|
|
266
351
|
const targets = new Set<string>();
|
|
@@ -281,123 +366,17 @@ export function getSourceTargetPaths({
|
|
|
281
366
|
return targets;
|
|
282
367
|
}
|
|
283
368
|
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (
|
|
295
|
-
extensionIsMarkdown(parsed.ext.toLowerCase()) ||
|
|
296
|
-
parsed.ext.toLowerCase() === '.html'
|
|
297
|
-
) {
|
|
298
|
-
addGeneratedRouteAliases(targets, `/${subPath}.html`);
|
|
299
|
-
return targets;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
addGeneratedRouteAliases(targets, `/${relPath}.html`);
|
|
303
|
-
targets.add(normalizeOutputPath(`/${relPath}`));
|
|
304
|
-
return targets;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
export function scanProject(siteVariables: SiteVariables): TadaProjectScan {
|
|
308
|
-
const scan = createEmptyScan(
|
|
309
|
-
getProcessedExts(Object.keys(getExtensionToShikiLanguage(siteVariables))),
|
|
310
|
-
);
|
|
311
|
-
|
|
312
|
-
for (const filePath of walkFiles(scan.contentDir).sort()) {
|
|
313
|
-
addContentSourceToScan({ scan, filePath });
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
for (const filePath of walkFiles(scan.publicDir).sort()) {
|
|
317
|
-
addPublicSourceToScan({ scan, filePath });
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return scan;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
export function updateProjectScan(
|
|
324
|
-
snapshot: TadaProjectScan,
|
|
325
|
-
batch: ChangeBatch,
|
|
326
|
-
): TadaProjectScan {
|
|
327
|
-
const scan = createEmptyScan(snapshot.processedExts);
|
|
328
|
-
scan.contentFiles = new Set(snapshot.contentFiles);
|
|
329
|
-
scan.buildContentFiles = new Set(snapshot.buildContentFiles);
|
|
330
|
-
scan.publicFiles = new Set(snapshot.publicFiles);
|
|
331
|
-
scan.literateJavaOutputPaths = new Set(snapshot.literateJavaOutputPaths);
|
|
332
|
-
scan.contentOwners = new Map(snapshot.contentOwners);
|
|
333
|
-
scan.publicOwners = new Map(snapshot.publicOwners);
|
|
334
|
-
scan.sourceOutputPaths = cloneSetMap(snapshot.sourceOutputPaths);
|
|
335
|
-
scan.sourceTargetPaths = cloneSetMap(snapshot.sourceTargetPaths);
|
|
336
|
-
scan.validTargets = collectValidTargets(scan.sourceTargetPaths);
|
|
337
|
-
|
|
338
|
-
for (const change of batch.changes) {
|
|
339
|
-
const sourcePath = path.resolve(change.path);
|
|
340
|
-
const inContent = sourcePath.startsWith(`${scan.contentDir}${path.sep}`);
|
|
341
|
-
const inPublic = sourcePath.startsWith(`${scan.publicDir}${path.sep}`);
|
|
342
|
-
if (!inContent && !inPublic) {
|
|
343
|
-
continue;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
for (const outputPath of scan.sourceOutputPaths.get(sourcePath) || []) {
|
|
347
|
-
if (inContent) {
|
|
348
|
-
scan.contentOwners.delete(outputPath);
|
|
349
|
-
} else {
|
|
350
|
-
scan.publicOwners.delete(outputPath);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
scan.sourceOutputPaths.delete(sourcePath);
|
|
355
|
-
scan.sourceTargetPaths.delete(sourcePath);
|
|
356
|
-
scan.contentFiles.delete(sourcePath);
|
|
357
|
-
scan.buildContentFiles.delete(sourcePath);
|
|
358
|
-
scan.publicFiles.delete(sourcePath);
|
|
359
|
-
|
|
360
|
-
if (inContent && isLiterateJava(sourcePath)) {
|
|
361
|
-
const parsed = path.parse(path.relative(scan.contentDir, sourcePath));
|
|
362
|
-
scan.literateJavaOutputPaths.delete(
|
|
363
|
-
`/${toPosix(path.join(parsed.dir, parsed.name))}`,
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
if (!fs.existsSync(sourcePath) || !fs.statSync(sourcePath).isFile()) {
|
|
368
|
-
continue;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
if (inContent) {
|
|
372
|
-
addContentSourceToScan({ scan, filePath: sourcePath });
|
|
373
|
-
continue;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
addPublicSourceToScan({ scan, filePath: sourcePath });
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// Rebuild the selected owners from the complete source inventory. Removing
|
|
380
|
-
// either producer of a formerly conflicting output must retain its survivor.
|
|
381
|
-
scan.contentOwners.clear();
|
|
382
|
-
for (const sourcePath of scan.contentFiles) {
|
|
383
|
-
for (const outputPath of scan.sourceOutputPaths.get(sourcePath) || []) {
|
|
384
|
-
scan.contentOwners.set(outputPath, sourcePath);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
scan.validTargets = collectValidTargets(scan.sourceTargetPaths);
|
|
388
|
-
return scan;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
export function assertNoOutputPathConflicts(scan: TadaProjectScan): string[] {
|
|
392
|
-
const owners = new Set<string>();
|
|
393
|
-
const conflicts = new Set<string>();
|
|
394
|
-
for (const outputs of scan.sourceOutputPaths.values()) {
|
|
395
|
-
for (const outputPath of outputs) {
|
|
396
|
-
if (owners.has(outputPath)) {
|
|
397
|
-
conflicts.add(outputPath);
|
|
398
|
-
}
|
|
399
|
-
owners.add(outputPath);
|
|
369
|
+
for (const output of getSourceOutputPaths({
|
|
370
|
+
contentDir: rootDir,
|
|
371
|
+
filePath,
|
|
372
|
+
processedExts,
|
|
373
|
+
buildContent,
|
|
374
|
+
})) {
|
|
375
|
+
if (output.endsWith('.html')) {
|
|
376
|
+
addGeneratedRouteAliases(targets, `/${output}`);
|
|
377
|
+
} else {
|
|
378
|
+
targets.add(normalizeOutputPath(`/${output}`));
|
|
400
379
|
}
|
|
401
380
|
}
|
|
402
|
-
return
|
|
381
|
+
return targets;
|
|
403
382
|
}
|