@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
@@ -1,198 +0,0 @@
1
- const path = require('path');
2
- const fontkit = require('fontkit');
3
- const sharp = require('sharp');
4
- const { default: pngToIco } = require('png-to-ico');
5
- const { makeLogger } = require('./log');
6
- const { getPackageDir } = require('./utils/paths');
7
-
8
- const log = makeLogger(__filename);
9
-
10
- const FONT_PATH = path.join(
11
- getPackageDir(),
12
- 'fonts',
13
- 'inter',
14
- 'InterVariable.ttf',
15
- );
16
-
17
- function createFaviconSvg(size, color, symbol, font, cssWeight) {
18
- if (size < 10) {
19
- throw new Error(`size is too small: ${size}`);
20
- }
21
-
22
- if (!symbol) {
23
- throw new Error(`invalid symbol: ${symbol}`);
24
- }
25
-
26
- if (symbol.length > 5) {
27
- throw new Error(`symbol is too long (must be <= 5 chars): ${symbol}`);
28
- }
29
-
30
- // Apply variation axes for variable fonts
31
- const instance =
32
- typeof font.getVariation === 'function' &&
33
- font.variationAxes &&
34
- Object.keys(font.variationAxes).length > 0
35
- ? font.getVariation({ wght: cssWeight })
36
- : font;
37
-
38
- const run = instance.layout(symbol);
39
-
40
- // Compute combined bounding box across all glyphs (font units, y-up)
41
- let x = 0;
42
- let totalMinX = Infinity,
43
- totalMaxX = -Infinity;
44
- let totalMinY = Infinity,
45
- totalMaxY = -Infinity;
46
- const advances = [];
47
-
48
- for (let i = 0; i < run.glyphs.length; i++) {
49
- const bbox = run.glyphs[i].bbox;
50
- totalMinX = Math.min(totalMinX, x + bbox.minX);
51
- totalMaxX = Math.max(totalMaxX, x + bbox.maxX);
52
- totalMinY = Math.min(totalMinY, bbox.minY);
53
- totalMaxY = Math.max(totalMaxY, bbox.maxY);
54
- advances.push(x);
55
- x += run.positions[i].xAdvance;
56
- }
57
-
58
- const textW = totalMaxX - totalMinX;
59
- const textH = totalMaxY - totalMinY;
60
-
61
- if (textW <= 0 || textH <= 0) {
62
- throw new Error(`Could not get glyph bounds for symbol: ${symbol}`);
63
- }
64
-
65
- const pad = size * 0.1;
66
- const contentW = size - pad * 2;
67
- const contentH = size - pad * 2;
68
-
69
- // Scale font units to SVG pixels to fill padded content area
70
- const s = Math.min(contentW / textW, contentH / textH);
71
-
72
- // Font coords are y-up; SVG is y-down. The outer transform flips y via scale(s, -s).
73
- // We want the bbox center to land at (size/2, size/2).
74
- const bboxCenterX = (totalMinX + totalMaxX) / 2;
75
- const bboxCenterY = (totalMinY + totalMaxY) / 2;
76
- const tx = size / 2 - bboxCenterX * s;
77
- const ty = size / 2 + bboxCenterY * s;
78
-
79
- // Each glyph is translated by its cumulative x-advance (in font units).
80
- // The outer group handles y-flip and centering.
81
- const paths = run.glyphs
82
- .map((glyph, i) => {
83
- const d = glyph.path.toSVG();
84
- return advances[i] === 0
85
- ? `<path d="${d}" fill="#fff" />`
86
- : `<path d="${d}" transform="translate(${advances[i]} 0)" fill="#fff" />`;
87
- })
88
- .join('\n ');
89
-
90
- return `<svg viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
91
- <rect width="${size}" height="${size}" rx="${size / 10}" fill="${color}" />
92
- <g transform="translate(${tx.toFixed(2)} ${ty.toFixed(2)}) scale(${s.toFixed(4)} ${(-s).toFixed(4)})">
93
- ${paths}
94
- </g>
95
- </svg>`;
96
- }
97
-
98
- class GenerateFaviconPlugin {
99
- _cachedAssets = null;
100
-
101
- constructor(siteVariables, options = {}) {
102
- this.options = {
103
- sizes: options.sizes || [16, 32, 48, 64, 128, 192, 256, 512, 1024],
104
- svgSize: options.svgSize || 512,
105
- filenameBase: options.filenameBase || 'favicon',
106
- color: siteVariables.faviconColor,
107
- symbol: siteVariables.faviconSymbol,
108
- fontWeight: siteVariables.faviconFontWeight || 700,
109
- };
110
- }
111
-
112
- apply(compiler) {
113
- compiler.hooks.thisCompilation.tap('GenerateFaviconPlugin', compilation => {
114
- const wp = compilation.compiler.webpack || {};
115
- const { RawSource } =
116
- (wp.sources && wp.sources) || require('webpack-sources');
117
-
118
- compilation.hooks.processAssets.tapPromise(
119
- {
120
- name: 'GenerateFaviconPlugin',
121
- stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
122
- },
123
- async assets => {
124
- if (this._cachedAssets) {
125
- for (const [name, source] of this._cachedAssets) {
126
- compilation.emitAsset(name, source);
127
- }
128
- return;
129
- }
130
-
131
- const { color, symbol, sizes, filenameBase, svgSize, fontWeight } =
132
- this.options;
133
-
134
- if (!color || !symbol) {
135
- throw new Error('Missing required "color" and/or "symbol" options');
136
- }
137
-
138
- this._cachedAssets = new Map();
139
-
140
- try {
141
- log.info`Generating favicons for "${symbol}"`;
142
- log.debug`Using font file: ${FONT_PATH}`;
143
- const font = fontkit.openSync(FONT_PATH);
144
-
145
- const svgMarkup = createFaviconSvg(
146
- svgSize,
147
- color,
148
- symbol,
149
- font,
150
- fontWeight,
151
- );
152
- const svgSource = new RawSource(svgMarkup);
153
- compilation.emitAsset(`${filenameBase}.svg`, svgSource);
154
- this._cachedAssets.set(`${filenameBase}.svg`, svgSource);
155
-
156
- const pngBuffers = await Promise.all(
157
- sizes.map(async size => {
158
- const svgForSize = createFaviconSvg(
159
- size,
160
- color,
161
- symbol,
162
- font,
163
- fontWeight,
164
- );
165
- const buf = await sharp(Buffer.from(svgForSize))
166
- .png()
167
- .toBuffer();
168
-
169
- const pngName = `${filenameBase}-${size}.png`;
170
- const pngSource = new RawSource(buf);
171
- compilation.emitAsset(pngName, pngSource);
172
- this._cachedAssets.set(pngName, pngSource);
173
- return { size, buf };
174
- }),
175
- );
176
-
177
- const icoBuffer = await pngToIco(
178
- pngBuffers
179
- .filter(b => b.size <= 256)
180
- .sort((a, b) => a.size - b.size)
181
- .map(x => x.buf),
182
- );
183
- const icoSource = new RawSource(icoBuffer);
184
- compilation.emitAsset(`${filenameBase}.ico`, icoSource);
185
- this._cachedAssets.set(`${filenameBase}.ico`, icoSource);
186
- } catch (err) {
187
- this._cachedAssets = null;
188
- compilation.errors.push(
189
- new Error(`Error: ${err && err.message ? err.message : err}`),
190
- );
191
- }
192
- },
193
- );
194
- });
195
- }
196
- }
197
-
198
- module.exports = GenerateFaviconPlugin;
@@ -1,69 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const wawoff2 = require('wawoff2');
4
- const { getPackageDir } = require('./utils/paths');
5
- const { makeLogger } = require('./log');
6
-
7
- const log = makeLogger(__filename);
8
- const FONTS_DIR = path.join(getPackageDir(), 'fonts');
9
-
10
- class GenerateFontsPlugin {
11
- _cachedAssets = null;
12
-
13
- apply(compiler) {
14
- compiler.hooks.thisCompilation.tap('GenerateFontsPlugin', compilation => {
15
- const { RawSource } =
16
- compilation.compiler.webpack?.sources || require('webpack-sources');
17
-
18
- compilation.hooks.processAssets.tapPromise(
19
- {
20
- name: 'GenerateFontsPlugin',
21
- stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
22
- },
23
- async () => {
24
- if (this._cachedAssets) {
25
- for (const [name, source] of this._cachedAssets) {
26
- compilation.emitAsset(name, source);
27
- }
28
- return;
29
- }
30
-
31
- this._cachedAssets = new Map();
32
-
33
- log.info`Bundling fonts from ${FONTS_DIR}`;
34
-
35
- for (const family of fs.readdirSync(FONTS_DIR)) {
36
- const familyDir = path.join(FONTS_DIR, family);
37
- if (!fs.statSync(familyDir).isDirectory()) {
38
- continue;
39
- }
40
-
41
- for (const file of fs.readdirSync(familyDir)) {
42
- const filePath = path.join(familyDir, file);
43
- let source;
44
-
45
- if (file.endsWith('.ttf')) {
46
- const ttfBuf = fs.readFileSync(filePath);
47
- const woff2Buf = Buffer.from(await wawoff2.compress(ttfBuf));
48
- const outName = file.replace(/\.ttf$/, '.woff2');
49
- const assetName = `${family}/${outName}`;
50
-
51
- source = new RawSource(woff2Buf);
52
- compilation.emitAsset(assetName, source);
53
- this._cachedAssets.set(assetName, source);
54
- log.debug`Converted ${family}/${file} to ${outName}`;
55
- } else {
56
- const assetName = `${family}/${file}`;
57
- source = new RawSource(fs.readFileSync(filePath));
58
- compilation.emitAsset(assetName, source);
59
- this._cachedAssets.set(assetName, source);
60
- }
61
- }
62
- }
63
- },
64
- );
65
- });
66
- }
67
- }
68
-
69
- module.exports = GenerateFontsPlugin;
@@ -1,19 +0,0 @@
1
- const JsonSchemaCompiler = require('ajv');
2
-
3
- const compiler = new JsonSchemaCompiler();
4
-
5
- function compile(schema) {
6
- return compiler.compile(schema);
7
- }
8
-
9
- function doValidation(validator, input, fileName) {
10
- const valid = validator(input);
11
- if (!valid) {
12
- validator.errors.forEach(error => {
13
- console.error(error);
14
- });
15
- throw new Error(`JSON file failed validation: ${fileName}`);
16
- }
17
- }
18
-
19
- module.exports = { compile, doValidation };
package/webpack/log.js DELETED
@@ -1,143 +0,0 @@
1
- const { inspect } = require('node:util');
2
- const path = require('path');
3
- const { G, R, P, Y, L } = require('./colors');
4
- const FLAIR_STRINGS = require('./flair.json');
5
-
6
- const LEVELS = ['debug', 'info', 'warn', 'error'];
7
-
8
- const ENV_LOG_LEVEL = process.env.TADA_LOG_LEVEL;
9
-
10
- if (ENV_LOG_LEVEL && !LEVELS.includes(ENV_LOG_LEVEL)) {
11
- throw new Error(
12
- `Invalid TADA_LOG_LEVEL "${ENV_LOG_LEVEL}", must be one of: ${LEVELS.join(', ')}`,
13
- );
14
- }
15
-
16
- function shouldLog(loggerLevel, level) {
17
- return LEVELS.indexOf(level) >= LEVELS.indexOf(loggerLevel);
18
- }
19
-
20
- function validateLevel(level) {
21
- if (!LEVELS.includes(level)) {
22
- throw new Error(
23
- `Invalid log level "${level}", must be one of: ${LEVELS.join(', ')}`,
24
- );
25
- }
26
- }
27
-
28
- function print(strings, stream = 'stdout', end = '\n') {
29
- for (const s of strings) {
30
- process[stream].write(s);
31
- }
32
- process[stream].write(end);
33
- }
34
-
35
- function makeLogger(name, logLevel = 'info') {
36
- validateLevel(logLevel);
37
-
38
- if (ENV_LOG_LEVEL) {
39
- logLevel = ENV_LOG_LEVEL;
40
- }
41
-
42
- if (!name) {
43
- name = '';
44
- } else {
45
- // Allow for passing __filename
46
- name = path.basename(name);
47
- }
48
-
49
- const logger = {
50
- /** Don't log if the level is < minLogLevel */
51
- setMinLogLevel(minLogLevel) {
52
- this.minLogLevel = minLogLevel;
53
- },
54
- getArgs(level, strings, args, colorFn) {
55
- const params = [];
56
- params.push(colorFn`${level}` + '\t');
57
- params.push(format(strings, ...args));
58
- return params;
59
- },
60
- debug(strings, ...args) {
61
- if (shouldLog(this.minLogLevel, 'debug')) {
62
- print(this.getArgs('debug', strings, args, L), 'stderr');
63
- }
64
- },
65
- info(strings, ...args) {
66
- if (shouldLog(this.minLogLevel, 'info')) {
67
- print(this.getArgs('info', strings, args, L));
68
- }
69
- },
70
- warn(strings, ...args) {
71
- if (shouldLog(this.minLogLevel, 'warn')) {
72
- print(this.getArgs('warn', strings, args, Y));
73
- }
74
- },
75
- error(strings, ...args) {
76
- if (shouldLog(this.minLogLevel, 'error')) {
77
- print(this.getArgs('error', strings, args, R));
78
- }
79
- },
80
- event(strings, ...args) {
81
- print(this.getArgs('event', strings, args, G));
82
- },
83
- followup(strings) {
84
- print(strings);
85
- },
86
- };
87
-
88
- logger.setMinLogLevel(logLevel);
89
- return logger;
90
- }
91
-
92
- function format(strings, ...args) {
93
- // Called as template tag: first arg is an array-like with .raw
94
- if (strings && typeof strings === 'object' && 'raw' in strings) {
95
- try {
96
- return String.raw(strings, ...args.map(toString));
97
- } catch (e) {
98
- // fallback to safe join
99
- }
100
- } else {
101
- if (Array.isArray(strings)) {
102
- args.unshift(...strings);
103
- } else {
104
- args.unshift(strings);
105
- }
106
-
107
- return args.map(toString).join(' ');
108
- }
109
- }
110
-
111
- function toString(item) {
112
- if (item === undefined) {
113
- return 'undefined';
114
- }
115
- if (item === null) {
116
- return 'null';
117
- }
118
- if (typeof item === 'string') {
119
- return item;
120
- }
121
-
122
- try {
123
- if (typeof item === 'object') {
124
- return inspect(item, {
125
- compact: true,
126
- depth: 2,
127
- breakLength: 80,
128
- maxStringLength: 250,
129
- colors: true,
130
- });
131
- }
132
- throw new Error('not an object');
133
- } catch (e) {
134
- return String(item);
135
- }
136
- }
137
-
138
- function getFlair() {
139
- const i = Math.floor(Math.random() * FLAIR_STRINGS.length);
140
- return P`${FLAIR_STRINGS[i]}!` + ' 🎉';
141
- }
142
-
143
- module.exports = { makeLogger, getFlair };