@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,105 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { generateTocHtml, generateCodeTocHtml } from './toc-plugin.js';
3
+ import type { JavaTocEntry } from './types.js';
4
+
5
+ describe('generateTocHtml', () => {
6
+ test('returns empty string for empty array', () => {
7
+ expect(generateTocHtml([])).toBe('');
8
+ });
9
+
10
+ test('returns empty string for null/undefined', () => {
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ expect(generateTocHtml(null as any)).toBe('');
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
+ expect(generateTocHtml(undefined as any)).toBe('');
15
+ });
16
+
17
+ test('generates heading items with correct level and link', () => {
18
+ const html = generateTocHtml([
19
+ { kind: 'heading', level: '2', id: 'intro', innerHtml: 'Introduction' },
20
+ ]);
21
+ expect(html).toContain('<ol>');
22
+ expect(html).toContain('class="heading-item level2"');
23
+ expect(html).toContain('href="#intro"');
24
+ expect(html).toContain('Introduction');
25
+ expect(html).toContain('</ol>');
26
+ });
27
+
28
+ test('generates dinkus items', () => {
29
+ const html = generateTocHtml([{ kind: 'dinkus' }]);
30
+ expect(html).toContain('class="dinkus-item"');
31
+ });
32
+
33
+ test('generates alert items at one level deeper than last heading', () => {
34
+ const html = generateTocHtml([
35
+ { kind: 'heading', level: '2', id: 'sec', innerHtml: 'Section' },
36
+ { kind: 'alert', type: 'warning', title: 'Caution' },
37
+ ]);
38
+ expect(html).toContain('class="alert-item level3 warning"');
39
+ expect(html).toContain('Caution');
40
+ });
41
+
42
+ test('alert before any heading uses level 2 (lastHeadingLevel defaults to 1)', () => {
43
+ const html = generateTocHtml([
44
+ { kind: 'alert', type: 'note', title: 'Note' },
45
+ ]);
46
+ expect(html).toContain('class="alert-item level2 note"');
47
+ });
48
+
49
+ test('handles mixed items in order', () => {
50
+ const html = generateTocHtml([
51
+ { kind: 'heading', level: '2', id: 'a', innerHtml: 'A' },
52
+ { kind: 'dinkus' },
53
+ { kind: 'heading', level: '3', id: 'b', innerHtml: 'B' },
54
+ { kind: 'alert', type: 'note', title: 'FYI' },
55
+ ]);
56
+ expect(html).toContain('level2');
57
+ expect(html).toContain('dinkus-item');
58
+ expect(html).toContain('level3');
59
+ expect(html).toContain('level4');
60
+ });
61
+ });
62
+
63
+ describe('generateCodeTocHtml', () => {
64
+ test('returns empty string for empty array', () => {
65
+ expect(generateCodeTocHtml([])).toBe('');
66
+ });
67
+
68
+ test('returns empty string for null/undefined', () => {
69
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
+ expect(generateCodeTocHtml(null as any)).toBe('');
71
+ });
72
+
73
+ test('generates grouped entries with labels', () => {
74
+ const items: JavaTocEntry[] = [
75
+ { kind: 'field', label: 'x', name: 'x', line: 5 },
76
+ { kind: 'method', label: 'getX()', name: 'getX()', line: 10 },
77
+ ];
78
+ const html = generateCodeTocHtml(items);
79
+ expect(html).toContain('Fields');
80
+ expect(html).toContain('Methods');
81
+ expect(html).toContain('href="#L5"');
82
+ expect(html).toContain('href="#L10"');
83
+ });
84
+
85
+ test('groups items by kind in order of appearance', () => {
86
+ const items: JavaTocEntry[] = [
87
+ { kind: 'method', label: 'foo()', name: 'foo()', line: 1 },
88
+ { kind: 'constructor', label: 'Bar()', name: 'Bar()', line: 5 },
89
+ { kind: 'method', label: 'baz()', name: 'baz()', line: 10 },
90
+ ];
91
+ const html = generateCodeTocHtml(items);
92
+ const methodsPos = html.indexOf('Methods');
93
+ const constructorsPos = html.indexOf('Constructors');
94
+ expect(methodsPos).toBeLessThan(constructorsPos);
95
+ });
96
+
97
+ test('escapes HTML in names', () => {
98
+ const items: JavaTocEntry[] = [
99
+ { kind: 'method', label: 'compare<T>()', name: 'compare<T>()', line: 1 },
100
+ ];
101
+ const html = generateCodeTocHtml(items);
102
+ expect(html).toContain('compare&lt;T&gt;()');
103
+ expect(html).not.toContain('compare<T>()');
104
+ });
105
+ });
@@ -1,14 +1,35 @@
1
- const { convertMarkdown: curlyQuote } = require('quote-quote');
1
+ import type MarkdownIt from 'markdown-it';
2
+ import { convertMarkdown as curlyQuote } from 'quote-quote';
3
+ import type { JavaTocEntry } from './types.js';
4
+
5
+ interface HeadingItem {
6
+ kind: 'heading';
7
+ level: string;
8
+ id: string;
9
+ innerHtml: string;
10
+ }
11
+
12
+ interface DinkusItem {
13
+ kind: 'dinkus';
14
+ }
2
15
 
