@apleasantview/eleventy-plugin-baseline 0.1.0-next.40 → 0.1.0-next.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -32
- package/core/back-compat/options.js +69 -0
- package/core/content-graph/backlinks.js +65 -0
- package/core/content-graph/extractors.js +185 -0
- package/core/content-graph/graph.js +121 -0
- package/core/content-graph/index.js +2 -0
- package/core/content-graph/prepass.js +121 -0
- package/core/dates/git-date.js +71 -0
- package/core/dates/index.js +55 -0
- package/core/locale/derive-lang.js +19 -0
- package/core/locale/index.js +6 -0
- package/core/locale/normalize-lang.js +13 -0
- package/core/locale/normalize-locale.js +20 -0
- package/core/locale/open-graph-locale.js +14 -0
- package/core/locale/resolve-default.js +27 -0
- package/core/locale/resolve-locale.js +16 -0
- package/core/logging/banner.js +49 -0
- package/core/{logging.js → logging/index.js} +19 -2
- package/core/logging/quips.js +30 -0
- package/core/markdown/auto-heading-ids.js +86 -0
- package/core/markdown/index.js +5 -0
- package/core/markdown/safe-use.js +42 -0
- package/core/{wikilinks.js → markdown/wikilinks.js} +4 -4
- package/core/page-context/build.js +336 -0
- package/core/page-context/index.js +1 -0
- package/core/page-context/register.js +73 -0
- package/core/page-context/seo-helpers.js +56 -0
- package/core/schema.js +22 -2
- package/core/seo-graph/adapter.js +246 -0
- package/core/seo-graph/build.js +87 -0
- package/core/seo-graph/index.js +1 -0
- package/core/seo-graph/open-graph.js +130 -0
- package/core/seo-graph/register.js +42 -0
- package/core/seo-graph/schema.js +18 -0
- package/core/slug-index.js +2 -2
- package/core/state.js +75 -0
- package/core/{shortcodes/image.js → surface/image-shortcode.js} +4 -4
- package/core/surface/index.js +22 -0
- package/core/types.js +3 -0
- package/core/utils/add-trailing-slash.js +11 -0
- package/core/utils/ensure-dot-slash-dir.js +13 -0
- package/core/utils/normalize-language-map.js +37 -0
- package/core/utils/resolve-field.js +9 -0
- package/core/utils/resolve-subdir.js +20 -0
- package/core/utils/slugify.js +15 -0
- package/core/utils/title-case-slug.js +15 -0
- package/core/utils/unique-by.js +25 -0
- package/core/virtual-dir.js +11 -10
- package/index.js +161 -118
- package/modules/assets/index.js +4 -2
- package/modules/assets/processors/esbuild-process.js +2 -2
- package/modules/assets/processors/postcss-process.js +2 -2
- package/modules/head/drivers/posthtml-head-elements.js +92 -12
- package/modules/head/index.js +23 -19
- package/modules/head/schema.js +7 -3
- package/modules/multilang/filters/i18n-default-translation.js +2 -4
- package/modules/multilang/filters/i18n-translation-in.js +2 -2
- package/modules/multilang/filters/i18n-translations-for.js +2 -2
- package/modules/multilang/index.js +80 -39
- package/modules/navigator/index.js +39 -25
- package/modules/navigator/templates/navigator-core.html +1 -1
- package/modules/sitemap/index.js +8 -4
- package/modules/sitemap/templates/sitemap-core.html +1 -1
- package/package.json +5 -2
- package/core/filters/index.js +0 -4
- package/core/global-functions/index.js +0 -6
- package/core/page-context.js +0 -310
- package/core/shortcodes/index.js +0 -2
- package/core/utils/helpers.js +0 -75
- /package/core/{global-functions/date.js → dates/date-global.js} +0 -0
- /package/core/{filters/markdown.js → markdown/markdownify.js} +0 -0
- /package/core/{filters → surface/filters}/isString.js +0 -0
- /package/core/{filters → surface/filters}/related-posts.js +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { TemplatePath } from '@11ty/eleventy-utils';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalise a directory path to a `./`-prefixed form, defaulting empty/missing
|
|
5
|
+
* input to the current directory. Thin wrapper over
|
|
6
|
+
* `TemplatePath.addLeadingDotSlash` that bakes in the empty-string fallback.
|
|
7
|
+
*
|
|
8
|
+
* @param {string | undefined} dir
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
11
|
+
export function ensureDotSlashDir(dir) {
|
|
12
|
+
return TemplatePath.addLeadingDotSlash(dir || './');
|
|
13
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize the `settings.languages` config into an object map with
|
|
3
|
+
* lowercased, trimmed keys.
|
|
4
|
+
*
|
|
5
|
+
* Accepts an array of language codes or an object keyed by language code;
|
|
6
|
+
* either form ends up with `[normalizedKey]: entry`. Returns undefined if
|
|
7
|
+
* the input is invalid or empty.
|
|
8
|
+
*
|
|
9
|
+
* Lives in `core/utils/` (not `core/locale/`) because the array-vs-object
|
|
10
|
+
* shape coercion is config-shape adapting, not locale handling. The
|
|
11
|
+
* lowercasing of keys is the only locale-shaped part.
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} settings - Options object containing languages.
|
|
14
|
+
* @param {import('../logging/index.js').BaselineLogger} [logger] - Logger for dropped-entry notice.
|
|
15
|
+
* @returns {Record<string, Object>|undefined} Normalized language map, or undefined.
|
|
16
|
+
*/
|
|
17
|
+
export function normalizeLanguageMap(settings, logger) {
|
|
18
|
+
const normalizedLanguages = Array.isArray(settings.languages)
|
|
19
|
+
? Object.fromEntries(
|
|
20
|
+
settings.languages
|
|
21
|
+
.filter((lang) => typeof lang === 'string' && lang.trim())
|
|
22
|
+
.map((lang) => [lang.toLowerCase().trim(), {}])
|
|
23
|
+
)
|
|
24
|
+
: settings.languages && typeof settings.languages === 'object'
|
|
25
|
+
? Object.fromEntries(
|
|
26
|
+
Object.entries(settings.languages).map(([k, v]) => [k.toLowerCase().trim(), v])
|
|
27
|
+
)
|
|
28
|
+
: undefined;
|
|
29
|
+
|
|
30
|
+
if (logger && Array.isArray(settings.languages)) {
|
|
31
|
+
const normalizedCount = normalizedLanguages ? Object.keys(normalizedLanguages).length : 0;
|
|
32
|
+
if (normalizedCount !== settings.languages.length) {
|
|
33
|
+
logger.info('Some languages entries were invalid and were dropped.');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return normalizedLanguages;
|
|
37
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a field with a page → site → fallback precedence chain.
|
|
3
|
+
*
|
|
4
|
+
* @param {{ pageValue?: any, siteValue?: any, fallbackValue?: any, isHome?: boolean }} args
|
|
5
|
+
* @returns {any}
|
|
6
|
+
*/
|
|
7
|
+
export function resolveField({ pageValue, siteValue, fallbackValue }) {
|
|
8
|
+
return pageValue ?? siteValue ?? fallbackValue;
|
|
9
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { TemplatePath } from '@11ty/eleventy-utils';
|
|
2
|
+
import { addTrailingSlash } from './add-trailing-slash.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve a subdirectory under input and output.
|
|
6
|
+
* Joins inputDir/outputDir with rawDir, normalises, and adds trailing slashes.
|
|
7
|
+
* @param {string} inputDir - The input directory (e.g., "./src/").
|
|
8
|
+
* @param {string} outputDir - The output directory (e.g., "./dist/").
|
|
9
|
+
* @param {string} rawDir - Raw subdirectory value (e.g., "assets", "static").
|
|
10
|
+
* @returns {{input: string, output: string}}
|
|
11
|
+
*/
|
|
12
|
+
export function resolveSubdir(inputDir, outputDir, rawDir) {
|
|
13
|
+
const joinedInput = TemplatePath.join(inputDir, rawDir || '');
|
|
14
|
+
const joinedOutput = TemplatePath.join(outputDir, rawDir || '');
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
input: addTrailingSlash(TemplatePath.standardizeFilePath(joinedInput)),
|
|
18
|
+
output: addTrailingSlash(TemplatePath.standardizeFilePath(joinedOutput))
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import slugifyLib from 'slugify';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Slugify a string into a wikilink-friendly key.
|
|
5
|
+
* Lowercases, strips diacritics, replaces non-alphanumerics with hyphens,
|
|
6
|
+
* trims leading/trailing hyphens. Returns undefined for empty input.
|
|
7
|
+
*
|
|
8
|
+
* @param {string|null|undefined} input
|
|
9
|
+
* @returns {string|undefined}
|
|
10
|
+
*/
|
|
11
|
+
export function slugify(input) {
|
|
12
|
+
if (input == null) return;
|
|
13
|
+
const slug = slugifyLib(String(input), { lower: true, strict: true, trim: true });
|
|
14
|
+
return slug || undefined;
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Title-case a URL slug for display: "core-reference" → "Core Reference".
|
|
3
|
+
* The rough inverse of {@link slugify}: splits on hyphens/underscores and
|
|
4
|
+
* capitalises each word.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} slug
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
export function titleCaseSlug(slug) {
|
|
10
|
+
return String(slug)
|
|
11
|
+
.split(/[-_]/)
|
|
12
|
+
.filter(Boolean)
|
|
13
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
14
|
+
.join(' ');
|
|
15
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deduplicate an array by a key (string property name or selector function).
|
|
3
|
+
* Items without a derivable key are kept via their JSON-stringified shape.
|
|
4
|
+
*
|
|
5
|
+
* @template T
|
|
6
|
+
* @param {T[]} arr
|
|
7
|
+
* @param {string | ((item: T) => string | undefined)} keyFn
|
|
8
|
+
* @returns {T[]}
|
|
9
|
+
*/
|
|
10
|
+
export const uniqueBy = (arr, keyFn) =>
|
|
11
|
+
Object.values(
|
|
12
|
+
(arr ?? []).reduce((acc, item) => {
|
|
13
|
+
if (!item) return acc;
|
|
14
|
+
|
|
15
|
+
const id = typeof keyFn === 'function' ? keyFn(item) : item?.[keyFn];
|
|
16
|
+
|
|
17
|
+
if (!id) {
|
|
18
|
+
acc[JSON.stringify(item)] = item;
|
|
19
|
+
return acc;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
acc[id] = item;
|
|
23
|
+
return acc;
|
|
24
|
+
}, {})
|
|
25
|
+
);
|
package/core/virtual-dir.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { resolveSubdir } from './utils/
|
|
3
|
-
import { createLogger } from './logging.js';
|
|
1
|
+
import { ensureDotSlashDir } from './utils/ensure-dot-slash-dir.js';
|
|
2
|
+
import { resolveSubdir } from './utils/resolve-subdir.js';
|
|
3
|
+
import { createLogger } from './logging/index.js';
|
|
4
4
|
import { getScope, addScopeListener, setEntry } from './registry.js';
|
|
5
5
|
|
|
6
|
+
const SCOPE_NAME = 'core:virtual-dir';
|
|
7
|
+
const LOG_NAME = 'virtual-dir';
|
|
8
|
+
|
|
6
9
|
/**
|
|
7
10
|
* Virtual directories (runtime substrate)
|
|
8
11
|
*
|
|
@@ -40,8 +43,6 @@ import { getScope, addScopeListener, setEntry } from './registry.js';
|
|
|
40
43
|
* live { input, output } cache → consumers
|
|
41
44
|
*/
|
|
42
45
|
|
|
43
|
-
const SCOPE_NAME = 'core:virtual-dir';
|
|
44
|
-
|
|
45
46
|
/**
|
|
46
47
|
* Register a virtual directory on eleventyConfig.directories.
|
|
47
48
|
*
|
|
@@ -56,10 +57,10 @@ const SCOPE_NAME = 'core:virtual-dir';
|
|
|
56
57
|
*/
|
|
57
58
|
export function registerVirtualDir(eleventyConfig, { key, outputDir } = {}) {
|
|
58
59
|
if (!key) {
|
|
59
|
-
throw new Error('
|
|
60
|
+
throw new Error('[baseline/virtual-dir] `name` is required');
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
const log = createLogger(
|
|
63
|
+
const log = createLogger(LOG_NAME);
|
|
63
64
|
const scope = getScope(eleventyConfig, SCOPE_NAME);
|
|
64
65
|
const rawDir = eleventyConfig.dir?.[key] || key;
|
|
65
66
|
const rawOutputDir = outputDir ?? rawDir;
|
|
@@ -75,7 +76,7 @@ export function registerVirtualDir(eleventyConfig, { key, outputDir } = {}) {
|
|
|
75
76
|
// shared listener below refreshes when Eleventy emits its final directories.
|
|
76
77
|
const existing = Object.getOwnPropertyDescriptor(eleventyConfig.directories, key);
|
|
77
78
|
if (existing && existing.configurable === false) {
|
|
78
|
-
log.info(`directories
|
|
79
|
+
log.info(`directories.${key} already defined, skipping`);
|
|
79
80
|
} else {
|
|
80
81
|
Object.defineProperty(eleventyConfig.directories, key, {
|
|
81
82
|
get() {
|
|
@@ -101,8 +102,8 @@ export function registerVirtualDir(eleventyConfig, { key, outputDir } = {}) {
|
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
function syncCache(cache, dirs, rawDir, rawOutputDir) {
|
|
104
|
-
const inputDir =
|
|
105
|
-
const outputDir =
|
|
105
|
+
const inputDir = ensureDotSlashDir(dirs.input);
|
|
106
|
+
const outputDir = ensureDotSlashDir(dirs.output);
|
|
106
107
|
|
|
107
108
|
// resolveSubdir symmetrically resolves against input and output; call twice
|
|
108
109
|
// so input and output subdirs can differ (e.g. `public` copies to root).
|
package/index.js
CHANGED
|
@@ -3,36 +3,41 @@ import { createRequire } from 'node:module';
|
|
|
3
3
|
|
|
4
4
|
import { HtmlBasePlugin } from '@11ty/eleventy';
|
|
5
5
|
import { eleventyImageOnRequestDuringServePlugin } from '@11ty/eleventy-img';
|
|
6
|
+
import markdownItAttrs from 'markdown-it-attrs';
|
|
6
7
|
|
|
7
|
-
import { createLogger } from './core/logging.js';
|
|
8
|
+
import { createLogger, printBannerOnce } from './core/logging/index.js';
|
|
9
|
+
import { isLegacyShape, normalizeLegacyShape } from './core/back-compat/options.js';
|
|
10
|
+
import { settingsSchema } from './core/schema.js';
|
|
11
|
+
import { deriveBaselineState } from './core/state.js';
|
|
12
|
+
import { runPrepass, PREPASS_SENTINEL } from './core/content-graph/index.js';
|
|
13
|
+
import { registerVirtualDir } from './core/virtual-dir.js';
|
|
8
14
|
import { createContentMapStore } from './core/content-map-store.js';
|
|
9
15
|
import { createTranslationMapStore } from './core/translation-map-store.js';
|
|
10
16
|
import { createSlugIndex } from './core/slug-index.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { wikilinks } from './core/
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
import { registerGlobals } from './core/global-functions/index.js';
|
|
17
|
-
import { markdownFilter, relatedPostsFilter, isStringFilter } from './core/filters/index.js';
|
|
18
|
-
import { imageShortcode } from './core/shortcodes/index.js';
|
|
17
|
+
import { registerPageContext } from './core/page-context/index.js';
|
|
18
|
+
import { registerSeoGraph } from './core/seo-graph/index.js';
|
|
19
|
+
import { autoHeadingIds, safeUse, wikilinks } from './core/markdown/index.js';
|
|
20
|
+
import { slugify } from './core/utils/slugify.js';
|
|
19
21
|
import { assetsCore, headCore, multilangCore, navigatorCore, sitemapCore } from './modules.js';
|
|
22
|
+
import {
|
|
23
|
+
registerGlobals,
|
|
24
|
+
markdownFilter,
|
|
25
|
+
relatedPostsFilter,
|
|
26
|
+
isStringFilter,
|
|
27
|
+
imageShortcode
|
|
28
|
+
} from './core/surface/index.js';
|
|
20
29
|
|
|
21
30
|
const __require = createRequire(import.meta.url);
|
|
22
31
|
const { name, version } = __require('./package.json');
|
|
32
|
+
const eleventyVersion = process.env.ELEVENTY_VERSION;
|
|
33
|
+
// const absoluteRoot = process.env.ELEVENTY_ROOT; -> Safekeeping.
|
|
23
34
|
|
|
24
35
|
const mode = process.env.ELEVENTY_ENV;
|
|
36
|
+
// eslint-disable-next-line no-unused-vars
|
|
25
37
|
const isDev = mode === 'development';
|
|
38
|
+
// eslint-disable-next-line no-unused-vars
|
|
26
39
|
const isProd = mode === 'production';
|
|
27
40
|
|
|
28
|
-
const LEGACY_OPTION_KEYS = [
|
|
29
|
-
'verbose',
|
|
30
|
-
'enableNavigatorTemplate',
|
|
31
|
-
'enableSitemapTemplate',
|
|
32
|
-
'assetsESBuild',
|
|
33
|
-
'multilingual'
|
|
34
|
-
];
|
|
35
|
-
|
|
36
41
|
// Whitelist of reserved global data keys used internally across the plugin.
|
|
37
42
|
// Positive side effect is they all get listed in order and merge data to the same key.
|
|
38
43
|
// Also prevents name collision with filters.
|
|
@@ -44,37 +49,20 @@ const INTERNAL_KEYS = [
|
|
|
44
49
|
'_navigator',
|
|
45
50
|
'_sitemap',
|
|
46
51
|
'_snapshot',
|
|
47
|
-
'_pageContext'
|
|
52
|
+
'eleventyComputed._pageContext',
|
|
53
|
+
'eleventyComputed._node',
|
|
54
|
+
'eleventyComputed._seoGraph',
|
|
55
|
+
'eleventyComputed._backlinks',
|
|
56
|
+
'eleventyComputed._outgoing',
|
|
57
|
+
'eleventyComputed._edges'
|
|
48
58
|
];
|
|
49
59
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
*
|
|
53
|
-
* The original plugin API accepted a single merged configuration object.
|
|
54
|
-
* This helper detects that shape and enables safe normalization into
|
|
55
|
-
* the current (settings, options) contract.
|
|
56
|
-
*
|
|
57
|
-
* NOTE: arguments.length is required because default parameters mask arity.
|
|
58
|
-
*/
|
|
59
|
-
function looksLikeLegacyOptions(firstArg, argsLength) {
|
|
60
|
-
if (argsLength >= 2) return false;
|
|
61
|
-
if (!firstArg || typeof firstArg !== 'object') return false;
|
|
62
|
-
return LEGACY_OPTION_KEYS.some((key) => key in firstArg);
|
|
63
|
-
}
|
|
60
|
+
// Base logger outputs regardless of options.
|
|
61
|
+
const baseLog = createLogger(null, { verbose: true });
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
* - settings → site identity (content + SEO concerns)
|
|
69
|
-
* - options → runtime behavior flags
|
|
70
|
-
*/
|
|
71
|
-
function splitLegacyOptions(legacy) {
|
|
72
|
-
const { defaultLanguage, languages, ...rest } = legacy;
|
|
73
|
-
return {
|
|
74
|
-
settings: { defaultLanguage, languages },
|
|
75
|
-
options: rest
|
|
76
|
-
};
|
|
77
|
-
}
|
|
63
|
+
printBannerOnce(baseLog, { version, eleventyVersion });
|
|
64
|
+
|
|
65
|
+
let contentGraph = null;
|
|
78
66
|
|
|
79
67
|
/**
|
|
80
68
|
* Baseline (composition root)
|
|
@@ -118,36 +106,29 @@ function splitLegacyOptions(legacy) {
|
|
|
118
106
|
*/
|
|
119
107
|
export default function baseline(settings = {}, options = {}) {
|
|
120
108
|
// --- Legacy compatibility layer ---
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
settings = split.settings;
|
|
127
|
-
options = split.options;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Base logger outputs regardless of options.
|
|
131
|
-
const baseLog = createLogger(null, { verbose: true });
|
|
132
|
-
|
|
133
|
-
// Scoped logging.
|
|
134
|
-
function scopedLog(name) {
|
|
135
|
-
return createLogger(name, { verbose: options.verbose });
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (wasLegacy) {
|
|
139
|
-
baseLog.info('DEPRECATED: single-object plugin arg. Use baseline(settings, options) instead.');
|
|
109
|
+
if (isLegacyShape(settings, arguments.length)) {
|
|
110
|
+
const normalized = normalizeLegacyShape(settings);
|
|
111
|
+
settings = normalized.settings;
|
|
112
|
+
options = normalized.options;
|
|
113
|
+
baseLog.info('Single-object plugin arg is deprecated. Use baseline(settings, options).');
|
|
140
114
|
}
|
|
141
115
|
|
|
142
116
|
// Validate configuration shape (non-fatal).
|
|
143
117
|
const parsed = settingsSchema.safeParse(settings);
|
|
144
118
|
if (!parsed.success) {
|
|
145
119
|
for (const issue of parsed.error.issues) {
|
|
146
|
-
baseLog.info('settings:', `${issue.path.join('.')}
|
|
120
|
+
baseLog.info('settings:', `${issue.path.join('.')}, ${issue.message}`);
|
|
147
121
|
}
|
|
148
122
|
}
|
|
149
123
|
|
|
150
|
-
|
|
124
|
+
// Resolve state once, above the closure. Pure; no eleventyConfig.
|
|
125
|
+
const state = deriveBaselineState(settings, options, { mode });
|
|
126
|
+
baseLog.info('Settings and options resolved, modules loaded');
|
|
127
|
+
|
|
128
|
+
// Scoped logging.
|
|
129
|
+
function scopedLog(name) {
|
|
130
|
+
return createLogger(name, { verbose: state.options.verbose });
|
|
131
|
+
}
|
|
151
132
|
|
|
152
133
|
/**
|
|
153
134
|
* Eleventy plugin initializer.
|
|
@@ -160,26 +141,70 @@ export default function baseline(settings = {}, options = {}) {
|
|
|
160
141
|
try {
|
|
161
142
|
eleventyConfig.versionCheck('>=3.0');
|
|
162
143
|
} catch (e) {
|
|
163
|
-
baseLog.error('Eleventy version mismatch
|
|
144
|
+
baseLog.error('Eleventy version mismatch.', e.message);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// --- Pre-pass wiring ---
|
|
148
|
+
// One mechanic: the pre-pass runs at the start of every Eleventy
|
|
149
|
+
// build cycle via `eleventy.before`. Initial build, watch rebuild,
|
|
150
|
+
// production build — all the same path. Templates always render
|
|
151
|
+
// against a graph rebuilt from current source. The sentinel keeps
|
|
152
|
+
// the inner Eleventy from re-attaching the hook on re-entry.
|
|
153
|
+
if (process.env[PREPASS_SENTINEL] !== '1') {
|
|
154
|
+
const prepassLog = scopedLog('pre-pass');
|
|
155
|
+
|
|
156
|
+
// Origins HtmlBasePlugin may have rewritten internal hrefs to.
|
|
157
|
+
// Stripped during link extraction so backlinks key on path-only.
|
|
158
|
+
const knownOrigins = new Set(['http://localhost:8080']);
|
|
159
|
+
for (const candidate of [settings.url, process.env.URL]) {
|
|
160
|
+
if (!candidate) continue;
|
|
161
|
+
try {
|
|
162
|
+
knownOrigins.add(new URL(candidate).origin);
|
|
163
|
+
} catch {
|
|
164
|
+
prepassLog.info('No known origins, using localhost only');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
eleventyConfig.on('eleventy.before', async () => {
|
|
169
|
+
contentGraph = await runPrepass(
|
|
170
|
+
eleventyConfig.directories?.input,
|
|
171
|
+
eleventyConfig.directories?.output,
|
|
172
|
+
scopedLog,
|
|
173
|
+
{ quietMode: true, knownOrigins }
|
|
174
|
+
);
|
|
175
|
+
});
|
|
164
176
|
}
|
|
165
177
|
|
|
166
178
|
INTERNAL_KEYS.forEach((key) => {
|
|
179
|
+
// We leave eleventyComputed callback keys alone, the rest are reserved-empty.
|
|
180
|
+
if (
|
|
181
|
+
key === 'eleventyComputed._pageContext' ||
|
|
182
|
+
key === 'eleventyComputed._node' ||
|
|
183
|
+
key === 'eleventyComputed._seoGraph' ||
|
|
184
|
+
key === 'eleventyComputed._backlinks' ||
|
|
185
|
+
key === 'eleventyComputed._outgoing' ||
|
|
186
|
+
key === 'eleventyComputed._edges'
|
|
187
|
+
)
|
|
188
|
+
return;
|
|
167
189
|
eleventyConfig.addGlobalData(key, {});
|
|
168
190
|
});
|
|
169
191
|
|
|
170
192
|
const env = {
|
|
171
|
-
name: 'Eleventy Baseline',
|
|
172
|
-
package: name,
|
|
173
193
|
version,
|
|
174
|
-
|
|
194
|
+
name: 'Eleventy Baseline',
|
|
195
|
+
env: {
|
|
196
|
+
mode,
|
|
197
|
+
package: name
|
|
198
|
+
}
|
|
175
199
|
};
|
|
176
200
|
|
|
177
201
|
eleventyConfig.addGlobalData('_baseline', {
|
|
178
|
-
env
|
|
202
|
+
...env,
|
|
203
|
+
options: state.options
|
|
179
204
|
});
|
|
180
205
|
|
|
181
206
|
if (!settings.url) {
|
|
182
|
-
baseLog.warn('settings.url missing
|
|
207
|
+
baseLog.warn('settings.url missing, canonical URLs will be relative');
|
|
183
208
|
}
|
|
184
209
|
|
|
185
210
|
registerGlobals(eleventyConfig);
|
|
@@ -188,38 +213,12 @@ export default function baseline(settings = {}, options = {}) {
|
|
|
188
213
|
baseHref: process.env.URL || eleventyConfig.pathPrefix
|
|
189
214
|
});
|
|
190
215
|
|
|
191
|
-
// ---
|
|
216
|
+
// --- Feature exposure to templates ---
|
|
192
217
|
const hasImageTransformPlugin = eleventyConfig.hasPlugin('eleventyImageTransformPlugin');
|
|
193
218
|
|
|
194
|
-
const state = {
|
|
195
|
-
settings: {
|
|
196
|
-
title: settings.title,
|
|
197
|
-
tagline: settings.tagline,
|
|
198
|
-
url: settings.url,
|
|
199
|
-
noindex: settings.noindex ?? false,
|
|
200
|
-
defaultLanguage: settings.defaultLanguage,
|
|
201
|
-
languages: settings.languages,
|
|
202
|
-
head: settings.head
|
|
203
|
-
},
|
|
204
|
-
|
|
205
|
-
options: {
|
|
206
|
-
verbose: options.verbose ?? false,
|
|
207
|
-
multilang: options.multilingual ?? false,
|
|
208
|
-
sitemap: options.sitemap ?? options.enableSitemapTemplate ?? true,
|
|
209
|
-
navigator: options.navigator ?? options.enableNavigatorTemplate ?? isDev,
|
|
210
|
-
head: {
|
|
211
|
-
titleSeparator: options.head?.titleSeparator,
|
|
212
|
-
showGenerator: options.head?.showGenerator
|
|
213
|
-
},
|
|
214
|
-
assets: {
|
|
215
|
-
esbuild: options.assets?.esbuild ?? options.assetsESBuild ?? {}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
|
|
220
219
|
eleventyConfig.addGlobalData('_baseline', {
|
|
221
220
|
features: {
|
|
222
|
-
...state.
|
|
221
|
+
...state.features,
|
|
223
222
|
hasImageTransformPlugin
|
|
224
223
|
}
|
|
225
224
|
});
|
|
@@ -234,6 +233,9 @@ export default function baseline(settings = {}, options = {}) {
|
|
|
234
233
|
outputDir: ''
|
|
235
234
|
});
|
|
236
235
|
|
|
236
|
+
const virtualDirLog = scopedLog('virtual-dir');
|
|
237
|
+
virtualDirLog.info('Virtual directories mounted');
|
|
238
|
+
|
|
237
239
|
const directories = {
|
|
238
240
|
input: eleventyConfig.directories?.input,
|
|
239
241
|
output: eleventyConfig.directories?.output,
|
|
@@ -277,6 +279,9 @@ export default function baseline(settings = {}, options = {}) {
|
|
|
277
279
|
get contentMap() {
|
|
278
280
|
return contentMapStore.get();
|
|
279
281
|
},
|
|
282
|
+
get contentGraph() {
|
|
283
|
+
return contentGraph;
|
|
284
|
+
},
|
|
280
285
|
translationMap: translationMapStore,
|
|
281
286
|
slugIndex
|
|
282
287
|
},
|
|
@@ -284,47 +289,85 @@ export default function baseline(settings = {}, options = {}) {
|
|
|
284
289
|
helpers
|
|
285
290
|
};
|
|
286
291
|
|
|
287
|
-
// Page context
|
|
292
|
+
// Page context and SEO graph registries
|
|
288
293
|
const pageContextRegistry = registerPageContext(eleventyConfig, coreContext);
|
|
294
|
+
const seoGraphRegistry = registerSeoGraph(eleventyConfig, coreContext);
|
|
295
|
+
|
|
296
|
+
// --- Content graph ---
|
|
297
|
+
// Cascade hookup for the content graph. Reads via the runtime getter so
|
|
298
|
+
// serve-mode rebuilds reassigning `contentGraph` are picked up.
|
|
299
|
+
function getNode(pageUrl) {
|
|
300
|
+
return coreContext.runtime.contentGraph?.nodes?.[pageUrl];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function getEdges() {
|
|
304
|
+
return coreContext.runtime.contentGraph?.edges ?? [];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
eleventyConfig.addGlobalData('eleventyComputed._node', () => (data) => {
|
|
308
|
+
const pageUrl = data.page?.url;
|
|
309
|
+
if (!pageUrl) return undefined;
|
|
310
|
+
|
|
311
|
+
return getNode(pageUrl);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
eleventyConfig.addGlobalData('eleventyComputed._backlinks', () => (data) => {
|
|
315
|
+
const edges = getEdges();
|
|
316
|
+
|
|
317
|
+
const pageUrl = data.page?.url;
|
|
318
|
+
if (!pageUrl) return [];
|
|
289
319
|
|
|
290
|
-
|
|
320
|
+
return edges.filter((edge) => edge.to === pageUrl);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
eleventyConfig.addGlobalData('eleventyComputed._outgoing', () => (data) => {
|
|
324
|
+
const edges = getEdges();
|
|
325
|
+
|
|
326
|
+
const pageUrl = data.page?.url;
|
|
327
|
+
if (!pageUrl) return [];
|
|
328
|
+
|
|
329
|
+
return edges.filter((edge) => edge.from === pageUrl);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// --- Markdown engine ---
|
|
333
|
+
// Order matters: attrs first so manual ids are visible to auto-heading-ids'
|
|
334
|
+
// seed pass; wikilinks last since it parses inline tokens independently.
|
|
335
|
+
const mdLog = scopedLog('markdown');
|
|
291
336
|
eleventyConfig.amendLibrary('md', (md) => {
|
|
292
|
-
md
|
|
337
|
+
safeUse(md, 'curly_attributes', markdownItAttrs, undefined, mdLog);
|
|
338
|
+
safeUse(md, 'baseline_auto_heading_ids', autoHeadingIds, { slugify }, mdLog);
|
|
339
|
+
safeUse(md, 'baseline_wikilinks', wikilinks, { slugIndex, pageContextRegistry, translationMapStore }, mdLog);
|
|
293
340
|
});
|
|
294
341
|
|
|
342
|
+
// --- Snapshots ---
|
|
295
343
|
coreContext.snapshots = {
|
|
296
344
|
contentMap: () => contentMapStore.snapshot(),
|
|
297
|
-
pageContext: () => pageContextRegistry.snapshot()
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
// --- Module activation ---
|
|
301
|
-
const features = {
|
|
302
|
-
multilang: Boolean(state.options.multilang),
|
|
303
|
-
sitemap: Boolean(state.options.sitemap),
|
|
304
|
-
navigator: Boolean(state.options.navigator),
|
|
305
|
-
head: true,
|
|
306
|
-
assets: true
|
|
345
|
+
pageContext: () => pageContextRegistry.snapshot(),
|
|
346
|
+
seoGraph: () => seoGraphRegistry.snapshot()
|
|
307
347
|
};
|
|
308
348
|
|
|
309
349
|
// --- Module registry ---
|
|
310
350
|
const moduleRegistry = [
|
|
311
|
-
{ when: features.multilang, name: 'multilang', plugin: multilangCore },
|
|
312
|
-
{ when: features.sitemap, name: 'sitemap', plugin: sitemapCore },
|
|
351
|
+
{ when: state.features.multilang, name: 'multilang', plugin: multilangCore },
|
|
352
|
+
{ when: state.features.sitemap, name: 'sitemap', plugin: sitemapCore },
|
|
313
353
|
{ name: 'navigator', plugin: navigatorCore },
|
|
314
|
-
{ when: features.head, name: 'head', plugin: headCore, consumes: { pageContext: true } },
|
|
315
|
-
{ when: features.assets, name: 'assets', plugin: assetsCore }
|
|
354
|
+
{ when: state.features.head, name: 'head', plugin: headCore, consumes: { pageContext: true, seoGraph: true } },
|
|
355
|
+
{ when: state.features.assets, name: 'assets', plugin: assetsCore }
|
|
316
356
|
];
|
|
317
357
|
|
|
358
|
+
const active = [];
|
|
318
359
|
for (const entry of moduleRegistry) {
|
|
319
360
|
const { when = true, name, plugin, consumes = {} } = entry;
|
|
320
361
|
if (!when) continue;
|
|
321
362
|
const moduleContext = {
|
|
322
363
|
...coreContext,
|
|
323
364
|
log: scopedLog(name),
|
|
324
|
-
resolvePageContext: consumes.pageContext ? pageContextRegistry : null
|
|
365
|
+
resolvePageContext: consumes.pageContext ? pageContextRegistry : null,
|
|
366
|
+
resolveSeoGraph: consumes.seoGraph ? seoGraphRegistry : null
|
|
325
367
|
};
|
|
326
368
|
|
|
327
369
|
eleventyConfig.addPlugin(plugin, moduleContext);
|
|
370
|
+
active.push(name);
|
|
328
371
|
}
|
|
329
372
|
|
|
330
373
|
// --- Filters ---
|
package/modules/assets/index.js
CHANGED
|
@@ -49,7 +49,7 @@ export function assetsCore(eleventyConfig, moduleContext) {
|
|
|
49
49
|
const parsed = optionsSchema.safeParse(options.assets);
|
|
50
50
|
if (!parsed.success) {
|
|
51
51
|
for (const issue of parsed.error.issues) {
|
|
52
|
-
log.info('options:', `${issue.path.join('.')}
|
|
52
|
+
log.info('options:', `${issue.path.join('.')}, ${issue.message}`);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -63,13 +63,15 @@ export function assetsCore(eleventyConfig, moduleContext) {
|
|
|
63
63
|
const watchGlob = TemplatePath.join(assetsDirectory, '**/*.{css,js,svg,png,jpeg,jpg,webp,gif,avif}');
|
|
64
64
|
|
|
65
65
|
if (!assetsDirectory) {
|
|
66
|
-
log.warn('
|
|
66
|
+
log.warn('directories.assets is unset, registerVirtualDir must run before this plugin');
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// Watch common asset formats so edits trigger reloads during --serve.
|
|
71
71
|
eleventyConfig.addWatchTarget(watchGlob);
|
|
72
72
|
|
|
73
|
+
log.info('Assets pipeline registered');
|
|
74
|
+
|
|
73
75
|
// --- JS (esbuild) ---
|
|
74
76
|
// Register js as a template format. Only index.js files under assets/js/
|
|
75
77
|
// are compiled; everything else (11tydata.js, non-entry scripts) is skipped
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as esbuild from 'esbuild';
|
|
2
|
-
import { createLogger } from '../../../core/logging.js';
|
|
2
|
+
import { createLogger } from '../../../core/logging/index.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* esbuild processor (processor)
|
|
@@ -61,7 +61,7 @@ export default async function assetsESbuild(jsFilePath, options = {}) {
|
|
|
61
61
|
// Return raw JS; markup wrapping is handled by the plugin registration.
|
|
62
62
|
return result.outputFiles[0].text;
|
|
63
63
|
} catch (error) {
|
|
64
|
-
log.error('esbuild failed
|
|
64
|
+
log.error('esbuild failed.', error);
|
|
65
65
|
// Surface a safe JS comment so the caller can decide how to wrap it.
|
|
66
66
|
return '/* Error processing JS */';
|
|
67
67
|
}
|
|
@@ -2,7 +2,7 @@ import fs from 'fs/promises';
|
|
|
2
2
|
import postcss from 'postcss';
|
|
3
3
|
import loadPostCSSConfig from 'postcss-load-config';
|
|
4
4
|
import fallbackPostCSSConfig from '../configs/postcss.config.js';
|
|
5
|
-
import { createLogger } from '../../../core/logging.js';
|
|
5
|
+
import { createLogger } from '../../../core/logging/index.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* PostCSS processor (processor)
|
|
@@ -79,7 +79,7 @@ export default async function assetsPostCSS(cssFilePath) {
|
|
|
79
79
|
// Return raw CSS; markup wrapping is handled in the plugin registration.
|
|
80
80
|
return result.css;
|
|
81
81
|
} catch (error) {
|
|
82
|
-
log.error('PostCSS failed
|
|
82
|
+
log.error('PostCSS failed.', error);
|
|
83
83
|
// Surface a safe CSS string so the caller can decide how to wrap it.
|
|
84
84
|
return '/* Error processing CSS */';
|
|
85
85
|
}
|