@abreen/tada 1.0.2 → 1.1.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.
Files changed (120) hide show
  1. package/README.md +29 -33
  2. package/bin/tada.ts +356 -0
  3. package/bin/validators.test.ts +204 -0
  4. package/bin/validators.ts +83 -0
  5. package/{webpack/apply-base-path-plugin.js → build/apply-base-path-plugin.ts} +16 -7
  6. package/build/bundle.ts +117 -0
  7. package/{webpack/code.test.js → build/code.test.ts} +6 -7
  8. package/build/colors.ts +25 -0
  9. package/build/content-watch.ts +107 -0
  10. package/build/copy.ts +118 -0
  11. package/{webpack/deflist-id-plugin.js → build/deflist-id-plugin.ts} +7 -6
  12. package/{webpack/external-links-plugin.js → build/external-links-plugin.ts} +14 -5
  13. package/build/features.ts +11 -0
  14. package/build/generate-content-assets.ts +315 -0
  15. package/build/generate-favicon.ts +165 -0
  16. package/build/generate-fonts.ts +31 -0
  17. package/{webpack/generate-manifest-plugin.js → build/generate-manifest.ts} +29 -36
  18. package/build/globals.test.ts +101 -0
  19. package/{webpack/globals.js → build/globals.ts} +28 -13
  20. package/{webpack/heading-subtitle-plugin.js → build/heading-subtitle-plugin.ts} +4 -2
  21. package/build/json-schema.test.ts +57 -0
  22. package/build/json-schema.ts +33 -0
  23. package/build/log.test.ts +111 -0
  24. package/build/log.ts +167 -0
  25. package/{webpack/markdown-plugins.test.js → build/markdown-plugins.test.ts} +94 -9
  26. package/{webpack/pagefind-plugin.test.js → build/pagefind.test.ts} +74 -13
  27. package/build/pagefind.ts +339 -0
  28. package/{webpack/pdf-text.js → build/pdf-text.ts} +47 -27
  29. package/build/pipeline.ts +93 -0
  30. package/{webpack/reachability.test.js → build/reachability.test.ts} +3 -3
  31. package/{webpack/reachability.js → build/reachability.ts} +77 -34
  32. package/build/serve.ts +112 -0
  33. package/{webpack/site-variables.js → build/site-variables.ts} +22 -15
  34. package/{webpack → build}/site.schema.json +3 -10
  35. package/{webpack/templates.js → build/templates.ts} +35 -33
  36. package/{webpack/text-to-id.js → build/text-to-id.ts} +2 -2
  37. package/build/toc-plugin.test.ts +105 -0
  38. package/{webpack/toc-plugin.js → build/toc-plugin.ts} +32 -13
  39. package/build/types.ts +172 -0
  40. package/build/util.ts +26 -0
  41. package/{webpack/utils/code.js → build/utils/code.ts} +119 -60
  42. package/{webpack/utils/content-files.js → build/utils/content-files.ts} +40 -35
  43. package/build/utils/derive-theme.test.ts +111 -0
  44. package/build/utils/derive-theme.ts +85 -0
  45. package/build/utils/file-types.test.ts +61 -0
  46. package/build/utils/file-types.ts +13 -0
  47. package/build/utils/front-matter.test.ts +80 -0
  48. package/{webpack/utils/front-matter.js → build/utils/front-matter.ts} +22 -9
  49. package/{webpack → build}/utils/jdi-runner/LiterateRunner.java +1 -1
  50. package/{webpack/utils/literate-java.js → build/utils/literate-java.ts} +63 -34
  51. package/{webpack/utils/markdown.js → build/utils/markdown.ts} +94 -49
  52. package/build/utils/paths.test.ts +91 -0
  53. package/{webpack/utils/paths.js → build/utils/paths.ts} +14 -22
  54. package/{webpack/utils/render.js → build/utils/render.ts} +188 -123
  55. package/build/utils/shiki-highlighter.ts +29 -0
  56. package/build/validate-internal-links-plugin.test.ts +106 -0
  57. package/{webpack/validate-internal-links-plugin.js → build/validate-internal-links-plugin.ts} +47 -20
  58. package/{webpack/watch-reachability-state.test.js → build/watch-reachability-state.test.ts} +8 -8
  59. package/{webpack/watch-reachability-state.js → build/watch-reachability-state.ts} +63 -24
  60. package/{webpack/watch-reload-client.js → build/watch-reload-client.ts} +3 -1
  61. package/build/watch.ts +573 -0
  62. package/content/index.md +9 -3
  63. package/content/markdown.md +2 -1
  64. package/content/problem_sets/index.html +14 -0
  65. package/fonts/google-sans-code/woff2/GoogleSansCodeVariable-Italic.woff2 +0 -0
  66. package/fonts/google-sans-code/woff2/GoogleSansCodeVariable.woff2 +0 -0
  67. package/fonts/inter/woff2/InterVariable-Italic.woff2 +0 -0
  68. package/fonts/inter/woff2/InterVariable.woff2 +0 -0
  69. package/package.json +28 -19
  70. package/src/_alerts.scss +92 -0
  71. package/src/_base.scss +106 -0
  72. package/src/{layout.scss → _layout.scss} +0 -2
  73. package/src/anchor/style.scss +1 -9
  74. package/src/code/index.ts +3 -3
  75. package/src/code.scss +1 -1
  76. package/src/critical.scss +5 -0
  77. package/src/header/_base.scss +129 -0
  78. package/src/header/style.scss +3 -131
  79. package/src/index.ts +1 -2
  80. package/src/question/style.scss +1 -1
  81. package/src/search/index.ts +36 -15
  82. package/src/search/style.scss +9 -15
  83. package/src/style.scss +6 -269
  84. package/src/toc/style.scss +5 -39
  85. package/src/util.ts +8 -5
  86. package/templates/_theme.scss +38 -14
  87. package/tsconfig.json +10 -6
  88. package/types/file-system-access.d.ts +5 -0
  89. package/types/markdown-it-plugins.d.ts +11 -0
  90. package/types/untyped-modules.d.ts +40 -0
  91. package/bin/tada.js +0 -361
  92. package/content/problem_sets/index.md +0 -6
  93. package/webpack/build-state.js +0 -97
  94. package/webpack/colors.js +0 -15
  95. package/webpack/config.base.js +0 -151
  96. package/webpack/config.dev.js +0 -23
  97. package/webpack/config.prod.js +0 -32
  98. package/webpack/content-watch-plugin.js +0 -153
  99. package/webpack/features.js +0 -5
  100. package/webpack/generate-content-assets-plugin.js +0 -308
  101. package/webpack/generate-favicon-plugin.js +0 -198
  102. package/webpack/generate-fonts-plugin.js +0 -69
  103. package/webpack/json-schema.js +0 -19
  104. package/webpack/log.js +0 -143
  105. package/webpack/pagefind-plugin.js +0 -379
  106. package/webpack/print-flair-plugin.js +0 -22
  107. package/webpack/serve.js +0 -104
  108. package/webpack/util.js +0 -49
  109. package/webpack/utils/define-plugin.js +0 -20
  110. package/webpack/utils/file-types.js +0 -26
  111. package/webpack/utils/parse-hsl.js +0 -8
  112. package/webpack/utils/shiki-highlighter.js +0 -26
  113. package/webpack/watch.js +0 -166
  114. /package/{webpack → build}/flair.json +0 -0
  115. /package/{webpack → build}/utils/jdi-runner/LiterateRunner.class +0 -0
  116. /package/fonts/google-sans-code/{GoogleSansCodeVariable-Italic.ttf → ttf/GoogleSansCodeVariable-Italic.ttf} +0 -0
  117. /package/fonts/google-sans-code/{GoogleSansCodeVariable.ttf → ttf/GoogleSansCodeVariable.ttf} +0 -0
  118. /package/fonts/inter/{InterVariable-Italic.ttf → ttf/InterVariable-Italic.ttf} +0 -0
  119. /package/fonts/inter/{InterVariable.ttf → ttf/InterVariable.ttf} +0 -0
  120. /package/types/{dev.ts → dev.d.ts} +0 -0
