@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.
- package/README.md +65 -64
- package/bin/tada.ts +61 -66
- package/build/build-manifest.ts +14 -3
- package/build/bundle.ts +0 -1
- package/build/config-files.ts +183 -0
- package/build/config-loader.ts +159 -0
- package/build/copy.ts +0 -83
- package/build/generate-content-assets.ts +75 -160
- package/build/globals.ts +48 -89
- package/build/log.ts +27 -18
- package/build/pagefind.ts +33 -26
- package/build/pipeline.ts +98 -79
- package/build/reachability.ts +51 -248
- package/build/serve.ts +35 -6
- package/build/site-variables.ts +20 -11
- package/build/source-model.ts +400 -0
- package/build/{watch/records.ts → source-records.ts} +111 -49
- package/build/template-globals.ts +91 -0
- package/build/templates.ts +50 -40
- package/build/test-helpers.ts +15 -0
- package/build/toc-plugin.ts +5 -1
- package/build/types.d.ts +9 -1
- package/build/util.ts +8 -0
- package/build/utils/code.ts +6 -16
- package/build/utils/content-files.ts +57 -126
- package/build/utils/final-html.ts +239 -0
- package/build/utils/markdown.ts +237 -17
- package/build/utils/paths.ts +2 -1
- package/build/utils/render.ts +108 -99
- package/build/validate-config-links.ts +91 -14
- package/build/watch/assets.ts +0 -2
- package/build/watch/build-full.ts +14 -15
- package/build/watch/build-incremental.ts +15 -8
- package/build/watch/compiler-types.d.ts +6 -1
- package/build/watch/compiler.ts +27 -15
- package/build/watch/index.ts +2 -7
- package/build/watch/planner.ts +32 -16
- package/build/watch/reload.ts +4 -0
- package/build/watch/runtime.ts +29 -45
- package/build/watch/snapshot.ts +50 -402
- package/build/watch/validation.ts +6 -3
- package/build/watch-reachability-state.ts +16 -23
- package/build/watch-reload-client.ts +11 -4
- package/init/content/index.md +1 -1
- package/init/content/labs/02/index.md +61 -0
- package/init/content/labs/02/selection_sort.py +23 -0
- package/init/content/labs/index.md +4 -2
- package/init/content/lectures/01/index.md +1 -1
- package/init/content/markdown.md +1 -1
- package/package.json +3 -4
- package/src/code/index.ts +46 -18
- package/src/code.scss +2 -1
- package/src/globals.ts +47 -0
- package/src/index.ts +1 -0
- package/src/navigate/index.ts +2 -5
- package/src/navigate/lifecycle.ts +3 -1
- package/src/navigate/runtime.ts +6 -5
- package/src/page-update/index.ts +20 -16
- package/src/search/index.ts +53 -25
- package/src/slides/index.ts +467 -0
- package/src/slides/style.scss +104 -0
- package/src/style.scss +8 -3
- package/src/timezone/index.ts +16 -20
- package/src/top/index.ts +2 -4
- package/src/trace/index.ts +91 -7
- package/src/trace/style.scss +1 -1
- package/src/util.ts +6 -2
- package/templates/_author.html +2 -2
- package/templates/_heading.html +1 -1
- package/templates/_nav.html +2 -2
- package/templates/_theme.scss +3 -3
- package/templates/_top.html +14 -14
- package/templates/default.html +8 -0
- package/types/dev.d.ts +1 -1
- package/types/site-variables.d.ts +4 -5
- package/watch/fs-commit.ts +63 -10
- package/build/apply-base-path-plugin.ts +0 -186
- package/build/content-watch.ts +0 -104
- package/build/validate-internal-links-plugin.ts +0 -207
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import timezones from '../src/timezone/timezones.json' with { type: 'json' };
|
|
2
|
+
import type { SiteVariables } from './types';
|
|
3
|
+
|
|
4
|
+
const MONTHS = [
|
|
5
|
+
'',
|
|
6
|
+
'January',
|
|
7
|
+
'February',
|
|
8
|
+
'March',
|
|
9
|
+
'April',
|
|
10
|
+
'May',
|
|
11
|
+
'June',
|
|
12
|
+
'July',
|
|
13
|
+
'August',
|
|
14
|
+
'September',
|
|
15
|
+
'October',
|
|
16
|
+
'November',
|
|
17
|
+
'December',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
interface TemplateGlobals {
|
|
21
|
+
isHomePage: boolean;
|
|
22
|
+
isoDate: (str: string | null | undefined) => string | null;
|
|
23
|
+
readableDate: (date: string | Date | null | undefined) => string;
|
|
24
|
+
classNames: (obj: Record<string, unknown>) => string;
|
|
25
|
+
cx: (obj: Record<string, unknown>) => string;
|
|
26
|
+
renderTimeZoneChooser: () => string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default function createTemplateGlobals(
|
|
30
|
+
pageVariables: Record<string, unknown>,
|
|
31
|
+
siteVariables: SiteVariables,
|
|
32
|
+
subPath: string,
|
|
33
|
+
): TemplateGlobals {
|
|
34
|
+
const defaultTz = timezones.find(
|
|
35
|
+
t => t.value === siteVariables.defaultTimeZone,
|
|
36
|
+
)!;
|
|
37
|
+
const options = timezones
|
|
38
|
+
.map(tz => {
|
|
39
|
+
const label =
|
|
40
|
+
tz.value === 'UTC' ? tz.label : `${tz.label} (${tz.abbreviation})`;
|
|
41
|
+
const selected = tz.value === defaultTz.value ? ' selected' : '';
|
|
42
|
+
return `<option value="${tz.value}"${selected}>${label}</option>`;
|
|
43
|
+
})
|
|
44
|
+
.join('');
|
|
45
|
+
const renderTimeZoneChooser = () =>
|
|
46
|
+
`<span class="time-zone-label" hidden>Times shown in </span><select class="time-zone" hidden disabled aria-label="Time zone">${options}</select><noscript>Times shown in ${defaultTz.abbreviation}.</noscript>`;
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
isHomePage: subPath === 'index',
|
|
50
|
+
isoDate,
|
|
51
|
+
readableDate,
|
|
52
|
+
classNames,
|
|
53
|
+
cx: classNames,
|
|
54
|
+
renderTimeZoneChooser,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isoDate(str: string | null | undefined): string | null {
|
|
59
|
+
if (str == null || str == '') {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const date = new Date(str);
|
|
63
|
+
return date.toISOString().slice(0, 10);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function readableDate(date: string | Date | null | undefined): string {
|
|
67
|
+
if (date == null || date == '') {
|
|
68
|
+
return '';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!(date instanceof Date)) {
|
|
72
|
+
date = new Date(date);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const str = date.toISOString();
|
|
76
|
+
const year = str.slice(0, 4);
|
|
77
|
+
const month = Number(str.slice(5, 7));
|
|
78
|
+
const day = Number(str.slice(8, 10));
|
|
79
|
+
|
|
80
|
+
return `${MONTHS[month]} ${day}, ${year}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function classNames(obj: Record<string, unknown>): string {
|
|
84
|
+
const names: string[] = [];
|
|
85
|
+
for (const key in obj) {
|
|
86
|
+
if (obj[key]) {
|
|
87
|
+
names.push(key);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return names.join(' ');
|
|
91
|
+
}
|
package/build/templates.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import _ from 'lodash';
|
|
4
|
+
import {
|
|
5
|
+
PROJECT_CONFIG_NAMES,
|
|
6
|
+
getProjectConfigBaseName,
|
|
7
|
+
type ProjectConfigName,
|
|
8
|
+
} from './config-files';
|
|
9
|
+
import {
|
|
10
|
+
loadProjectConfig,
|
|
11
|
+
type LoadedProjectConfigFile,
|
|
12
|
+
} from './config-loader';
|
|
4
13
|
import { compile as compileJsonSchema, doValidation } from './json-schema';
|
|
5
14
|
import { makeLogger } from './log';
|
|
6
15
|
import { getPackageDir, getProjectDir } from './utils/paths';
|
|
@@ -12,34 +21,33 @@ const log: Logger = makeLogger(import.meta.url);
|
|
|
12
21
|
// Store all templates in memory (don't read template files during build)
|
|
13
22
|
const templates: Record<string, string> = {};
|
|
14
23
|
|
|
15
|
-
// All parsed
|
|
16
|
-
const
|
|
24
|
+
// All parsed project config files
|
|
25
|
+
const projectConfig: Partial<Record<ProjectConfigName, unknown>> = {};
|
|
26
|
+
const loadedProjectConfig: Partial<
|
|
27
|
+
Record<ProjectConfigName, LoadedProjectConfigFile>
|
|
28
|
+
> = {};
|
|
17
29
|
|
|
18
|
-
// Compiled JSON Schema for the
|
|
19
|
-
const validators: Record<
|
|
30
|
+
// Compiled JSON Schema for the project config files
|
|
31
|
+
const validators: Partial<Record<ProjectConfigName, ValidateFunction>> = {};
|
|
20
32
|
|
|
21
33
|
// Keeps track of template call tree
|
|
22
34
|
const renderStack: string[] = [];
|
|
23
35
|
let errorStack: string[] | null = null;
|
|
24
36
|
|
|
25
|
-
// JSON data files that live in the user's config directory
|
|
26
|
-
const REQUIRED_JSON_DATA_FILES: string[] = ['nav.json'];
|
|
27
|
-
const OPTIONAL_JSON_DATA_FILES: string[] = ['authors.json'];
|
|
28
|
-
const JSON_DATA_FILES: string[] = [
|
|
29
|
-
...REQUIRED_JSON_DATA_FILES,
|
|
30
|
-
...OPTIONAL_JSON_DATA_FILES,
|
|
31
|
-
];
|
|
32
|
-
|
|
33
37
|
function getHtmlTemplatesDir(): string {
|
|
34
38
|
return path.resolve(getPackageDir(), 'templates');
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
function
|
|
41
|
+
function getProjectConfigDir(): string {
|
|
38
42
|
return getProjectDir();
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
export function
|
|
42
|
-
return
|
|
45
|
+
export function config(name: ProjectConfigName): unknown {
|
|
46
|
+
return projectConfig[name];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function getConfigFileName(name: ProjectConfigName): string | undefined {
|
|
50
|
+
return loadedProjectConfig[name]?.fileName;
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
export function render(
|
|
@@ -50,8 +58,8 @@ export function render(
|
|
|
50
58
|
// Allow the template to call render(), it will use our params
|
|
51
59
|
params.render = (otherFileName: string) => render(otherFileName, params);
|
|
52
60
|
|
|
53
|
-
// Allow
|
|
54
|
-
params.
|
|
61
|
+
// Allow templates to read the project config files we previously read into memory
|
|
62
|
+
params.config = config;
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
renderStack.push(fileName);
|
|
@@ -82,7 +90,11 @@ export function compileTemplates(
|
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
Object.keys(templates).forEach(k => delete templates[k]);
|
|
85
|
-
|
|
93
|
+
for (const name of PROJECT_CONFIG_NAMES) {
|
|
94
|
+
delete projectConfig[name];
|
|
95
|
+
delete loadedProjectConfig[name];
|
|
96
|
+
delete validators[name];
|
|
97
|
+
}
|
|
86
98
|
|
|
87
99
|
// Load HTML templates from the package
|
|
88
100
|
const htmlDir = getHtmlTemplatesDir();
|
|
@@ -97,41 +109,39 @@ export function compileTemplates(
|
|
|
97
109
|
}
|
|
98
110
|
});
|
|
99
111
|
|
|
100
|
-
// Load
|
|
101
|
-
const
|
|
102
|
-
for (const
|
|
103
|
-
const
|
|
104
|
-
if (!
|
|
105
|
-
if (REQUIRED_JSON_DATA_FILES.includes(fileName)) {
|
|
106
|
-
throw new Error(`Missing required config file: ${filePath}`);
|
|
107
|
-
}
|
|
112
|
+
// Load project config files from the project's config directory
|
|
113
|
+
const projectConfigDir = getProjectConfigDir();
|
|
114
|
+
for (const name of PROJECT_CONFIG_NAMES) {
|
|
115
|
+
const loaded = loadProjectConfig(projectConfigDir, name, siteVariables);
|
|
116
|
+
if (!loaded) {
|
|
108
117
|
continue;
|
|
109
118
|
}
|
|
110
119
|
|
|
111
120
|
// Schema validation
|
|
112
121
|
const schemaDir = path.resolve(getPackageDir(), 'schema');
|
|
113
|
-
const schemaFile = `${
|
|
122
|
+
const schemaFile = `${getProjectConfigBaseName(name)}.schema.json`;
|
|
114
123
|
const schemaPath = path.join(schemaDir, schemaFile);
|
|
115
124
|
if (!fs.existsSync(schemaPath)) {
|
|
116
|
-
throw new Error(
|
|
125
|
+
throw new Error(
|
|
126
|
+
`Missing JSON Schema for ${loaded.fileName}: ${schemaPath}`,
|
|
127
|
+
);
|
|
117
128
|
}
|
|
118
|
-
compileAndSetValidator(schemaPath,
|
|
129
|
+
compileAndSetValidator(schemaPath, name);
|
|
119
130
|
|
|
120
|
-
log.debug`Reading ${fileName}`;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
vars: siteVariables.vars || {},
|
|
124
|
-
site: siteVariables,
|
|
125
|
-
}),
|
|
126
|
-
);
|
|
131
|
+
log.debug`Reading ${loaded.fileName}`;
|
|
132
|
+
projectConfig[name] = loaded.value;
|
|
133
|
+
loadedProjectConfig[name] = loaded;
|
|
127
134
|
|
|
128
|
-
doValidation(validators[
|
|
135
|
+
doValidation(validators[name]!, projectConfig[name], loaded.fileName);
|
|
129
136
|
}
|
|
130
137
|
}
|
|
131
138
|
|
|
132
|
-
function compileAndSetValidator(
|
|
139
|
+
function compileAndSetValidator(
|
|
140
|
+
schemaPath: string,
|
|
141
|
+
name: ProjectConfigName,
|
|
142
|
+
): void {
|
|
133
143
|
const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf-8'));
|
|
134
|
-
validators[
|
|
144
|
+
validators[name] = compileJsonSchema(schema);
|
|
135
145
|
}
|
|
136
146
|
|
|
137
|
-
export { getHtmlTemplatesDir,
|
|
147
|
+
export { getHtmlTemplatesDir, getProjectConfigDir };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type FsModule = typeof import('fs');
|
|
2
|
+
type FsModuleOverrides = object & { promises?: FsModule['promises'] };
|
|
3
|
+
|
|
4
|
+
type FsModuleMock<T extends FsModuleOverrides> = T & {
|
|
5
|
+
default: Omit<T, 'promises'> & Pick<FsModule, 'promises'>;
|
|
6
|
+
promises: FsModule['promises'];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function createFsModuleMock<T extends FsModuleOverrides>(
|
|
10
|
+
overrides: T,
|
|
11
|
+
): FsModuleMock<T> {
|
|
12
|
+
const promises = overrides.promises ?? ({} as FsModule['promises']);
|
|
13
|
+
|
|
14
|
+
return { ...overrides, default: { ...overrides, promises }, promises };
|
|
15
|
+
}
|
package/build/toc-plugin.ts
CHANGED
|
@@ -55,7 +55,11 @@ export function tocPlugin(md: MarkdownIt): void {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// Thematic breaks / dinkuses (only at top level)
|
|
58
|
-
if (
|
|
58
|
+
if (
|
|
59
|
+
token.type === 'hr' &&
|
|
60
|
+
containerStack.length === 0 &&
|
|
61
|
+
!state.env?.slides
|
|
62
|
+
) {
|
|
59
63
|
items.push({ kind: 'dinkus' });
|
|
60
64
|
continue;
|
|
61
65
|
}
|
package/build/types.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { BundledLanguage } from 'shiki';
|
|
2
|
+
import type { TadaProjectScan } from './source-model';
|
|
2
3
|
|
|
3
4
|
export type PlainTextLanguage = 'text' | 'txt' | 'plain';
|
|
4
5
|
|
|
5
|
-
/** Site configuration loaded from site.dev.json or site.prod.json */
|
|
6
|
+
/** Site configuration loaded from site.dev.yaml/yml/json or site.prod.yaml/yml/json */
|
|
6
7
|
export interface SiteVariables {
|
|
7
8
|
base: string;
|
|
8
9
|
basePath: string;
|
|
@@ -33,15 +34,21 @@ export type FeatureConfig = {
|
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
/** A rendered content asset ready to write to dist/ */
|
|
37
|
+
export interface HtmlOutputAnalysis {
|
|
38
|
+
outgoingTargets: Set<string>;
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
export interface Asset {
|
|
37
42
|
assetPath: string;
|
|
38
43
|
content: string | Buffer;
|
|
44
|
+
htmlAnalysis?: HtmlOutputAnalysis;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
/** Options for the content rendering pipeline */
|
|
42
48
|
export interface ContentRenderOptions {
|
|
43
49
|
distDir: string;
|
|
44
50
|
assetFiles: string[];
|
|
51
|
+
scan: TadaProjectScan;
|
|
45
52
|
watchState?: WatchState;
|
|
46
53
|
}
|
|
47
54
|
|
|
@@ -59,6 +66,7 @@ export interface ContentRenderResult {
|
|
|
59
66
|
removedHtmlAssetPaths: Set<string>;
|
|
60
67
|
removedOutputRelPaths: Set<string>;
|
|
61
68
|
htmlAssetsByPath: Map<string, string>;
|
|
69
|
+
htmlAnalysisByPath: Map<string, HtmlOutputAnalysis>;
|
|
62
70
|
buildContentFiles: string[];
|
|
63
71
|
}
|
|
64
72
|
|
package/build/util.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export {
|
|
2
|
+
addGeneratedRouteAliases,
|
|
3
|
+
getProcessedExts,
|
|
4
|
+
getSourceOutputPaths,
|
|
5
|
+
getSourceTargetPaths,
|
|
6
|
+
isBuildContentSource,
|
|
7
|
+
} from './source-model';
|
|
1
8
|
export {
|
|
2
9
|
getBuildContentFiles,
|
|
3
10
|
getContentFiles,
|
|
@@ -21,6 +28,7 @@ export {
|
|
|
21
28
|
} from './utils/paths';
|
|
22
29
|
export {
|
|
23
30
|
injectAssetTags,
|
|
31
|
+
preparePageTemplateHtml,
|
|
24
32
|
renderCodePageAsset,
|
|
25
33
|
renderCopiedContentAsset,
|
|
26
34
|
renderLiterateJavaPageAsset,
|
package/build/utils/code.ts
CHANGED
|
@@ -6,7 +6,6 @@ import { makeLogger } from '../log';
|
|
|
6
6
|
import { getExtensionToShikiLanguage } from '../site-variables';
|
|
7
7
|
import { highlightCode } from './shiki-highlighter';
|
|
8
8
|
import externalLinksPlugin from '../external-links-plugin';
|
|
9
|
-
import applyBasePathPlugin from '../apply-base-path-plugin';
|
|
10
9
|
import { createApplyBasePath } from './paths';
|
|
11
10
|
import { splitLines } from './literate-java';
|
|
12
11
|
import type { JavaTocEntry, SiteVariables } from '../types';
|
|
@@ -40,13 +39,11 @@ const log = makeLogger(import.meta.url);
|
|
|
40
39
|
|
|
41
40
|
const PROSE_LINE = /^\s*\/\/\/(\s|$)/;
|
|
42
41
|
|
|
43
|
-
function createCodeMarkdown(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
.use(externalLinksPlugin, siteVariables)
|
|
49
|
-
.use(applyBasePathPlugin, siteVariables, options);
|
|
42
|
+
function createCodeMarkdown(siteVariables: SiteVariables): MarkdownIt {
|
|
43
|
+
return new MarkdownIt({ html: true, typographer: true }).use(
|
|
44
|
+
externalLinksPlugin,
|
|
45
|
+
siteVariables,
|
|
46
|
+
);
|
|
50
47
|
}
|
|
51
48
|
|
|
52
49
|
// Matches Markdown links: [text](url)
|
|
@@ -504,15 +501,8 @@ export function renderCodeWithComments(
|
|
|
504
501
|
lang: string,
|
|
505
502
|
siteVariables: SiteVariables,
|
|
506
503
|
pageDirPath?: string,
|
|
507
|
-
sourceUrlPath?: string,
|
|
508
|
-
validTargets?: Set<string>,
|
|
509
|
-
literateJavaOutputPaths?: Set<string>,
|
|
510
504
|
): string {
|
|
511
|
-
const md = createCodeMarkdown(siteVariables
|
|
512
|
-
sourceUrlPath,
|
|
513
|
-
validTargets,
|
|
514
|
-
literateJavaOutputPaths,
|
|
515
|
-
});
|
|
505
|
+
const md = createCodeMarkdown(siteVariables);
|
|
516
506
|
const lines = splitLines(sourceCode);
|
|
517
507
|
|
|
518
508
|
// Group lines into segments
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { parseFrontMatterAndContent } from './front-matter';
|
|
4
3
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
getProcessedExts,
|
|
5
|
+
getSourceOutputPaths,
|
|
6
|
+
getSourceTargetPaths,
|
|
7
|
+
isBuildContentSource,
|
|
8
|
+
shouldSkipContentFile,
|
|
9
|
+
} from '../source-model';
|
|
10
|
+
import { getPublicDir, toPosix } from './paths';
|
|
11
11
|
|
|
12
12
|
function walkFiles(dir: string): string[] {
|
|
13
13
|
return fs.readdirSync(dir).flatMap(file => {
|
|
@@ -31,23 +31,14 @@ export function getContentFiles(
|
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export function shouldSkipContentFile(filePath: string): boolean {
|
|
35
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
36
|
-
if (!(extensionIsMarkdown(ext) || ext === '.html')) {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
41
|
-
const { pageVariables } = parseFrontMatterAndContent(raw, ext);
|
|
42
|
-
return pageVariables?.skip === true;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
34
|
export function getBuildContentFiles(
|
|
46
35
|
contentDir: string,
|
|
47
36
|
codeExtensions: string[],
|
|
48
37
|
): string[] {
|
|
49
|
-
|
|
50
|
-
|
|
38
|
+
const processedExts = getProcessedExts(codeExtensions);
|
|
39
|
+
|
|
40
|
+
return getContentFiles(contentDir, codeExtensions).filter(filePath =>
|
|
41
|
+
isBuildContentSource(filePath, processedExts),
|
|
51
42
|
);
|
|
52
43
|
}
|
|
53
44
|
|
|
@@ -56,47 +47,17 @@ export function getContentSourceOutputRelPaths(
|
|
|
56
47
|
filePath: string,
|
|
57
48
|
codeExtensions: string[],
|
|
58
49
|
): Set<string> {
|
|
59
|
-
const
|
|
60
|
-
const relPath = toPosix(path.relative(contentDir, filePath));
|
|
61
|
-
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
62
|
-
const parsed = path.parse(relPath);
|
|
63
|
-
const outputs = new Set<string>();
|
|
64
|
-
|
|
65
|
-
if (!processedExtSet.has(ext)) {
|
|
66
|
-
outputs.add(relPath);
|
|
67
|
-
return outputs;
|
|
68
|
-
}
|
|
69
|
-
|
|
50
|
+
const processedExts = getProcessedExts(codeExtensions);
|
|
70
51
|
const buildContentFiles = new Set(
|
|
71
52
|
getBuildContentFiles(contentDir, codeExtensions),
|
|
72
53
|
);
|
|
73
|
-
if (!buildContentFiles.has(filePath)) {
|
|
74
|
-
return outputs;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (isLiterateJava(filePath)) {
|
|
78
|
-
outputs.add(toPosix(path.join(parsed.dir, `${parsed.name}.html`)));
|
|
79
|
-
outputs.add(toPosix(path.join(parsed.dir, parsed.name)));
|
|
80
|
-
return outputs;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (
|
|
84
|
-
extensionIsMarkdown(parsed.ext.toLowerCase()) ||
|
|
85
|
-
parsed.ext.toLowerCase() === '.html'
|
|
86
|
-
) {
|
|
87
|
-
outputs.add(toPosix(path.join(parsed.dir, `${parsed.name}.html`)));
|
|
88
|
-
return outputs;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const codeExtensionSet = new Set(
|
|
92
|
-
codeExtensions.map(value => value.toLowerCase()),
|
|
93
|
-
);
|
|
94
|
-
if (codeExtensionSet.has(ext)) {
|
|
95
|
-
outputs.add(relPath);
|
|
96
|
-
outputs.add(`${relPath}.html`);
|
|
97
|
-
}
|
|
98
54
|
|
|
99
|
-
return
|
|
55
|
+
return getSourceOutputPaths({
|
|
56
|
+
contentDir,
|
|
57
|
+
filePath,
|
|
58
|
+
processedExts,
|
|
59
|
+
buildContent: buildContentFiles.has(filePath),
|
|
60
|
+
});
|
|
100
61
|
}
|
|
101
62
|
|
|
102
63
|
export function getContentOutputRelPaths(
|
|
@@ -104,37 +65,23 @@ export function getContentOutputRelPaths(
|
|
|
104
65
|
codeExtensions: string[],
|
|
105
66
|
): Set<string> {
|
|
106
67
|
const buildContentFiles = getBuildContentFiles(contentDir, codeExtensions);
|
|
107
|
-
const
|
|
108
|
-
codeExtensions.map(ext => ext.toLowerCase()),
|
|
109
|
-
);
|
|
68
|
+
const processedExts = getProcessedExts(codeExtensions);
|
|
110
69
|
const outputs = new Set<string>();
|
|
111
70
|
|
|
112
71
|
for (const filePath of buildContentFiles) {
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
outputs.add(
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (extensionIsMarkdown(ext) || ext === '.html') {
|
|
124
|
-
outputs.add(toPosix(path.join(parsed.dir, `${parsed.name}.html`)));
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (codeExtensionSet.has(ext.slice(1))) {
|
|
129
|
-
outputs.add(relPath);
|
|
130
|
-
outputs.add(`${relPath}.html`);
|
|
72
|
+
for (const outputPath of getSourceOutputPaths({
|
|
73
|
+
contentDir,
|
|
74
|
+
filePath,
|
|
75
|
+
processedExts,
|
|
76
|
+
buildContent: true,
|
|
77
|
+
})) {
|
|
78
|
+
outputs.add(outputPath);
|
|
131
79
|
}
|
|
132
80
|
}
|
|
133
81
|
|
|
134
|
-
const processedExtSet = new Set(getProcessedExtensions(codeExtensions));
|
|
135
82
|
for (const filePath of walkFiles(contentDir)) {
|
|
136
83
|
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
137
|
-
if (
|
|
84
|
+
if (processedExts.has(ext)) {
|
|
138
85
|
continue;
|
|
139
86
|
}
|
|
140
87
|
outputs.add(toPosix(path.relative(contentDir, filePath)));
|
|
@@ -143,24 +90,6 @@ export function getContentOutputRelPaths(
|
|
|
143
90
|
return outputs;
|
|
144
91
|
}
|
|
145
92
|
|
|
146
|
-
function addGeneratedRouteAliases(
|
|
147
|
-
pathSet: Set<string>,
|
|
148
|
-
outputPath: string,
|
|
149
|
-
): void {
|
|
150
|
-
const normalizedPath = normalizeOutputPath(outputPath);
|
|
151
|
-
pathSet.add(normalizedPath);
|
|
152
|
-
|
|
153
|
-
if (!normalizedPath.endsWith('/index.html')) {
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const base = normalizedPath.slice(0, -'index.html'.length);
|
|
158
|
-
pathSet.add(base);
|
|
159
|
-
if (base.endsWith('/') && base.length > 1) {
|
|
160
|
-
pathSet.add(base.slice(0, -1));
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
93
|
function getPublicFiles(publicDir: string): string[] {
|
|
165
94
|
if (!fs.existsSync(publicDir)) {
|
|
166
95
|
return [];
|
|
@@ -191,49 +120,51 @@ export function getValidInternalTargets(
|
|
|
191
120
|
codeExtensions: string[],
|
|
192
121
|
): Set<string> {
|
|
193
122
|
const targets = new Set<string>();
|
|
194
|
-
const
|
|
195
|
-
codeExtensions.map(ext => ext.toLowerCase()),
|
|
196
|
-
);
|
|
123
|
+
const processedExts = getProcessedExts(codeExtensions);
|
|
197
124
|
|
|
198
125
|
for (const filePath of contentFiles) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (isLiterateJava(filePath)) {
|
|
208
|
-
// parsed.name is e.g. "VowelCounter.java"; the output page is
|
|
209
|
-
// VowelCounter.java.html and the source file is VowelCounter.java
|
|
210
|
-
const javaSubPath = toPosix(path.join(parsed.dir, parsed.name));
|
|
211
|
-
addGeneratedRouteAliases(targets, `/${javaSubPath}.html`);
|
|
212
|
-
targets.add(normalizeOutputPath(`/${javaSubPath}`));
|
|
213
|
-
} else if (extensionIsMarkdown(ext) || ext === '.html') {
|
|
214
|
-
addGeneratedRouteAliases(targets, `/${subPath}.html`);
|
|
215
|
-
} else if (codeExtensionSet.has(ext.slice(1))) {
|
|
216
|
-
addGeneratedRouteAliases(targets, `/${relPath}.html`);
|
|
217
|
-
targets.add(normalizeOutputPath(`/${relPath}`));
|
|
126
|
+
for (const targetPath of getSourceTargetPaths({
|
|
127
|
+
kind: 'content',
|
|
128
|
+
rootDir: contentDir,
|
|
129
|
+
filePath,
|
|
130
|
+
processedExts,
|
|
131
|
+
buildContent: true,
|
|
132
|
+
})) {
|
|
133
|
+
targets.add(targetPath);
|
|
218
134
|
}
|
|
219
135
|
}
|
|
220
136
|
|
|
221
137
|
// Include non-processed assets in content/ that are copied directly to dist/.
|
|
222
|
-
const processedExtSet = new Set(getProcessedExtensions(codeExtensions));
|
|
223
138
|
for (const filePath of walkFiles(contentDir)) {
|
|
224
139
|
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
225
|
-
if (
|
|
140
|
+
if (processedExts.has(ext)) {
|
|
226
141
|
continue;
|
|
227
142
|
}
|
|
228
|
-
const
|
|
229
|
-
|
|
143
|
+
for (const targetPath of getSourceTargetPaths({
|
|
144
|
+
kind: 'content',
|
|
145
|
+
rootDir: contentDir,
|
|
146
|
+
filePath,
|
|
147
|
+
processedExts,
|
|
148
|
+
buildContent: false,
|
|
149
|
+
})) {
|
|
150
|
+
targets.add(targetPath);
|
|
151
|
+
}
|
|
230
152
|
}
|
|
231
153
|
|
|
232
154
|
const publicDir = getPublicDir();
|
|
233
155
|
for (const filePath of getPublicFiles(publicDir)) {
|
|
234
|
-
const
|
|
235
|
-
|
|
156
|
+
for (const targetPath of getSourceTargetPaths({
|
|
157
|
+
kind: 'public',
|
|
158
|
+
rootDir: publicDir,
|
|
159
|
+
filePath,
|
|
160
|
+
processedExts,
|
|
161
|
+
buildContent: true,
|
|
162
|
+
})) {
|
|
163
|
+
targets.add(targetPath);
|
|
164
|
+
}
|
|
236
165
|
}
|
|
237
166
|
|
|
238
167
|
return targets;
|
|
239
168
|
}
|
|
169
|
+
|
|
170
|
+
export { shouldSkipContentFile };
|