@abreen/tada 1.11.2 → 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/search/style.scss +1 -1
- 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
package/build/reachability.ts
CHANGED
|
@@ -1,288 +1,114 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import {
|
|
2
|
+
import { normalizeOutputPath } from './util';
|
|
3
|
+
import type { HtmlOutputAnalysis } from './types';
|
|
3
4
|
|
|
4
|
-
function
|
|
5
|
-
return
|
|
5
|
+
function normalizeInternalTarget(target: string): string {
|
|
6
|
+
return normalizeOutputPath(target);
|
|
6
7
|
}
|
|
7
8
|
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
return true;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(href);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function normalizeUrlPath(pathname: string): string {
|
|
17
|
-
const normalized = path.posix.normalize(pathname);
|
|
18
|
-
if (normalized === '.' || normalized === '') {
|
|
19
|
-
return '/';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return normalized.startsWith('/') ? normalized : `/${normalized}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function stripBasePath(pathname: string, basePath: string): string {
|
|
26
|
-
const normalizedPath = normalizeUrlPath(pathname);
|
|
27
|
-
const normalizedBasePath = normalizeUrlPath(basePath);
|
|
28
|
-
|
|
29
|
-
if (normalizedBasePath === '/') {
|
|
30
|
-
return normalizedPath;
|
|
31
|
-
}
|
|
9
|
+
function toCandidateHtmlAssetPaths(target: string): string[] {
|
|
10
|
+
const normalizedTarget = normalizeInternalTarget(target);
|
|
32
11
|
|
|
33
|
-
if (
|
|
34
|
-
return '/';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const prefix = `${normalizedBasePath}/`;
|
|
38
|
-
if (normalizedPath.startsWith(prefix)) {
|
|
39
|
-
return normalizeUrlPath(normalizedPath.slice(normalizedBasePath.length));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return normalizedPath;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function toCandidateAssetPaths(urlPath: string): string[] {
|
|
46
|
-
const normalizedPath = normalizeUrlPath(urlPath);
|
|
47
|
-
|
|
48
|
-
if (normalizedPath === '/') {
|
|
12
|
+
if (normalizedTarget === '/') {
|
|
49
13
|
return ['index.html'];
|
|
50
14
|
}
|
|
51
15
|
|
|
52
|
-
if (
|
|
53
|
-
return [
|
|
16
|
+
if (normalizedTarget.endsWith('.html')) {
|
|
17
|
+
return [normalizedTarget.slice(1)];
|
|
54
18
|
}
|
|
55
19
|
|
|
56
|
-
if (path.posix.extname(
|
|
20
|
+
if (path.posix.extname(normalizedTarget)) {
|
|
57
21
|
return [];
|
|
58
22
|
}
|
|
59
23
|
|
|
60
|
-
const withoutLeadingSlash =
|
|
61
|
-
if (
|
|
24
|
+
const withoutLeadingSlash = normalizedTarget.slice(1);
|
|
25
|
+
if (normalizedTarget.endsWith('/')) {
|
|
62
26
|
return [`${withoutLeadingSlash}index.html`];
|
|
63
27
|
}
|
|
64
28
|
|
|
65
29
|
return [`${withoutLeadingSlash}/index.html`, `${withoutLeadingSlash}.html`];
|
|
66
30
|
}
|
|
67
31
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
basePath?: string;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function resolveInternalPath({
|
|
75
|
-
href,
|
|
76
|
-
fromAssetPath,
|
|
77
|
-
basePath = '/',
|
|
78
|
-
}: ResolveInternalPathOptions): string | null {
|
|
79
|
-
if (!href) {
|
|
32
|
+
function resolveTargetToAssetTarget(target: string): string | null {
|
|
33
|
+
const normalizedTarget = normalizeInternalTarget(target);
|
|
34
|
+
if (normalizedTarget === '/' || normalizedTarget.endsWith('.html')) {
|
|
80
35
|
return null;
|
|
81
36
|
}
|
|
82
|
-
|
|
83
|
-
const trimmedHref = href.trim();
|
|
84
|
-
if (!trimmedHref || isExternalOrAnchor(trimmedHref)) {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const pathname = stripQueryAndHash(trimmedHref);
|
|
89
|
-
if (!pathname) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const resolvedPath = pathname.startsWith('/')
|
|
94
|
-
? stripBasePath(pathname, basePath)
|
|
95
|
-
: normalizeUrlPath(
|
|
96
|
-
path.posix.join(path.posix.dirname(`/${fromAssetPath}`), pathname),
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
return normalizeUrlPath(decodeURIComponent(resolvedPath));
|
|
101
|
-
} catch {
|
|
102
|
-
return normalizeUrlPath(resolvedPath);
|
|
103
|
-
}
|
|
37
|
+
return path.posix.extname(normalizedTarget) ? normalizedTarget : null;
|
|
104
38
|
}
|
|
105
39
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
knownAssets: Set<string>;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function resolveHrefToHtmlAssetPath({
|
|
114
|
-
href,
|
|
115
|
-
fromAssetPath,
|
|
116
|
-
basePath = '/',
|
|
117
|
-
knownAssets,
|
|
118
|
-
}: ResolveHrefToHtmlOptions): string | null {
|
|
119
|
-
const decodedPath = resolveInternalPath({ href, fromAssetPath, basePath });
|
|
120
|
-
if (!decodedPath) {
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
for (const candidate of toCandidateAssetPaths(decodedPath)) {
|
|
125
|
-
if (knownAssets.has(candidate)) {
|
|
126
|
-
return candidate;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
interface ResolveHrefToPdfOptions {
|
|
134
|
-
href: string | null;
|
|
135
|
-
fromAssetPath: string;
|
|
136
|
-
basePath?: string;
|
|
137
|
-
knownPdfPaths: Set<string>;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function resolveHrefToPdfPath({
|
|
141
|
-
href,
|
|
142
|
-
fromAssetPath,
|
|
143
|
-
basePath = '/',
|
|
144
|
-
knownPdfPaths,
|
|
145
|
-
}: ResolveHrefToPdfOptions): string | null {
|
|
146
|
-
const decodedPath = resolveInternalPath({ href, fromAssetPath, basePath });
|
|
147
|
-
if (!decodedPath || !decodedPath.toLowerCase().endsWith('.pdf')) {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return knownPdfPaths.has(decodedPath) ? decodedPath : null;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
interface CollectDirectLinksOptions {
|
|
155
|
-
html: string;
|
|
156
|
-
fromAssetPath: string;
|
|
157
|
-
knownAssets: Set<string>;
|
|
158
|
-
knownPdfPaths?: Set<string>;
|
|
159
|
-
basePath?: string;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
interface DirectSiteAssetLinks {
|
|
163
|
-
htmlAssetPaths: string[];
|
|
164
|
-
pdfPaths: string[];
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
export function collectDirectSiteAssetLinks({
|
|
168
|
-
html,
|
|
169
|
-
fromAssetPath,
|
|
170
|
-
knownAssets,
|
|
171
|
-
knownPdfPaths = new Set(),
|
|
172
|
-
basePath = '/',
|
|
173
|
-
}: CollectDirectLinksOptions): DirectSiteAssetLinks {
|
|
40
|
+
export function collectOutgoingHtmlAssetPaths(
|
|
41
|
+
analysis: HtmlOutputAnalysis,
|
|
42
|
+
knownHtmlAssetPaths: Set<string>,
|
|
43
|
+
): string[] {
|
|
174
44
|
const htmlAssetPaths = new Set<string>();
|
|
175
|
-
const pdfPaths = new Set<string>();
|
|
176
|
-
const dom = new JSDOM(html);
|
|
177
|
-
|
|
178
|
-
try {
|
|
179
|
-
const links = dom.window.document.querySelectorAll('a');
|
|
180
|
-
for (const link of links) {
|
|
181
|
-
if (link.classList.contains('disabled')) {
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const href = link.getAttribute('href');
|
|
186
|
-
const pdfPath = resolveHrefToPdfPath({
|
|
187
|
-
href,
|
|
188
|
-
fromAssetPath,
|
|
189
|
-
basePath,
|
|
190
|
-
knownPdfPaths,
|
|
191
|
-
});
|
|
192
|
-
if (pdfPath) {
|
|
193
|
-
pdfPaths.add(pdfPath);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const targetPath = resolveHrefToHtmlAssetPath({
|
|
197
|
-
href,
|
|
198
|
-
fromAssetPath,
|
|
199
|
-
basePath,
|
|
200
|
-
knownAssets,
|
|
201
|
-
});
|
|
202
|
-
if (targetPath) {
|
|
203
|
-
htmlAssetPaths.add(targetPath);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
45
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
basePath,
|
|
213
|
-
knownAssets,
|
|
214
|
-
});
|
|
215
|
-
if (targetPath) {
|
|
216
|
-
htmlAssetPaths.add(targetPath);
|
|
46
|
+
for (const target of analysis.outgoingTargets) {
|
|
47
|
+
for (const candidate of toCandidateHtmlAssetPaths(target)) {
|
|
48
|
+
if (knownHtmlAssetPaths.has(candidate)) {
|
|
49
|
+
htmlAssetPaths.add(candidate);
|
|
50
|
+
break;
|
|
217
51
|
}
|
|
218
52
|
}
|
|
219
|
-
} finally {
|
|
220
|
-
dom.window.close();
|
|
221
53
|
}
|
|
222
54
|
|
|
223
|
-
return
|
|
224
|
-
htmlAssetPaths: [...htmlAssetPaths].sort(),
|
|
225
|
-
pdfPaths: [...pdfPaths].sort(),
|
|
226
|
-
};
|
|
55
|
+
return [...htmlAssetPaths].sort();
|
|
227
56
|
}
|
|
228
57
|
|
|
229
58
|
interface CollectReachableOptions {
|
|
230
|
-
|
|
231
|
-
|
|
59
|
+
htmlAnalysisByPath: Map<string, HtmlOutputAnalysis>;
|
|
60
|
+
knownAssetTargets?: Set<string>;
|
|
232
61
|
rootPath?: string;
|
|
233
|
-
basePath?: string;
|
|
234
62
|
}
|
|
235
63
|
|
|
236
64
|
interface ReachableSiteAssets {
|
|
237
65
|
reachableHtmlPaths: string[];
|
|
238
|
-
|
|
66
|
+
reachableAssetTargets: string[];
|
|
239
67
|
}
|
|
240
68
|
|
|
241
69
|
export function collectReachableSiteAssets({
|
|
242
|
-
|
|
243
|
-
|
|
70
|
+
htmlAnalysisByPath,
|
|
71
|
+
knownAssetTargets = new Set(),
|
|
244
72
|
rootPath = 'index.html',
|
|
245
|
-
basePath = '/',
|
|
246
73
|
}: CollectReachableOptions): ReachableSiteAssets {
|
|
247
|
-
if (!
|
|
74
|
+
if (!htmlAnalysisByPath.has(rootPath)) {
|
|
248
75
|
throw new Error(`Pagefind reachability root not found: ${rootPath}`);
|
|
249
76
|
}
|
|
250
77
|
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
const
|
|
78
|
+
const knownHtmlAssetPaths = new Set(htmlAnalysisByPath.keys());
|
|
79
|
+
const reachableHtmlPaths = new Set<string>();
|
|
80
|
+
const reachableAssetTargets = new Set<string>();
|
|
254
81
|
const pending: string[] = [rootPath];
|
|
255
82
|
|
|
256
83
|
while (pending.length > 0) {
|
|
257
84
|
const currentPath = pending.pop()!;
|
|
258
|
-
if (
|
|
85
|
+
if (reachableHtmlPaths.has(currentPath)) {
|
|
259
86
|
continue;
|
|
260
87
|
}
|
|
261
|
-
|
|
88
|
+
reachableHtmlPaths.add(currentPath);
|
|
262
89
|
|
|
263
|
-
const
|
|
264
|
-
const { htmlAssetPaths, pdfPaths } = collectDirectSiteAssetLinks({
|
|
265
|
-
html,
|
|
266
|
-
fromAssetPath: currentPath,
|
|
267
|
-
knownAssets,
|
|
268
|
-
knownPdfPaths,
|
|
269
|
-
basePath,
|
|
270
|
-
});
|
|
90
|
+
const analysis = htmlAnalysisByPath.get(currentPath)!;
|
|
271
91
|
|
|
272
|
-
for (const
|
|
273
|
-
|
|
92
|
+
for (const targetPath of collectOutgoingHtmlAssetPaths(
|
|
93
|
+
analysis,
|
|
94
|
+
knownHtmlAssetPaths,
|
|
95
|
+
)) {
|
|
96
|
+
if (!reachableHtmlPaths.has(targetPath)) {
|
|
97
|
+
pending.push(targetPath);
|
|
98
|
+
}
|
|
274
99
|
}
|
|
275
100
|
|
|
276
|
-
for (const
|
|
277
|
-
|
|
278
|
-
|
|
101
|
+
for (const target of analysis.outgoingTargets) {
|
|
102
|
+
const assetTarget = resolveTargetToAssetTarget(target);
|
|
103
|
+
if (assetTarget && knownAssetTargets.has(assetTarget)) {
|
|
104
|
+
reachableAssetTargets.add(assetTarget);
|
|
279
105
|
}
|
|
280
106
|
}
|
|
281
107
|
}
|
|
282
108
|
|
|
283
109
|
return {
|
|
284
|
-
reachableHtmlPaths: [...
|
|
285
|
-
|
|
110
|
+
reachableHtmlPaths: [...reachableHtmlPaths].sort(),
|
|
111
|
+
reachableAssetTargets: [...reachableAssetTargets].sort(),
|
|
286
112
|
};
|
|
287
113
|
}
|
|
288
114
|
|
|
@@ -291,26 +117,3 @@ export function collectReachableHtmlAssets(
|
|
|
291
117
|
): string[] {
|
|
292
118
|
return collectReachableSiteAssets(options).reachableHtmlPaths;
|
|
293
119
|
}
|
|
294
|
-
|
|
295
|
-
function getMetaRefreshTarget(document: Document): string | null {
|
|
296
|
-
const refreshTags = document.querySelectorAll('meta[http-equiv]');
|
|
297
|
-
for (const tag of refreshTags) {
|
|
298
|
-
const httpEquiv = tag.getAttribute('http-equiv');
|
|
299
|
-
if (!httpEquiv || httpEquiv.toLowerCase() !== 'refresh') {
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const content = tag.getAttribute('content') || '';
|
|
304
|
-
const match = content.match(/(?:^|;)\s*url\s*=\s*(.+)\s*$/i);
|
|
305
|
-
if (!match) {
|
|
306
|
-
continue;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const target = match[1].trim().replace(/^['"]|['"]$/g, '');
|
|
310
|
-
if (target) {
|
|
311
|
-
return target;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return null;
|
|
316
|
-
}
|
package/build/serve.ts
CHANGED
|
@@ -2,9 +2,14 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { makeLogger } from './log';
|
|
4
4
|
import { B } from './colors';
|
|
5
|
+
import { WATCH_RELOAD_PATH, WATCH_RELOAD_TOPIC } from './watch/reload';
|
|
5
6
|
|
|
6
7
|
const log = makeLogger(import.meta.url);
|
|
7
8
|
|
|
9
|
+
interface ServerUpgradeTarget {
|
|
10
|
+
upgrade(req: Request): boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
export function resolvePathname(
|
|
9
14
|
distDir: string,
|
|
10
15
|
pathname: string,
|
|
@@ -40,13 +45,21 @@ export interface StartServerOptions {
|
|
|
40
45
|
port?: number;
|
|
41
46
|
distDir: string;
|
|
42
47
|
onReady?: (port: number) => void;
|
|
48
|
+
watchReload?: { onClientOpen?: () => void; onClientClose?: () => void };
|
|
43
49
|
}
|
|
44
50
|
|
|
45
|
-
export function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
export function createDevServerFetchHandler(
|
|
52
|
+
distDir: string,
|
|
53
|
+
watchReloadEnabled = false,
|
|
54
|
+
) {
|
|
55
|
+
return (req: Request, server: ServerUpgradeTarget): Response | undefined => {
|
|
49
56
|
const url = new URL(req.url);
|
|
57
|
+
if (watchReloadEnabled && url.pathname === WATCH_RELOAD_PATH) {
|
|
58
|
+
return server.upgrade(req)
|
|
59
|
+
? undefined
|
|
60
|
+
: new Response('WebSocket upgrade error', { status: 400 });
|
|
61
|
+
}
|
|
62
|
+
|
|
50
63
|
const result = resolvePathname(distDir, url.pathname);
|
|
51
64
|
if (!result) {
|
|
52
65
|
return new Response('Not Found', { status: 404 });
|
|
@@ -67,22 +80,38 @@ export function startServer(options: StartServerOptions): void {
|
|
|
67
80
|
}
|
|
68
81
|
|
|
69
82
|
return new Response(Bun.file(result.filePath), { headers });
|
|
70
|
-
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function startServer(options: StartServerOptions) {
|
|
87
|
+
const { port = 8080, distDir, onReady, watchReload } = options;
|
|
71
88
|
|
|
72
89
|
try {
|
|
90
|
+
const fetch = createDevServerFetchHandler(distDir, Boolean(watchReload));
|
|
73
91
|
const server = Bun.serve({
|
|
74
92
|
port,
|
|
75
|
-
fetch
|
|
93
|
+
fetch,
|
|
76
94
|
error(error: Error) {
|
|
77
95
|
log.error`Request failed: ${error}`;
|
|
78
96
|
return new Response('Internal Server Error', { status: 500 });
|
|
79
97
|
},
|
|
98
|
+
websocket: {
|
|
99
|
+
message() {},
|
|
100
|
+
open(ws) {
|
|
101
|
+
ws.subscribe(WATCH_RELOAD_TOPIC);
|
|
102
|
+
watchReload?.onClientOpen?.();
|
|
103
|
+
},
|
|
104
|
+
close() {
|
|
105
|
+
watchReload?.onClientClose?.();
|
|
106
|
+
},
|
|
107
|
+
},
|
|
80
108
|
});
|
|
81
109
|
|
|
82
110
|
log.info`Dev server: ${B`http://localhost:${server.port}/index.html`}`;
|
|
83
111
|
if (onReady) {
|
|
84
112
|
onReady(server.port!);
|
|
85
113
|
}
|
|
114
|
+
return server;
|
|
86
115
|
} catch (err) {
|
|
87
116
|
log.error`Failed to start server on port ${port}: ${err}`;
|
|
88
117
|
process.exit(1);
|
package/build/site-variables.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
1
|
import { bundledLanguages } from 'shiki';
|
|
4
2
|
import type { BundledLanguage } from 'shiki';
|
|
3
|
+
import { getSiteConfigBaseName, type SiteEnv } from './config-files';
|
|
4
|
+
import { loadSiteConfig } from './config-loader';
|
|
5
5
|
import { compile as compileJsonSchema, doValidation } from './json-schema';
|
|
6
6
|
import { getProjectDir } from './utils/paths';
|
|
7
7
|
import type { PlainTextLanguage, SiteVariables } from './types';
|
|
@@ -17,12 +17,6 @@ const DEFAULT: Partial<SiteVariables> = {
|
|
|
17
17
|
|
|
18
18
|
const isValid = compileJsonSchema(siteSchema);
|
|
19
19
|
|
|
20
|
-
function getJson(filePath: string): Record<string, unknown> {
|
|
21
|
-
return JSON.parse(
|
|
22
|
-
fs.readFileSync(path.resolve(configDir, filePath), 'utf-8'),
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
20
|
const PLAIN_TEXT_LANGUAGES = ['plain', 'text', 'txt'] as const;
|
|
27
21
|
|
|
28
22
|
export function isPlainTextLanguage(lang: string): lang is PlainTextLanguage {
|
|
@@ -112,9 +106,10 @@ export function getRuntimeBundledShikiLanguages(
|
|
|
112
106
|
return [...new Set(configured)];
|
|
113
107
|
}
|
|
114
108
|
|
|
115
|
-
function getSiteVariables(env:
|
|
116
|
-
const
|
|
117
|
-
const
|
|
109
|
+
function getSiteVariables(env: SiteEnv): SiteVariables {
|
|
110
|
+
const loaded = loadSiteConfig(configDir, env);
|
|
111
|
+
const fileName = loaded.fileName;
|
|
112
|
+
const fromFile = getValidatedSiteConfigRoot(loaded.value, fileName);
|
|
118
113
|
const extensionToShikiLanguage = validateExtensionToShikiLanguage(
|
|
119
114
|
fromFile.extensionToShikiLanguage,
|
|
120
115
|
fileName,
|
|
@@ -167,3 +162,17 @@ export function getDevSiteVariables(): SiteVariables {
|
|
|
167
162
|
export function getProdSiteVariables(): SiteVariables {
|
|
168
163
|
return getSiteVariables('prod');
|
|
169
164
|
}
|
|
165
|
+
|
|
166
|
+
export { getSiteConfigBaseName };
|
|
167
|
+
|
|
168
|
+
function getValidatedSiteConfigRoot(
|
|
169
|
+
value: unknown,
|
|
170
|
+
fileName: string,
|
|
171
|
+
): Record<string, unknown> {
|
|
172
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
173
|
+
return value as Record<string, unknown>;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
doValidation(isValid, value, fileName);
|
|
177
|
+
throw new Error(`${fileName} failed validation`);
|
|
178
|
+
}
|