@@ -0,0 +1,91 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import path from 'path';
3
+ import {
4
+ getPackageDir,
5
+ getProjectDir,
6
+ getContentDir,
7
+ getDistDir,
8
+ getPublicDir,
9
+ getConfigDir,
10
+ createApplyBasePath,
11
+ normalizeOutputPath,
12
+ } from './paths.js';
13
+ import type { SiteVariables } from '../types.js';
14
+
15
+ describe('getPackageDir', () => {
16
+ test('returns an absolute path', () => {
17
+ expect(path.isAbsolute(getPackageDir())).toBe(true);
18
+ });
19
+
20
+ test('points to repo root (contains package.json)', () => {
21
+ const dir = getPackageDir();
22
+ const pkg = path.join(dir, 'package.json');
23
+ expect(Bun.file(pkg).size).toBeGreaterThan(0);
24
+ });
25
+ });
26
+
27
+ describe('getProjectDir', () => {
28
+ test('returns process.cwd()', () => {
29
+ expect(getProjectDir()).toBe(process.cwd());
30
+ });
31
+ });
32
+
33
+ describe('directory getters', () => {
34
+ test('getContentDir ends with /content', () => {
35
+ expect(getContentDir()).toEndWith('/content');
36
+ });
37
+
38
+ test('getDistDir ends with /dist', () => {
39
+ expect(getDistDir()).toEndWith('/dist');
40
+ });
41
+
42
+ test('getPublicDir ends with /public', () => {
43
+ expect(getPublicDir()).toEndWith('/public');
44
+ });
45
+
46
+ test('getConfigDir equals getProjectDir', () => {
47
+ expect(getConfigDir()).toBe(getProjectDir());
48
+ });
49
+ });
50
+
51
+ describe('createApplyBasePath', () => {
52
+ function apply(basePath: string, subPath: string): string {
53
+ const site = { base: '', basePath } as SiteVariables;
54
+ return createApplyBasePath(site)(subPath);
55
+ }
56
+
57
+ test('prepends basePath to subPath', () => {
58
+ expect(apply('/course', '/page.html')).toBe('/course/page.html');
59
+ });
60
+
61
+ test('handles basePath with trailing slash', () => {
62
+ expect(apply('/course/', '/page.html')).toBe('/course/page.html');
63
+ });
64
+
65
+ test('handles root basePath', () => {
66
+ expect(apply('/', '/page.html')).toBe('/page.html');
67
+ });
68
+
69
+ test('throws for subPath without leading slash', () => {
70
+ expect(() => apply('/course', 'page.html')).toThrow('must start with "/"');
71
+ });
72
+ });
73
+
74
+ describe('normalizeOutputPath', () => {
75
+ test('adds leading slash if missing', () => {
76
+ expect(normalizeOutputPath('page.html')).toBe('/page.html');
77
+ });
78
+
79
+ test('preserves leading slash', () => {
80
+ expect(normalizeOutputPath('/page.html')).toBe('/page.html');
81
+ });
82
+
83
+ test('normalizes dot-segments', () => {
84
+ expect(normalizeOutputPath('/a/../b/page.html')).toBe('/b/page.html');
85
+ });
86
+
87
+ test('returns / for empty or dot path', () => {
88
+ expect(normalizeOutputPath('')).toBe('/');
89
+ expect(normalizeOutputPath('.')).toBe('/');
90
+ });
91
+ });
@@ -1,31 +1,34 @@
1
- const path = require('path');
1
+ import path from 'path';
2
+ import type { SiteVariables } from '../types.js';
2
3
 
