@abreen/tada 1.15.8 → 1.15.10
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/build/bundle.ts +55 -26
- package/build/colors.ts +2 -2
- package/build/config-files.ts +11 -21
- package/build/config-loader.ts +1 -1
- package/build/external-links-plugin.ts +1 -0
- package/build/generate-favicon.ts +0 -2
- package/build/generate-web-app-manifest.ts +0 -2
- package/build/pdf-text.ts +2 -2
- package/build/reachability.ts +1 -1
- package/build/site-variables.ts +1 -3
- package/build/template-globals.ts +7 -0
- package/build/templates.ts +1 -1
- package/build/util.ts +1 -22
- package/build/utils/literate-java.ts +1 -1
- package/build/utils/markdown-partials.ts +1 -1
- package/build/utils/shiki-highlighter.ts +1 -4
- package/build/utils/trace-layout.ts +2 -2
- package/build/validate-config-links.ts +51 -1
- package/build/watch/config-paths.ts +1 -1
- package/build/watch/planner.ts +1 -1
- package/build/watch/snapshot.ts +4 -16
- package/package.json +5 -5
- package/schema/authors.schema.json +1 -1
- package/src/navigate/eligible.ts +8 -1
- package/src/navigate/runtime.ts +57 -5
- package/src/page-update/index.ts +6 -2
- package/src/search/index.ts +105 -71
- package/src/search/pdf-utils.ts +1 -1
- package/src/slides/index.ts +1 -1
- package/src/timezone/index.ts +8 -44
- package/src/timezone/time-format.ts +43 -0
- package/src/toc/index.ts +7 -79
- package/src/toc/model.ts +78 -0
- package/src/top/index.ts +3 -2
- package/templates/_author.html +2 -2
- package/templates/_footer.html +1 -1
- package/templates/_heading.html +1 -1
- package/templates/_nav.html +2 -2
package/build/bundle.ts
CHANGED
|
@@ -19,6 +19,19 @@ import type { SiteVariables } from './types';
|
|
|
19
19
|
import timezones from '../src/timezone/timezones.json' with { type: 'json' };
|
|
20
20
|
import pkg from '../package.json' with { type: 'json' };
|
|
21
21
|
|
|
22
|
+
interface BunBuildPlugin {
|
|
23
|
+
name: string;
|
|
24
|
+
setup(build: PluginBuilder): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface CoverageHooks {
|
|
28
|
+
createBundlePlugin(): BunBuildPlugin;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface CoverageGlobal {
|
|
32
|
+
__tadaCoverage?: CoverageHooks;
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
export function getBundleNaming(): string {
|
|
23
36
|
const version = pkg.version.replace(/[^a-zA-Z0-9.-]/g, '-');
|
|
24
37
|
return `[name].bundle.tada-${version}.[ext]`;
|
|
@@ -92,9 +105,7 @@ function createDefine(
|
|
|
92
105
|
};
|
|
93
106
|
}
|
|
94
107
|
|
|
95
|
-
function createScssPlugin(
|
|
96
|
-
const themeDir = renderThemeScss(siteVariables);
|
|
97
|
-
|
|
108
|
+
function createScssPlugin(themeDir: string) {
|
|
98
109
|
return {
|
|
99
110
|
name: 'scss',
|
|
100
111
|
setup(build: PluginBuilder) {
|
|
@@ -108,6 +119,12 @@ function createScssPlugin(siteVariables: SiteVariables) {
|
|
|
108
119
|
};
|
|
109
120
|
}
|
|
110
121
|
|
|
122
|
+
function getCoverageBundlePlugin(): BunBuildPlugin | null {
|
|
123
|
+
return (
|
|
124
|
+
(globalThis as CoverageGlobal).__tadaCoverage?.createBundlePlugin() ?? null
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
111
128
|
export async function bundle(
|
|
112
129
|
siteVariables: SiteVariables,
|
|
113
130
|
{ mode = 'development', distDir }: { mode?: string; distDir?: string } = {},
|
|
@@ -121,30 +138,42 @@ export async function bundle(
|
|
|
121
138
|
path.resolve(packageDir, 'src/critical.scss'),
|
|
122
139
|
];
|
|
123
140
|
|
|
124
|
-
const
|
|
125
|
-
entrypoints,
|
|
126
|
-
outdir: resolvedDistDir,
|
|
127
|
-
naming: getBundleNaming(),
|
|
128
|
-
minify: mode === 'production',
|
|
129
|
-
sourcemap: isDev ? 'inline' : 'none',
|
|
130
|
-
define: createDefine(siteVariables, isDev),
|
|
131
|
-
external: ['*.woff2'],
|
|
132
|
-
plugins: [createScssPlugin(siteVariables)],
|
|
133
|
-
});
|
|
141
|
+
const themeDir = renderThemeScss(siteVariables);
|
|
134
142
|
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
143
|
+
try {
|
|
144
|
+
const plugins = [createScssPlugin(themeDir)];
|
|
145
|
+
const coverageBundlePlugin = getCoverageBundlePlugin();
|
|
146
|
+
if (coverageBundlePlugin) {
|
|
147
|
+
plugins.push(coverageBundlePlugin);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const result = await Bun.build({
|
|
151
|
+
entrypoints,
|
|
152
|
+
outdir: resolvedDistDir,
|
|
153
|
+
naming: getBundleNaming(),
|
|
154
|
+
minify: mode === 'production',
|
|
155
|
+
sourcemap: isDev ? 'inline' : 'none',
|
|
156
|
+
define: createDefine(siteVariables, isDev),
|
|
157
|
+
external: ['*.woff2'],
|
|
158
|
+
plugins,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
if (!result.success) {
|
|
162
|
+
const messages = result.logs
|
|
163
|
+
.filter(log => log.level === 'error')
|
|
164
|
+
.map(log => log.message || String(log));
|
|
165
|
+
throw new Error(`Bundle failed:\n${messages.join('\n')}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Return the output filenames for asset tag injection
|
|
169
|
+
const assetFiles = result.outputs.map(output =>
|
|
170
|
+
toPosix(path.relative(resolvedDistDir, output.path)),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
return assetFiles;
|
|
174
|
+
} finally {
|
|
175
|
+
fs.rmSync(themeDir, { recursive: true, force: true });
|
|
140
176
|
}
|
|
141
|
-
|
|
142
|
-
// Return the output filenames for asset tag injection
|
|
143
|
-
const assetFiles = result.outputs.map(output =>
|
|
144
|
-
toPosix(path.relative(resolvedDistDir, output.path)),
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
return assetFiles;
|
|
148
177
|
}
|
|
149
178
|
|
|
150
|
-
export { renderThemeScss
|
|
179
|
+
export { renderThemeScss };
|
package/build/colors.ts
CHANGED
|
@@ -12,11 +12,11 @@ export const R = style(['red']);
|
|
|
12
12
|
export const G = style(['green']);
|
|
13
13
|
export const B = style(['blue']);
|
|
14
14
|
export const Y = style(['yellow']);
|
|
15
|
-
export const L = style(['
|
|
15
|
+
export const L = style(['gray']);
|
|
16
16
|
export const P = style(['magenta']);
|
|
17
17
|
export const I = style(['italic', 'bold']);
|
|
18
18
|
|
|
19
19
|
export const Ri = style(['inverse', 'red']);
|
|
20
20
|
export const Gi = style(['inverse', 'green']);
|
|
21
21
|
export const Yi = style(['inverse', 'yellow']);
|
|
22
|
-
export const Li = style(['inverse', '
|
|
22
|
+
export const Li = style(['inverse', 'gray']);
|
package/build/config-files.ts
CHANGED
|
@@ -5,7 +5,7 @@ export type SiteEnv = 'dev' | 'prod';
|
|
|
5
5
|
export type SiteConfigName = 'site.dev' | 'site.prod';
|
|
6
6
|
export type ProjectConfigName = 'nav' | 'authors';
|
|
7
7
|
export type ConfigName = SiteConfigName | ProjectConfigName;
|
|
8
|
-
|
|
8
|
+
type ProjectConfigExtension = '.yaml' | '.yml' | '.json';
|
|
9
9
|
|
|
10
10
|
export interface ResolvedProjectConfigFile<
|
|
11
11
|
Name extends ConfigName = ConfigName,
|
|
@@ -18,21 +18,21 @@ export interface ResolvedProjectConfigFile<
|
|
|
18
18
|
required: boolean;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const CONFIG_FILE_EXTENSIONS = ['.yaml', '.yml', '.json'] as const;
|
|
22
|
+
const DEFAULT_CONFIG_FILE_EXTENSION = '.yaml' as const;
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
const SITE_CONFIG_BASE_NAMES = {
|
|
25
25
|
dev: 'site.dev',
|
|
26
26
|
prod: 'site.prod',
|
|
27
27
|
} as const satisfies Record<SiteEnv, SiteConfigName>;
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
const PROJECT_CONFIG_BASE_NAMES = {
|
|
30
30
|
nav: 'nav',
|
|
31
31
|
authors: 'authors',
|
|
32
32
|
} as const satisfies Record<ProjectConfigName, ProjectConfigName>;
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
const REQUIRED_PROJECT_CONFIG_NAMES = ['nav'] as const;
|
|
35
|
+
const OPTIONAL_PROJECT_CONFIG_NAMES = ['authors'] as const;
|
|
36
36
|
export const PROJECT_CONFIG_NAMES = [
|
|
37
37
|
...REQUIRED_PROJECT_CONFIG_NAMES,
|
|
38
38
|
...OPTIONAL_PROJECT_CONFIG_NAMES,
|
|
@@ -66,7 +66,7 @@ export function getProjectConfigBaseName(
|
|
|
66
66
|
return PROJECT_CONFIG_BASE_NAMES[name];
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
function getSupportedConfigFileNames(baseName: string): string[] {
|
|
70
70
|
return CONFIG_FILE_EXTENSIONS.map(ext => `${baseName}${ext}`);
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -83,21 +83,17 @@ export function getSupportedConfigFileNamesText(baseName: string): string {
|
|
|
83
83
|
return joinWithOr(getSupportedConfigFileNames(baseName));
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
function getDefaultConfigFileName(baseName: string): string {
|
|
87
87
|
return `${baseName}${DEFAULT_CONFIG_FILE_EXTENSION}`;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
function getDefaultConfigFilePath(
|
|
91
91
|
projectDir: string,
|
|
92
92
|
baseName: string,
|
|
93
93
|
): string {
|
|
94
94
|
return path.join(projectDir, getDefaultConfigFileName(baseName));
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
export function getDefaultSiteConfigFileName(env: SiteEnv): string {
|
|
98
|
-
return getDefaultConfigFileName(getSiteConfigBaseName(env));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
97
|
export function getDefaultSiteConfigPath(
|
|
102
98
|
projectDir: string,
|
|
103
99
|
env: SiteEnv,
|
|
@@ -105,12 +101,6 @@ export function getDefaultSiteConfigPath(
|
|
|
105
101
|
return getDefaultConfigFilePath(projectDir, getSiteConfigBaseName(env));
|
|
106
102
|
}
|
|
107
103
|
|
|
108
|
-
export function getDefaultProjectConfigFileName(
|
|
109
|
-
name: ProjectConfigName,
|
|
110
|
-
): string {
|
|
111
|
-
return getDefaultConfigFileName(getProjectConfigBaseName(name));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
104
|
export function getDefaultProjectConfigFilePath(
|
|
115
105
|
projectDir: string,
|
|
116
106
|
name: ProjectConfigName,
|
|
@@ -118,7 +108,7 @@ export function getDefaultProjectConfigFilePath(
|
|
|
118
108
|
return getDefaultConfigFilePath(projectDir, getProjectConfigBaseName(name));
|
|
119
109
|
}
|
|
120
110
|
|
|
121
|
-
|
|
111
|
+
function resolveConfigFile<Name extends ConfigName = ConfigName>(
|
|
122
112
|
projectDir: string,
|
|
123
113
|
{
|
|
124
114
|
name,
|
package/build/config-loader.ts
CHANGED
|
@@ -175,6 +175,7 @@ export default function externalLinks(
|
|
|
175
175
|
const newClassAttr = classAttr ? `${classAttr} external` : 'external';
|
|
176
176
|
token.attrSet('class', newClassAttr);
|
|
177
177
|
token.attrSet('target', '_blank');
|
|
178
|
+
token.attrSet('rel', 'noopener noreferrer');
|
|
178
179
|
log.debug`${href} -> "${newClassAttr}"`;
|
|
179
180
|
|
|
180
181
|
const closeIdx = findMatchingClose(children, i);
|
package/build/pdf-text.ts
CHANGED
|
@@ -8,12 +8,12 @@ interface MutoolOutput {
|
|
|
8
8
|
stderr: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
interface PdfPage {
|
|
12
12
|
pageNumber: number;
|
|
13
13
|
content: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
interface PdfExtractResult {
|
|
17
17
|
pages: PdfPage[];
|
|
18
18
|
hasExtractedText: boolean;
|
|
19
19
|
}
|
package/build/reachability.ts
CHANGED
|
@@ -37,7 +37,7 @@ function resolveTargetToAssetTarget(target: string): string | null {
|
|
|
37
37
|
return path.posix.extname(normalizedTarget) ? normalizedTarget : null;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
function collectOutgoingHtmlAssetPaths(
|
|
41
41
|
analysis: HtmlOutputAnalysis,
|
|
42
42
|
knownHtmlAssetPaths: Set<string>,
|
|
43
43
|
): string[] {
|
package/build/site-variables.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { bundledLanguages } from 'shiki';
|
|
2
2
|
import type { BundledLanguage } from 'shiki';
|
|
3
|
-
import {
|
|
3
|
+
import type { SiteEnv } from './config-files';
|
|
4
4
|
import { loadSiteConfig } from './config-loader';
|
|
5
5
|
import { compile as compileJsonSchema, doValidation } from './json-schema';
|
|
6
6
|
import { getProjectDir } from './utils/paths';
|
|
@@ -163,8 +163,6 @@ export function getProdSiteVariables(): SiteVariables {
|
|
|
163
163
|
return getSiteVariables('prod');
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
export { getSiteConfigBaseName };
|
|
167
|
-
|
|
168
166
|
function getValidatedSiteConfigRoot(
|
|
169
167
|
value: unknown,
|
|
170
168
|
fileName: string,
|
|
@@ -23,6 +23,7 @@ interface TemplateGlobals {
|
|
|
23
23
|
readableDate: (date: string | Date | null | undefined) => string;
|
|
24
24
|
classNames: (obj: Record<string, unknown>) => string;
|
|
25
25
|
cx: (obj: Record<string, unknown>) => string;
|
|
26
|
+
encodeAuthoredUrl: (value: string) => string;
|
|
26
27
|
renderTimeZoneChooser: () => string;
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -51,10 +52,16 @@ export default function createTemplateGlobals(
|
|
|
51
52
|
readableDate,
|
|
52
53
|
classNames,
|
|
53
54
|
cx: classNames,
|
|
55
|
+
encodeAuthoredUrl,
|
|
54
56
|
renderTimeZoneChooser,
|
|
55
57
|
};
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
// Encode raw authored URL text, but preserve percent escapes authors already wrote.
|
|
61
|
+
export function encodeAuthoredUrl(value: string): string {
|
|
62
|
+
return encodeURI(value).replace(/%25([0-9a-fA-F]{2})/g, '%$1');
|
|
63
|
+
}
|
|
64
|
+
|
|
58
65
|
function isoDate(str: string | null | undefined): string | null {
|
|
59
66
|
if (str == null || str == '') {
|
|
60
67
|
return null;
|
package/build/templates.ts
CHANGED
package/build/util.ts
CHANGED
|
@@ -1,37 +1,16 @@
|
|
|
1
|
-
export {
|
|
2
|
-
addGeneratedRouteAliases,
|
|
3
|
-
getProcessedExts,
|
|
4
|
-
getSourceOutputPaths,
|
|
5
|
-
getSourceTargetPaths,
|
|
6
|
-
isBuildContentSource,
|
|
7
|
-
} from './source-model';
|
|
8
|
-
export {
|
|
9
|
-
getBuildContentFiles,
|
|
10
|
-
getContentFiles,
|
|
11
|
-
getContentOutputRelPaths,
|
|
12
|
-
getContentSourceOutputRelPaths,
|
|
13
|
-
getFilesByExtensions,
|
|
14
|
-
getValidInternalTargets,
|
|
15
|
-
shouldSkipContentFile,
|
|
16
|
-
} from './utils/content-files';
|
|
17
|
-
export { extensionIsMarkdown, isPartial } from './utils/file-types';
|
|
18
|
-
export { createMarkdown } from './utils/markdown';
|
|
1
|
+
export { getFilesByExtensions } from './utils/content-files';
|
|
19
2
|
export {
|
|
20
3
|
createApplyBasePath,
|
|
21
4
|
getContentDir,
|
|
22
5
|
getDistDir,
|
|
23
6
|
getPackageDir,
|
|
24
|
-
getProjectDir,
|
|
25
7
|
getPublicDir,
|
|
26
8
|
normalizeOutputPath,
|
|
27
9
|
toPosix,
|
|
28
10
|
} from './utils/paths';
|
|
29
11
|
export {
|
|
30
|
-
injectAssetTags,
|
|
31
|
-
preparePageTemplateHtml,
|
|
32
12
|
renderCodePageAsset,
|
|
33
13
|
renderCopiedContentAsset,
|
|
34
14
|
renderLiterateJavaPageAsset,
|
|
35
15
|
renderPlainTextPageAsset,
|
|
36
16
|
} from './utils/render';
|
|
37
|
-
export { parseFrontMatter } from './utils/front-matter';
|
|
@@ -32,7 +32,7 @@ export function runJavac(
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
function getExecError(err: unknown): string {
|
|
36
36
|
const execErr = err as { stderr?: Buffer; message: string };
|
|
37
37
|
return execErr.stderr ? execErr.stderr.toString() : execErr.message;
|
|
38
38
|
}
|
|
@@ -23,7 +23,7 @@ interface MarkdownPartialsEnv {
|
|
|
23
23
|
[PARTIAL_CONTEXT_STACK_KEY]?: MarkdownPartialContext[];
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
interface MarkdownPartialsOptions {
|
|
27
27
|
filePath: string;
|
|
28
28
|
templateParams: Record<string, unknown>;
|
|
29
29
|
dependencyCollector?: RenderDependencyCollector;
|
|
@@ -64,10 +64,7 @@ export async function initHighlighter(
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
BundledLanguage,
|
|
69
|
-
BundledTheme
|
|
70
|
-
> {
|
|
67
|
+
function getHighlighter(): HighlighterGeneric<BundledLanguage, BundledTheme> {
|
|
71
68
|
if (!highlighter) {
|
|
72
69
|
throw new Error('Shiki highlighter not initialized');
|
|
73
70
|
}
|
|
@@ -33,7 +33,7 @@ const MIN_VALUE_WIDTH = 40;
|
|
|
33
33
|
const MIN_ARRAY_CELL_WIDTH = 48;
|
|
34
34
|
|
|
35
35
|
/** Information collected about a heap object across all trace steps. */
|
|
36
|
-
|
|
36
|
+
interface ObjectInfo {
|
|
37
37
|
id: string;
|
|
38
38
|
type: string;
|
|
39
39
|
firstStep: number;
|
|
@@ -61,7 +61,7 @@ export interface ObjectInfo {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/** A tree node used to build the hierarchy for d3-flextree. */
|
|
64
|
-
|
|
64
|
+
interface TreeNode {
|
|
65
65
|
id: string;
|
|
66
66
|
width: number;
|
|
67
67
|
height: number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
+
import { encodeAuthoredUrl } from './template-globals';
|
|
2
3
|
import { normalizeOutputPath } from './utils/paths';
|
|
3
4
|
|
|
4
5
|
interface NavLink {
|
|
@@ -24,6 +25,51 @@ function requireRootRelativePath(
|
|
|
24
25
|
return `${errorPrefix} must start with "/": "${value}"`;
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
const SPECIAL_URL_SCHEMES = new Set([
|
|
29
|
+
'file',
|
|
30
|
+
'ftp',
|
|
31
|
+
'http',
|
|
32
|
+
'https',
|
|
33
|
+
'ws',
|
|
34
|
+
'wss',
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
function isValidAbsoluteAuthorUrl(value: string): boolean {
|
|
38
|
+
if (value.trim() !== value) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const schemeMatch = value.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/);
|
|
43
|
+
if (!schemeMatch) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const scheme = schemeMatch[1].toLowerCase();
|
|
48
|
+
if (
|
|
49
|
+
SPECIAL_URL_SCHEMES.has(scheme) &&
|
|
50
|
+
!value.startsWith(`${schemeMatch[1]}://`)
|
|
51
|
+
) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
new URL(encodeAuthoredUrl(value));
|
|
57
|
+
return true;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getInternalHrefTarget(href: string): string {
|
|
64
|
+
const { pathname } = splitHref(href);
|
|
65
|
+
const normalized = normalizeOutputPath(pathname);
|
|
66
|
+
try {
|
|
67
|
+
return normalizeOutputPath(decodeURIComponent(normalized));
|
|
68
|
+
} catch {
|
|
69
|
+
return normalized;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
27
73
|
export function validateNavLinks(
|
|
28
74
|
navData: unknown,
|
|
29
75
|
validTargets: Set<string>,
|
|
@@ -97,6 +143,10 @@ export function validateAuthorLinks(
|
|
|
97
143
|
}
|
|
98
144
|
|
|
99
145
|
if (author.url) {
|
|
146
|
+
if (isValidAbsoluteAuthorUrl(author.url)) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
100
150
|
const urlRootRelativeError = requireRootRelativePath(
|
|
101
151
|
author.url,
|
|
102
152
|
`${fileName}: url for "${key}"`,
|
|
@@ -104,7 +154,7 @@ export function validateAuthorLinks(
|
|
|
104
154
|
if (urlRootRelativeError) {
|
|
105
155
|
errors.push(urlRootRelativeError);
|
|
106
156
|
} else {
|
|
107
|
-
const urlPath =
|
|
157
|
+
const urlPath = getInternalHrefTarget(author.url);
|
|
108
158
|
if (!validTargets.has(urlPath)) {
|
|
109
159
|
errors.push(`${fileName}: broken url for "${key}": "${author.url}"`);
|
|
110
160
|
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from '../config-files';
|
|
7
7
|
import { getProjectConfigDir } from '../templates';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
type WatchConfigPathKind = 'site' | 'nav' | 'authors';
|
|
10
10
|
|
|
11
11
|
function getConfigPathEntries(): Array<{
|
|
12
12
|
kind: WatchConfigPathKind;
|
package/build/watch/planner.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface TadaIncrementalWatchPlan {
|
|
|
14
14
|
publicToRemove: Set<string>;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
type TadaWatchPlan = { kind: 'full' } | TadaIncrementalWatchPlan;
|
|
18
18
|
|
|
19
19
|
export function diffAuthorKeys(previous: unknown, next: unknown): Set<string> {
|
|
20
20
|
const previousMap =
|
package/build/watch/snapshot.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { normalizeOutputPath } from '../util';
|
|
4
3
|
import {
|
|
5
4
|
type TadaProjectScan,
|
|
6
5
|
assertNoOutputPathConflicts,
|
|
7
|
-
scanProject,
|
|
8
|
-
updateProjectScan,
|
|
9
6
|
} from '../source-model';
|
|
10
7
|
import type { Asset, HtmlOutputAnalysis, SiteVariables } from '../types';
|
|
11
8
|
import type { TadaBuildMeta } from './compiler-types';
|
|
@@ -60,7 +57,7 @@ function buildReverseMap(
|
|
|
60
57
|
return reverse;
|
|
61
58
|
}
|
|
62
59
|
|
|
63
|
-
|
|
60
|
+
function collectOutputOwners(snapshot: {
|
|
64
61
|
contentRecords: Map<string, TadaSourceRecord>;
|
|
65
62
|
publicRecords: Map<string, TadaSourceRecord>;
|
|
66
63
|
}): Map<string, TadaOutputOwner> {
|
|
@@ -164,7 +161,7 @@ export function collectSourceHtmlAnalysis(
|
|
|
164
161
|
return htmlAnalysisByOutputPath;
|
|
165
162
|
}
|
|
166
163
|
|
|
167
|
-
|
|
164
|
+
function collectHtmlAssetsByPath(
|
|
168
165
|
contentRecords: Map<string, TadaSourceRecord>,
|
|
169
166
|
): Map<string, string> {
|
|
170
167
|
const htmlAssetsByPath = new Map<string, string>();
|
|
@@ -178,7 +175,7 @@ export function collectHtmlAssetsByPath(
|
|
|
178
175
|
return htmlAssetsByPath;
|
|
179
176
|
}
|
|
180
177
|
|
|
181
|
-
|
|
178
|
+
function collectHtmlAnalysisByPath(
|
|
182
179
|
contentRecords: Map<string, TadaSourceRecord>,
|
|
183
180
|
): Map<string, HtmlOutputAnalysis> {
|
|
184
181
|
const htmlAnalysisByPath = new Map<string, HtmlOutputAnalysis>();
|
|
@@ -201,13 +198,4 @@ export function createBuildMeta(snapshot: TadaSnapshot): TadaBuildMeta {
|
|
|
201
198
|
};
|
|
202
199
|
}
|
|
203
200
|
|
|
204
|
-
export
|
|
205
|
-
return normalizeOutputPath(target);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export {
|
|
209
|
-
assertNoOutputPathConflicts,
|
|
210
|
-
scanProject,
|
|
211
|
-
updateProjectScan,
|
|
212
|
-
type TadaProjectScan,
|
|
213
|
-
};
|
|
201
|
+
export { assertNoOutputPathConflicts, type TadaProjectScan };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abreen/tada",
|
|
3
|
-
"version": "1.15.
|
|
3
|
+
"version": "1.15.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A static site generator",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"check-deps": "bun run scripts/check-deps.ts",
|
|
34
34
|
"init-example": "bun run bin/tada.ts init example --no-interactive",
|
|
35
35
|
"iew": "bun run scripts/init-example-watch.ts",
|
|
36
|
-
"lint": "eslint src/ build/ bin/",
|
|
36
|
+
"lint": "eslint src/ build/ bin/ templates/*.test.ts",
|
|
37
37
|
"lint:sass": "bun run scripts/lint-sass.ts",
|
|
38
38
|
"typecheck": "tsc --noEmit",
|
|
39
39
|
"format": "prettier --write \"**/*.{ts,js,scss,json}\"",
|
|
@@ -61,7 +61,6 @@
|
|
|
61
61
|
"chokidar": "^4.0.3",
|
|
62
62
|
"culori": "^4.0.2",
|
|
63
63
|
"d3-flextree": "^2.1.2",
|
|
64
|
-
"d3-hierarchy": "^3.1.2",
|
|
65
64
|
"fontkit": "^2.0.4",
|
|
66
65
|
"front-matter": "^4.0.2",
|
|
67
66
|
"java-parser": "^3.0.1",
|
|
@@ -101,7 +100,6 @@
|
|
|
101
100
|
"@types/bun": "^1.3.12",
|
|
102
101
|
"@types/culori": "^4.0.1",
|
|
103
102
|
"@types/d3-flextree": "^2.1.4",
|
|
104
|
-
"@types/d3-hierarchy": "^3.1.7",
|
|
105
103
|
"@types/fontkit": "^2.0.9",
|
|
106
104
|
"@types/istanbul-lib-coverage": "^2.0.6",
|
|
107
105
|
"@types/istanbul-lib-instrument": "^1.7.8",
|
|
@@ -109,12 +107,14 @@
|
|
|
109
107
|
"@types/istanbul-reports": "^3.0.4",
|
|
110
108
|
"@types/lodash": "^4.17.24",
|
|
111
109
|
"@types/markdown-it-container": "^4.0.0",
|
|
110
|
+
"@types/node": "^25.6.0",
|
|
112
111
|
"eslint": "^10.2.1",
|
|
113
112
|
"husky": "^9.1.7",
|
|
114
113
|
"istanbul-lib-coverage": "^3.2.2",
|
|
115
114
|
"istanbul-lib-instrument": "^6.0.3",
|
|
116
115
|
"istanbul-lib-report": "^3.0.1",
|
|
117
116
|
"istanbul-reports": "^3.2.0",
|
|
117
|
+
"knip": "^6.11.0",
|
|
118
118
|
"lint-staged": "^16.4.0",
|
|
119
119
|
"postcss-scss": "^4.0.9",
|
|
120
120
|
"prettier": "^3.8.3",
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
"stylelint": "^17.8.0",
|
|
123
123
|
"stylelint-config-recess-order": "^7.7.0",
|
|
124
124
|
"stylelint-config-standard-scss": "^17.0.0",
|
|
125
|
-
"typescript": "^
|
|
125
|
+
"typescript": "^6.0.3",
|
|
126
126
|
"typescript-eslint": "^8.58.2"
|
|
127
127
|
}
|
|
128
128
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"properties": {
|
|
7
7
|
"name": { "type": "string" },
|
|
8
8
|
"avatar": { "type": "string", "pattern": "^\\/[-a-zA-Z0-9_./#]*$" },
|
|
9
|
-
"url": { "type": "string", "
|
|
9
|
+
"url": { "type": "string", "minLength": 1 }
|
|
10
10
|
},
|
|
11
11
|
"additionalProperties": false
|
|
12
12
|
}
|
package/src/navigate/eligible.ts
CHANGED
|
@@ -45,7 +45,14 @@ export function isEligibleLink(
|
|
|
45
45
|
return false;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
const normalizedBasePath = basePath.endsWith('/')
|
|
49
|
+
? basePath.slice(0, -1) || '/'
|
|
50
|
+
: basePath;
|
|
51
|
+
if (
|
|
52
|
+
normalizedBasePath !== '/' &&
|
|
53
|
+
url.pathname !== normalizedBasePath &&
|
|
54
|
+
!url.pathname.startsWith(`${normalizedBasePath}/`)
|
|
55
|
+
) {
|
|
49
56
|
return false;
|
|
50
57
|
}
|
|
51
58
|
|