3
- function tocPlugin(md) {
16
+ interface AlertItem {
17
+ kind: 'alert';
18
+ type: string;
19
+ title: string;
20
+ }
21
+
22
+ type TocItem = HeadingItem | DinkusItem | AlertItem;
23
+
24
+ export function tocPlugin(md: MarkdownIt): void {
4
25
  md.core.ruler.push('toc_collector', state => {
5
26
  if (!state.env) {
6
27
  return;
7
28
  }
8
29
 
9
30
  const tokens = state.tokens;
10
- const items = [];
11
- const containerStack = [];
31
+ const items: TocItem[] = [];
32
+ const containerStack: string[] = [];
12
33
 
13
34
  for (let i = 0; i < tokens.length; i++) {
14
35
  const token = tokens[i];
@@ -23,7 +44,7 @@ function tocPlugin(md) {
23
44
  const level = token.tag[1]; // 'h2' -> '2'
24
45
  const id = token.attrGet('id') || '';
25
46
  const innerHtml = md.renderer.renderInline(
26
- inline.children,
47
+ inline.children ?? [],
27
48
  md.options,
28
49
  state.env,
29
50
  );
@@ -82,7 +103,7 @@ function tocPlugin(md) {
82
103
  });
83
104
  }
84
105
 
85
- function generateTocHtml(tocItems) {
106
+ export function generateTocHtml(tocItems: TocItem[]): string {
86
107
  if (!tocItems || tocItems.length === 0) {
87
108
  return '';
88
109
  }
@@ -118,7 +139,7 @@ function generateTocHtml(tocItems) {
118
139
  return parts.join('');
119
140
  }
120
141
 
121
- function escapeHtml(str) {
142
+ function escapeHtml(str: string): string {
122
143
  return str
123
144
  .replace(/&/g, '&amp;')
124
145
  .replace(/</g, '&lt;')
@@ -126,19 +147,19 @@ function escapeHtml(str) {
126
147
  .replace(/"/g, '&quot;');
127
148
  }
128
149
 
129
- const GROUP_LABELS = {
150
+ const GROUP_LABELS: Record<string, string> = {
130
151
  field: 'Fields',
131
152
  constructor: 'Constructors',
132
153
  method: 'Methods',
133
154
  };
134
155
 
135
- function generateCodeTocHtml(codeTocItems) {
156
+ export function generateCodeTocHtml(codeTocItems: JavaTocEntry[]): string {
136
157
  if (!codeTocItems || codeTocItems.length === 0) {
137
158
  return '';
138
159
  }
139
160
 
140
- const groups = Object.create(null);
141
- const kindOrder = [];
161
+ const groups: Record<string, JavaTocEntry[]> = Object.create(null);
162
+ const kindOrder: string[] = [];
142
163
  for (const item of codeTocItems) {
143
164
  if (!groups[item.kind]) {
144
165
  groups[item.kind] = [];
@@ -163,5 +184,3 @@ function generateCodeTocHtml(codeTocItems) {
163
184
  parts.push('</ol>');
164
185
  return parts.join('');
165
186
  }
166
-
167
- module.exports = { tocPlugin, generateTocHtml, generateCodeTocHtml };
package/build/types.ts ADDED
@@ -0,0 +1,172 @@
1
+ /** Site configuration loaded from site.dev.json or site.prod.json */
2
+ export interface SiteVariables {
3
+ base: string;
4
+ basePath: string;
5
+ title?: string;
6
+ titlePostfix?: string;
7
+ symbol?: string;
8
+ faviconSymbol?: string;
9
+ courseId?: string;
10
+ themeColor?: string;
11
+ faviconColor?: string;
12
+ faviconFontWeight?: number;
13
+ internalDomains?: string[];
14
+ defaultTimeZone?: string;
15
+ features?: { search?: boolean; code?: boolean; favicon?: boolean };
16
+ codeLanguages?: Record<string, string>;
17
+ tintHue?: number;
18
+ tintAmount?: number;
19
+ vars?: Record<string, unknown>;
20
+ }
21
+
22
+ /** A rendered content asset ready to write to dist/ */
23
+ export interface Asset {
24
+ assetPath: string;
25
+ content: string | Buffer;
26
+ }
27
+
28
+ /** Options for the content rendering pipeline */
29
+ export interface ContentRenderOptions {
30
+ distDir: string;
31
+ assetFiles: string[];
32
+ watchState?: WatchState;
33
+ }
34
+
35
+ /** State passed from watch mode to control incremental rebuilds */
36
+ export interface WatchState {
37
+ changedContentFiles?: Set<string>;
38
+ templatesChanged?: boolean;
39
+ }
40
+
41
+ /** Result from ContentRenderer.processContent() */
42
+ export interface ContentRenderResult {
43
+ errors: Error[];
44
+ changedHtmlAssetPaths: Set<string>;
45
+ removedHtmlAssetPaths: Set<string>;
46
+ htmlAssetsByPath: Map<string, string>;
47
+ buildContentFiles: string[];
48
+ }
49
+
50
+ /** Logger returned by makeLogger() */
51
+ export interface Logger {
52
+ minLogLevel: string;
53
+ setMinLogLevel(level: string): void;
54
+ getArgs(
55
+ level: string,
56
+ strings: TemplateStringsArray | string | string[],
57
+ args: unknown[],
58
+ colorFn: (strings: TemplateStringsArray, ...args: unknown[]) => string,
59
+ ): string[];
60
+ debug(strings: TemplateStringsArray, ...args: unknown[]): void;
61
+ info(strings: TemplateStringsArray, ...args: unknown[]): void;
62
+ warn(strings: TemplateStringsArray, ...args: unknown[]): void;
63
+ error(strings: TemplateStringsArray, ...args: unknown[]): void;
64
+ event(strings: TemplateStringsArray, ...args: unknown[]): void;
65
+ followup(strings: string[]): void;
66
+ }
67
+
68
+ /** Options for plain text asset rendering */
69
+ export interface RenderPlainTextOptions {
70
+ filePath: string;
71
+ contentDir: string;
72
+ siteVariables: SiteVariables;
73
+ validInternalTargets: Set<string>;
74
+ assetFiles: string[];
75
+ }
76
+
77
+ /** Options for code page asset rendering */
78
+ export interface RenderCodePageOptions {
79
+ filePath: string;
80
+ contentDir: string;
81
+ siteVariables: SiteVariables;
82
+ assetFiles: string[];
83
+ }
84
+
85
+ /** Options for literate Java asset rendering */
86
+ export interface RenderLiterateJavaOptions {
87
+ filePath: string;
88
+ contentDir: string;
89
+ siteVariables: SiteVariables;
90
+ assetFiles: string[];
91
+ skipExecution?: boolean;
92
+ }
93
+
94
+ /** Options for copied content asset rendering */
95
+ export interface RenderCopiedContentOptions {
96
+ filePath: string;
97
+ contentDir: string;
98
+ }
99
+
100
+ /** Front matter parse result (from parseFrontMatterAndContent) */
101
+ export interface ParsedContent {
102
+ pageVariables: Record<string, unknown>;
103
+ content: string;
104
+ }
105
+
106
+ /** Front matter parse result (from parseFrontMatter) */
107
+ export interface ParsedFrontMatter {
108
+ frontMatter: Record<string, unknown>;
109
+ content: string;
110
+ }
111
+
112
+ /** Theme derivation result */
113
+ export interface DerivedTheme {
114
+ themeColorLight: string;
115
+ themeColorDark: string;
116
+ themeColorTextLight: string;
117
+ themeColorTextDark: string;
118
+ textOnThemeLight: string;
119
+ textOnThemeDark: string;
120
+ }
121
+
122
+ /** Java code TOC entry from extractJavaMethodToc */
123
+ export interface JavaTocEntry {
124
+ kind: 'method' | 'constructor' | 'field';
125
+ label: string;
126
+ name: string;
127
+ line: number;
128
+ }
129
+
130
+ /** Heading TOC item collected by toc-plugin */
131
+ export interface HeadingTocItem {
132
+ id: string;
133
+ text: string;
134
+ level: number;
135
+ subtitle?: string;
136
+ }
137
+
138
+ /** Literate Java code block */
139
+ export interface LiterateCodeBlock {
140
+ javaStartLine: number;
141
+ javaEndLine: number;
142
+ content: string;
143
+ hidden: boolean;
144
+ }
145
+
146
+ /** Result from parseLiterateJava */
147
+ export interface LiterateJavaParseResult {
148
+ pageVariables: Record<string, unknown>;
149
+ content: string;
150
+ javaSource: string;
151
+ codeBlocks: LiterateCodeBlock[];
152
+ visibleBlockIndices: number[];
153
+ }
154
+
155
+ /** Execution output entry from LiterateRunner */
156
+ export interface LiterateRunnerEntry {
157
+ blockIndex: number;
158
+ output: string;
159
+ }
160
+
161
+ /** Site config creation input (used by bin/validators) */
162
+ export interface SiteConfigInput {
163
+ title: string;
164
+ symbol: string;
165
+ themeColor: string;
166
+ tintHue: string;
167
+ tintAmount: string;
168
+ defaultTimeZone: string;
169
+ base: string;
170
+ basePath: string;
171
+ internalDomains: string[];
172
+ }
package/build/util.ts ADDED
@@ -0,0 +1,26 @@
1
+ export {
2
+ getBuildContentFiles,
3
+ getContentFiles,
4
+ getFilesByExtensions,
5
+ getValidInternalTargets,
6
+ shouldSkipContentFile,
7
+ } from './utils/content-files.js';
8
+ export { extensionIsMarkdown } from './utils/file-types.js';
9
+ export { createMarkdown } from './utils/markdown.js';
10
+ export {
11
+ createApplyBasePath,
12
+ getContentDir,
13
+ getDistDir,
14
+ getPackageDir,
15
+ getProjectDir,
16
+ getPublicDir,
17
+ normalizeOutputPath,
18
+ } from './utils/paths.js';
19
+ export {
20
+ injectAssetTags,
21
+ renderCodePageAsset,
22
+ renderCopiedContentAsset,
23
+ renderLiterateJavaPageAsset,
24
+ renderPlainTextPageAsset,
25
+ } from './utils/render.js';
26
+ export { parseFrontMatter } from './utils/front-matter.js';