3
- function getPackageDir() {
4
+ export function getPackageDir(): string {
4
5
  return path.resolve(__dirname, '..', '..');
5
6
  }
6
7
 
7
- function getProjectDir() {
8
+ export function getProjectDir(): string {
8
9
  return process.cwd();
9
10
  }
10
11
 
11
- function getContentDir() {
12
+ export function getContentDir(): string {
12
13
  return path.resolve(getProjectDir(), 'content');
13
14
  }
14
15
 
15
- function getDistDir() {
16
+ export function getDistDir(): string {
16
17
  return path.resolve(getProjectDir(), 'dist');
17
18
  }
18
19
 
19
- function getPublicDir() {
20
+ export function getPublicDir(): string {
20
21
  return path.resolve(getProjectDir(), 'public');
21
22
  }
22
23
 
23
- function getConfigDir() {
24
- return path.resolve(getProjectDir(), 'config');
24
+ export function getConfigDir(): string {
25
+ return getProjectDir();
25
26
  }
26
27
 
27
- function createApplyBasePath(siteVariables) {
28
- return function applyBasePath(subPath) {
28
+ export function createApplyBasePath(
29
+ siteVariables: SiteVariables,
30
+ ): (subPath: string) => string {
31
+ return function applyBasePath(subPath: string): string {
29
32
  if (!subPath.startsWith('/')) {
30
33
  throw new Error('invalid internal path, must start with "/": ' + subPath);
31
34
  }
@@ -38,21 +41,10 @@ function createApplyBasePath(siteVariables) {
38
41
  };
39
42
  }
40
43
 
41
- function normalizeOutputPath(outputPath) {
44
+ export function normalizeOutputPath(outputPath: string): string {
42
45
  const normalized = path.posix.normalize(outputPath);
43
46
  if (normalized === '.' || normalized === '') {
44
47
  return '/';
45
48
  }
46
49
  return normalized.startsWith('/') ? normalized : `/${normalized}`;
47
50
  }
48
-
49
- module.exports = {
50
- createApplyBasePath,
51
- getConfigDir,
52
- getContentDir,
53
- getDistDir,
54
- getPackageDir,
55
- getProjectDir,
56
- getPublicDir,
57
- normalizeOutputPath,
58
- };
@@ -1,39 +1,69 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const _ = require('lodash');
4
- const { stripHtml } = require('string-strip-html');
5
- const { makeLogger } = require('../log');
6
- const { B } = require('../colors');
7
- const createGlobals = require('../globals');
8
- const { render, json } = require('../templates');
9
- const {
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import _ from 'lodash';
4
+ import { stripHtml } from 'string-strip-html';
5
+ import { makeLogger } from '../log.js';
6
+ import { B } from '../colors.js';
7
+ import createGlobals from '../globals.js';
8
+ import { render, json } from '../templates.js';
9
+ import {
10
10
  extractJavaMethodToc,
11
11
  renderCodeSegment,
12
12
  renderCodeWithComments,
13
- } = require('./code');
14
- const { extensionIsMarkdown } = require('./file-types');
15
- const { createApplyBasePath, normalizeOutputPath } = require('./paths');
16
- const { parseFrontMatterAndContent } = require('./front-matter');
17
- const { createMarkdown } = require('./markdown');
18
- const { generateTocHtml, generateCodeTocHtml } = require('../toc-plugin');
19
- const {
13
+ } from './code.js';
14
+ import { extensionIsMarkdown } from './file-types.js';
15
+ import {
16
+ createApplyBasePath,
17
+ normalizeOutputPath,
18
+ getDistDir,
19
+ } from './paths.js';
20
+ import { parseFrontMatterAndContent } from './front-matter.js';
21
+ import { createMarkdown } from './markdown.js';
22
+ import { generateTocHtml, generateCodeTocHtml } from '../toc-plugin.js';
23
+ import {
20
24
  parseLiterateJava,
21
25
  hasMainMethod,
22
26
  deriveClassName,
23
27
  compileJavaSource,
24
28
  executeLiterateJava,
25
- } = require('./literate-java');
29
+ } from './literate-java.js';
30
+ import type {
31
+ Asset,
32
+ SiteVariables,
33
+ RenderPlainTextOptions,
34
+ RenderCodePageOptions,
35
+ RenderLiterateJavaOptions,
36
+ RenderCopiedContentOptions,
37
+ } from '../types.js';
26
38
 
27
39
  const log = makeLogger(__filename);
28
40
 
29
41
  const REQUIRED_FRONT_MATTER_FIELDS = ['title'];
30
42
 
31
- function resolveAuthor(pageVariables, filePath) {
43
+ interface TemplateParametersInput {
44
+ pageVariables: Record<string, unknown>;
45
+ siteVariables: SiteVariables;
46
+ content: string | null;
47
+ applyBasePath: (subPath: string) => string;
48
+ subPath: string;
49
+ }
50
+
51
+ function resolveAuthor(
52
+ pageVariables: Record<string, unknown>,
53
+ filePath: string,
54
+ ): void {
32
55
  if (!pageVariables.author) {
33
56
  return;
34
57
  }
35
- const authors = json('authors.json');
36
- const authorKey = pageVariables.author;
58
+ const authors = json('authors.json') as
59
+ | Record<string, Record<string, unknown>>
60
+ | undefined;
61
+ if (!authors) {
62
+ throw new Error(
63
+ `${filePath}: author "${pageVariables.author}" specified but no authors.json found`,
64
+ );
65
+ }
66
+ const authorKey = pageVariables.author as string;
37
67
  const authorEntry = authors[authorKey];
38
68
  if (!authorEntry) {
39
69
  throw new Error(
@@ -43,15 +73,20 @@ function resolveAuthor(pageVariables, filePath) {
43
73
  pageVariables.author = authorEntry;
44
74
  }
45
75
 
46
- function validateFrontMatter(pageVariables, filePath) {
47
- let valid = true;
48
- for (const field of REQUIRED_FRONT_MATTER_FIELDS) {
49
- if (!pageVariables[field]) {
50
- log.error`${filePath}: missing required front matter field: "${field}"`;
51
- valid = false;
52
- }
76
+ function validateFrontMatter(
77
+ pageVariables: Record<string, unknown>,
78
+ filePath: string,
79
+ ): void {
80
+ const missing = REQUIRED_FRONT_MATTER_FIELDS.filter(
81
+ field => !pageVariables[field],
82
+ );
83
+ if (missing.length > 0) {
84
+ const noun = missing.length === 1 ? 'field' : 'fields';
85
+ const fields = missing.map(f => `"${f}"`).join(', ');
86
+ throw new Error(
87
+ `${filePath}: missing required front matter ${noun}: ${fields}`,
88
+ );
53
89
  }
54
- return valid;
55
90
  }
56
91
 
57
92
  function createTemplateParameters({
@@ -60,7 +95,7 @@ function createTemplateParameters({
60
95
  content,
61
96
  applyBasePath,
62
97
  subPath,
63
- }) {
98
+ }: TemplateParametersInput): Record<string, unknown> {
64
99
  return {
65
100
  ...(siteVariables.vars || {}),
66
101
  ...createGlobals(pageVariables, siteVariables, subPath),
@@ -73,43 +108,54 @@ function createTemplateParameters({
73
108
  };
74
109
  }
75
110
 
76
- function injectWebpackAssets(html, compilation, applyBasePath) {
77
- const assets = compilation.getAssets();
78
- const jsAssets = assets
79
- .filter(asset => asset.name.endsWith('.js'))
80
- .map(asset => asset.name);
81
- const cssAssets = assets
82
- .filter(asset => asset.name.endsWith('.css'))
83
- .map(asset => asset.name);
111
+ export function injectAssetTags(
112
+ html: string,
113
+ assetFiles: string[],
114
+ applyBasePath: (subPath: string) => string,
115
+ ): string {
116
+ const jsAssets = assetFiles.filter(f => f.endsWith('.js'));
117
+ const cssAssets = assetFiles.filter(f => f.endsWith('.css'));
84
118
 
85
119
  const scriptTags = jsAssets
86
120
  .map(asset => `<script defer src="${applyBasePath('/' + asset)}"></script>`)
87
121
  .join('');
88
- const linkTags = cssAssets
122
+ const criticalAssets = cssAssets.filter(f => f.includes('critical.bundle.'));
123
+ const asyncAssets = cssAssets.filter(f => !f.includes('critical.bundle.'));
124
+
125
+ const distDir = getDistDir();
126
+ const criticalTags = criticalAssets
127
+ .map(asset => {
128
+ const css = fs.readFileSync(path.join(distDir, asset), 'utf-8');
129
+ return `<style>${css}</style>`;
130
+ })
131
+ .join('');
132
+ const asyncLinkTags = asyncAssets
89
133
  .map(
90
- asset => `<link href="${applyBasePath('/' + asset)}" rel="stylesheet">`,
134
+ asset =>
135
+ `<link href="${applyBasePath('/' + asset)}" rel="stylesheet" media="print" onload="this.media='all'">` +
136
+ `<noscript><link href="${applyBasePath('/' + asset)}" rel="stylesheet"></noscript>`,
91
137
  )
92
138
  .join('');
93
139
 
94
140
  return html
95
- .replace('<head>', `<head>${linkTags}`)
141
+ .replace('<head>', `<head>${criticalTags}${asyncLinkTags}`)
96
142
  .replace('</head>', `${scriptTags}</head>`);
97
143
  }
98
144
 
99
- function toContentAssetPath(contentDir, filePath) {
145
+ function toContentAssetPath(contentDir: string, filePath: string): string {
100
146
  return path
101
147
  .relative(contentDir, filePath)
102
148
  .split(path.sep)
103
149
  .join(path.posix.sep);
104
150
  }
105
151
 
106
- function renderPlainTextPageAsset({
152
+ export function renderPlainTextPageAsset({
107
153
  filePath,
108
154
  contentDir,
109
155
  siteVariables,
110
156
  validInternalTargets,
111
- compilation,
112
- }) {
157
+ assetFiles,
158
+ }: RenderPlainTextOptions): Asset[] {
113
159
  const { dir, name, ext } = path.parse(filePath);
114
160
  const subPath = path.relative(contentDir, path.join(dir, name));
115
161
  const applyBasePath = createApplyBasePath(siteVariables);
@@ -124,16 +170,16 @@ function renderPlainTextPageAsset({
124
170
  { validateInternalLinks: extensionIsMarkdown(ext.toLowerCase()) },
125
171
  );
126
172
 
127
- if (!validateFrontMatter(pageVariables, filePath)) {
128
- return [];
129
- }
173
+ validateFrontMatter(pageVariables, filePath);
130
174
 
131
175
  if (!pageVariables.template) {
132
176
  pageVariables.template = 'default';
133
177
  }
134
178
 
135
179
  if (pageVariables.toc && tocItems) {
136
- pageVariables.tocHtml = generateTocHtml(tocItems);
180
+ pageVariables.tocHtml = generateTocHtml(
181
+ tocItems as Parameters<typeof generateTocHtml>[0],
182
+ );
137
183
  }
138
184
 
139
185
  const templateParameters = createTemplateParameters({
@@ -144,9 +190,9 @@ function renderPlainTextPageAsset({
144
190
  subPath,
145
191
  });
146
192
 
147
- const html = injectWebpackAssets(
148
- render(`${pageVariables.template}.html`, templateParameters),
149
- compilation,
193
+ const html = injectAssetTags(
194
+ render(`${pageVariables.template}.html`, templateParameters) as string,
195
+ assetFiles,
150
196
  applyBasePath,
151
197
  );
152
198
 
@@ -158,16 +204,16 @@ function renderPlainTextPageAsset({
158
204
  ];
159
205
  }
160
206
 
161
- function renderCodePageAsset({
207
+ export function renderCodePageAsset({
162
208
  filePath,
163
209
  contentDir,
164
210
  siteVariables,
165
- compilation,
166
- }) {
211
+ assetFiles,
212
+ }: RenderCodePageOptions): Asset[] {
167
213
  const { dir, name, ext } = path.parse(filePath);
168
214
  const subPath = path.relative(contentDir, path.join(dir, name));
169
215
  const applyBasePath = createApplyBasePath(siteVariables);
170
- const lang = siteVariables.codeLanguages[ext.slice(1).toLowerCase()];
216
+ const lang = siteVariables.codeLanguages![ext.slice(1).toLowerCase()];
171
217
  const sourceCode = fs.readFileSync(filePath, 'utf-8');
172
218
 
173
219
  log.info`Rendering code page ${B`${subPath + ext}`}`;
@@ -178,7 +224,7 @@ function renderCodePageAsset({
178
224
  const titleHtml = `<tt>${name + ext}</tt>`;
179
225
  const tocItems = lang === 'java' ? extractJavaMethodToc(sourceCode) : [];
180
226
  const tocHtml = generateCodeTocHtml(tocItems);
181
- const pageVariables = {
227
+ const pageVariables: Record<string, unknown> = {
182
228
  template: 'code',
183
229
  filePath,
184
230
  title: `${name}${ext}`,
@@ -197,9 +243,9 @@ function renderCodePageAsset({
197
243
  subPath,
198
244
  });
199
245
 
200
- const html = injectWebpackAssets(
201
- render('code.html', templateParameters),
202
- compilation,
246
+ const html = injectAssetTags(
247
+ render('code.html', templateParameters) as string,
248
+ assetFiles,
203
249
  applyBasePath,
204
250
  );
205
251
 
@@ -211,9 +257,11 @@ function renderCodePageAsset({
211
257
  ];
212
258
  }
213
259
 
214
- function renderCopiedContentAsset({ filePath, contentDir }) {
215
- const ext = path.extname(filePath).toLowerCase();
216
- const label = ext === '.pdf' ? 'Copying' : 'Copying source file';
260
+ export function renderCopiedContentAsset({
261
+ filePath,
262
+ contentDir,
263
+ }: RenderCopiedContentOptions): Asset[] {
264
+ const label = 'Copying source file';
217
265
  const relPath = toContentAssetPath(contentDir, filePath);
218
266
 
219
267
  log.info`${label} ${B`${relPath}`}`;
@@ -222,13 +270,17 @@ function renderCopiedContentAsset({ filePath, contentDir }) {
222
270
 
223
271
  /** Parses the file, renders using template, returns HTML & params used to generate page */
224
272
  function renderPlainTextContent(
225
- filePath,
226
- subPath,
227
- siteVariables,
228
- applyBasePath,
229
- validInternalTargets,
273
+ filePath: string,
274
+ subPath: string,
275
+ siteVariables: SiteVariables,
276
+ applyBasePath: (subPath: string) => string,
277
+ validInternalTargets: Set<string>,
230
278
  { validateInternalLinks = true } = {},
231
- ) {
279
+ ): {
280
+ content: string | null;
281
+ pageVariables: Record<string, unknown>;
282
+ tocItems: unknown[] | null;
283
+ } {
232
284
  const sourceUrlPath = `/${subPath}.html`;
233
285
  const md = createMarkdown(siteVariables, {
234
286
  validatorOptions: {
@@ -239,7 +291,7 @@ function renderPlainTextContent(
239
291
  codeExtensions:
240
292
  siteVariables.features?.code === false
241
293
  ? []
242
- : Object.keys(siteVariables.codeLanguages),
294
+ : Object.keys(siteVariables.codeLanguages!),
243
295
  },
244
296
  });
245
297
 
@@ -256,25 +308,32 @@ function renderPlainTextContent(
256
308
  applyBasePath,
257
309
  subPath,
258
310
  });
259
- const pageVariablesProcessed = Object.entries(pageVariables)
311
+ const pageVariablesProcessed: Record<string, unknown> = Object.entries(
312
+ pageVariables,
313
+ )
260
314
  .map(([k, v]) => {
261
315
  const newValue =
262
316
  typeof v === 'string' ? _.template(v)(siteOnlyParams) : v;
263
- return [k, newValue];
317
+ return [k, newValue] as [string, unknown];
264
318
  })
265
- .reduce((acc, [k, v]) => {
266
- acc[k] = v;
267
- return acc;
268
- }, {});
319
+ .reduce(
320
+ (acc, [k, v]) => {
321
+ acc[k] = v;
322
+ return acc;
323
+ },
324
+ {} as Record<string, unknown>,
325
+ );
269
326
 
270
327
  // Render title and description as inline Markdown
271
328
  if (pageVariablesProcessed.title) {
272
- const titleHtml = md.renderInline(pageVariablesProcessed.title);
329
+ const titleHtml = md.renderInline(pageVariablesProcessed.title as string);
273
330
  pageVariablesProcessed.titleHtml = titleHtml;
274
331
  pageVariablesProcessed.title = stripHtml(titleHtml).result;
275
332
  }
276
333
  if (pageVariablesProcessed.description) {
277
- const descriptionHtml = md.renderInline(pageVariablesProcessed.description);
334
+ const descriptionHtml = md.renderInline(
335
+ pageVariablesProcessed.description as string,
336
+ );
278
337
  pageVariablesProcessed.descriptionHtml = descriptionHtml;
279
338
  pageVariablesProcessed.description = stripHtml(descriptionHtml).result;
280
339
  }
@@ -291,35 +350,41 @@ function renderPlainTextContent(
291
350
  subPath,
292
351
  });
293
352
 
294
- let html = null;
353
+ let html: string;
295
354
  try {
296
355
  html = _.template(strippedContent)(params);
297
- } catch (err) {
356
+ } catch (err: unknown) {
298
357
  throw new Error(
299
- `${filePath}: Lodash template error in page or template: ${err.message}`,
358
+ `${filePath}: Lodash template error in page or template: ${(err as Error).message}`,
359
+ { cause: err },
300
360
  );
301
361
  }
302
362
 
303
- let tocItems = null;
363
+ let tocItems: unknown[] | null = null;
304
364
  if (extensionIsMarkdown(ext)) {
305
- const env = {};
306
- html = md.render(html, env);
307
- tocItems = env.tocItems || null;
365
+ const env: Record<string, unknown> = {};
366
+ html = md.render(html!, env);
367
+ tocItems = (env.tocItems as unknown[] | undefined) || null;
308
368
  }
309
369
 
310
- return { content: html, pageVariables: params.page, tocItems };
370
+ return {
371
+ content: html,
372
+ pageVariables: params.page as Record<string, unknown>,
373
+ tocItems,
374
+ };
311
375
  }
312
376
 
313
- function stripHtmlComments(str) {
377
+ export function stripHtmlComments(str: string): string {
314
378
  return str.replace(/<!---[\s\S]*?-->/g, '');
315
379
  }
316
380
 
317
- function renderLiterateJavaPageAsset({
381
+ export function renderLiterateJavaPageAsset({
318
382
  filePath,
319
383
  contentDir,
320
384
  siteVariables,
321
- compilation,
322
- }) {
385
+ assetFiles,
386
+ skipExecution,
387
+ }: RenderLiterateJavaOptions): Asset[] {
323
388
  const { dir, name } = path.parse(filePath);
324
389
  const className = deriveClassName(filePath);
325
390
  const subPath = path.relative(contentDir, path.join(dir, className));
@@ -336,24 +401,30 @@ function renderLiterateJavaPageAsset({
336
401
  visibleBlockIndices,
337
402
  } = parseLiterateJava(raw, siteVariables);
338
403
 
339
- if (!validateFrontMatter(pageVariables, filePath)) {
340
- return [];
341
- }
342
-
343
- // Compile the concatenated Java source
344
- let tempDir;
345
- let blockOutputMap = null;
346
- try {
347
- tempDir = compileJavaSource(javaSource, className);
348
-
349
- // Execute if there is a main() method
350
- if (hasMainMethod(javaSource)) {
351
- const outputEntries = executeLiterateJava(className, tempDir, codeBlocks);
352
- blockOutputMap = new Map(outputEntries);
353
- }
354
- } finally {
355
- if (tempDir) {
356
- fs.rmSync(tempDir, { recursive: true, force: true });
404
+ validateFrontMatter(pageVariables, filePath);
405
+
406
+ // Compile and execute the concatenated Java source
407
+ let tempDir: string | undefined;
408
+ let blockOutputMap: Map<number, string> | null = null;
409
+ if (!skipExecution) {
410
+ try {
411
+ tempDir = compileJavaSource(javaSource, className);
412
+
413
+ // Execute if there is a main() method
414
+ if (hasMainMethod(javaSource)) {
415
+ const outputEntries = executeLiterateJava(
416
+ className,
417
+ tempDir,
418
+ codeBlocks,
419
+ );
420
+ blockOutputMap = new Map(
421
+ outputEntries.map(e => [e.blockIndex, e.output]),
422
+ );
423
+ }
424
+ } finally {
425
+ if (tempDir) {
426
+ fs.rmSync(tempDir, { recursive: true, force: true });
427
+ }
357
428
  }
358
429
  }
359
430
 
@@ -376,7 +447,7 @@ function renderLiterateJavaPageAsset({
376
447
  if (line.trim().length === 0) {
377
448
  return min;
378
449
  }
379
- const indent = line.match(/^(\s*)/)[1].length;
450
+ const indent = line.match(/^(\s*)/)![1].length;
380
451
  return Math.min(min, indent);
381
452
  }, Infinity);
382
453
  const dedented =
@@ -404,8 +475,8 @@ function renderLiterateJavaPageAsset({
404
475
  return codeHtml;
405
476
  };
406
477
 
407
- const env = {};
408
- const contentHtml = md.render(content, env);
478
+ const env: Record<string, unknown> = {};
479
+ const contentHtml = md.render(stripHtmlComments(content), env);
409
480
 
410
481
  // Build page variables
411
482
  const javaFileName = `${className}.java`;
@@ -415,7 +486,7 @@ function renderLiterateJavaPageAsset({
415
486
  ),
416
487
  );
417
488
 
418
- const titleHtml = md.renderInline(pageVariables.title);
489
+ const titleHtml = md.renderInline(pageVariables.title as string);
419
490
  pageVariables.titleHtml = titleHtml;
420
491
  pageVariables.title = stripHtml(titleHtml).result;
421
492
  pageVariables.template = 'literate';
@@ -423,7 +494,9 @@ function renderLiterateJavaPageAsset({
423
494
  pageVariables.downloadName = javaFileName;
424
495
 
425
496
  if (pageVariables.toc && env.tocItems) {
426
- pageVariables.tocHtml = generateTocHtml(env.tocItems);
497
+ pageVariables.tocHtml = generateTocHtml(
498
+ env.tocItems as Parameters<typeof generateTocHtml>[0],
499
+ );
427
500
  }
428
501
 
429
502
  resolveAuthor(pageVariables, filePath);
@@ -436,9 +509,9 @@ function renderLiterateJavaPageAsset({
436
509
  subPath,
437
510
  });
438
511
 
439
- const html = injectWebpackAssets(
440
- render('literate.html', templateParameters),
441
- compilation,
512
+ const html = injectAssetTags(
513
+ render('literate.html', templateParameters) as string,
514
+ assetFiles,
442
515
  applyBasePath,
443
516
  );
444
517
 
@@ -456,11 +529,3 @@ function renderLiterateJavaPageAsset({
456
529
  },
457
530
  ];
458
531
  }
459
-
460
- module.exports = {
461
- injectWebpackAssets,
462
- renderCodePageAsset,
463
- renderCopiedContentAsset,
464
- renderLiterateJavaPageAsset,
465
- renderPlainTextPageAsset,
466
- };