@apleasantview/eleventy-plugin-baseline 0.1.0-next.40 → 0.1.0-next.41

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 (51) hide show
  1. package/README.md +21 -23
  2. package/core/back-compat/options.js +69 -0
  3. package/core/content-graph/backlinks.js +65 -0
  4. package/core/content-graph/extractors.js +140 -0
  5. package/core/content-graph/graph.js +118 -0
  6. package/core/content-graph/index.js +2 -0
  7. package/core/content-graph/prepass.js +121 -0
  8. package/core/logging/banner.js +49 -0
  9. package/core/{logging.js → logging/index.js} +19 -2
  10. package/core/logging/quips.js +30 -0
  11. package/core/markdown/auto-heading-ids.js +86 -0
  12. package/core/markdown/index.js +5 -0
  13. package/core/markdown/safe-use.js +42 -0
  14. package/core/{wikilinks.js → markdown/wikilinks.js} +3 -3
  15. package/core/page-context/build.js +239 -0
  16. package/core/page-context/index.js +1 -0
  17. package/core/page-context/register.js +73 -0
  18. package/core/page-context/seo-helpers.js +56 -0
  19. package/core/schema.js +19 -1
  20. package/core/slug-index.js +2 -2
  21. package/core/state.js +73 -0
  22. package/core/{shortcodes/image.js → surface/image-shortcode.js} +4 -4
  23. package/core/surface/index.js +22 -0
  24. package/core/utils/add-trailing-slash.js +11 -0
  25. package/core/utils/ensure-dot-slash-dir.js +13 -0
  26. package/core/utils/normalize-languages.js +28 -0
  27. package/core/utils/resolve-field.js +9 -0
  28. package/core/utils/resolve-subdir.js +20 -0
  29. package/core/utils/slugify.js +15 -0
  30. package/core/utils/unique-by.js +25 -0
  31. package/core/virtual-dir.js +11 -10
  32. package/index.js +152 -115
  33. package/modules/assets/index.js +4 -2
  34. package/modules/assets/processors/esbuild-process.js +2 -2
  35. package/modules/assets/processors/postcss-process.js +2 -2
  36. package/modules/head/drivers/posthtml-head-elements.js +1 -3
  37. package/modules/head/index.js +7 -10
  38. package/modules/multilang/index.js +4 -2
  39. package/modules/navigator/index.js +33 -20
  40. package/modules/navigator/templates/navigator-core.html +1 -1
  41. package/modules/sitemap/index.js +7 -3
  42. package/package.json +4 -2
  43. package/core/filters/index.js +0 -4
  44. package/core/global-functions/index.js +0 -6
  45. package/core/page-context.js +0 -310
  46. package/core/shortcodes/index.js +0 -2
  47. package/core/utils/helpers.js +0 -75
  48. /package/core/{filters/markdown.js → markdown/markdownify.js} +0 -0
  49. /package/core/{filters → surface/filters}/isString.js +0 -0
  50. /package/core/{filters → surface/filters}/related-posts.js +0 -0
  51. /package/core/{global-functions/date.js → surface/global-date-function.js} +0 -0
package/core/state.js ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * State derivation (composition root helper)
3
+ *
4
+ * Pure normalisation of user-supplied `settings` and `options` into the
5
+ * resolved `state` shape modules read from. No eleventyConfig, no
6
+ * environment reads beyond the `mode` argument, no side effects.
7
+ *
8
+ * Architecture layer:
9
+ * composition root (pure helper)
10
+ *
11
+ * System role:
12
+ * The single place that applies defaults, fallbacks, and feature
13
+ * inference. Extracted from the entry point so it can be reasoned
14
+ * about — and tested — without booting Eleventy.
15
+ *
16
+ * Why this exists:
17
+ * Keeping defaults and feature derivation tangled with eleventyConfig
18
+ * wiring made the entry point hard to scan. Pulling the pure half out
19
+ * leaves the composition root as a list of registration steps.
20
+ *
21
+ * Scope:
22
+ * Owns settings/options normalisation and the derived `features` map.
23
+ * Does not own validation (see core/schema.js) or any runtime wiring.
24
+ *
25
+ * Data flow:
26
+ * settings + options + { mode } → { settings, options, features }
27
+ *
28
+ * @param {import('./types.js').BaselineSettings} settings
29
+ * @param {import('./types.js').BaselineOptions} options
30
+ * @param {{ mode?: string }} [env]
31
+ * @returns {import('./types.js').BaselineState & { features: Readonly<Record<string, boolean>> }}
32
+ */
33
+ export function deriveBaselineState(settings, options, { mode } = {}) {
34
+ const isDev = mode === 'development';
35
+
36
+ const resolvedSettings = {
37
+ title: settings.title,
38
+ tagline: settings.tagline,
39
+ url: settings.url,
40
+ noindex: settings.noindex ?? false,
41
+ defaultLanguage: settings.defaultLanguage,
42
+ languages: settings.languages,
43
+ head: settings.head
44
+ };
45
+
46
+ const resolvedOptions = {
47
+ verbose: options.verbose ?? true,
48
+ multilang: options.multilingual ?? false,
49
+ sitemap: options.sitemap ?? options.enableSitemapTemplate ?? true,
50
+ navigator: options.navigator ?? options.enableNavigatorTemplate ?? isDev,
51
+ head: {
52
+ titleSeparator: options.head?.titleSeparator,
53
+ showGenerator: options.head?.showGenerator
54
+ },
55
+ assets: {
56
+ esbuild: options.assets?.esbuild ?? options.assetsESBuild ?? {}
57
+ }
58
+ };
59
+
60
+ const features = Object.freeze({
61
+ multilang: Boolean(resolvedOptions.multilang),
62
+ sitemap: Boolean(resolvedOptions.sitemap),
63
+ navigator: Boolean(resolvedOptions.navigator),
64
+ head: true,
65
+ assets: true
66
+ });
67
+
68
+ return Object.freeze({
69
+ settings: Object.freeze(resolvedSettings),
70
+ options: Object.freeze(resolvedOptions),
71
+ features
72
+ });
73
+ }
@@ -1,10 +1,10 @@
1
1
  import path from 'node:path';
2
2
  import Image from '@11ty/eleventy-img';
3
- import { createLogger } from '../logging.js';
3
+ import { createLogger } from '../logging/index.js';
4
4
 
5
5
  // Module-level logger. Image shortcode only uses `.warn`, which emits regardless
6
6
  // of verbose, so we don't thread verbose through the shortcode signature.
7
- const log = createLogger('image');
7
+ const log = createLogger('image-shortcode');
8
8
 
9
9
  const DEFAULT_WIDTHS = [320, 640, 960, 1280, 1920, 'auto'];
10
10
  const DEFAULT_FORMATS = ['avif', 'webp'];
@@ -68,7 +68,7 @@ export async function imageShortcode(options = {}) {
68
68
 
69
69
  // --- Validation and normalization ---
70
70
 
71
- if (!src) throw new Error(`imageShortcode: src is required (received ${JSON.stringify(src)})`);
71
+ if (!src) throw new Error(`[baseline/image-shortcode] src is required (received ${JSON.stringify(src)})`);
72
72
  if (alt == null) {
73
73
  log.warn('alt is required (use empty string for decorative images)');
74
74
  }
@@ -106,7 +106,7 @@ export async function imageShortcode(options = {}) {
106
106
  });
107
107
  } catch (error) {
108
108
  if (process.env.ELEVENTY_RUN_MODE === 'serve') {
109
- log.warn(`transformOnRequest failed for ${src}, retrying.\n > ${error?.message || error}`);
109
+ log.warn(`transformOnRequest failed for ${src}, retrying. ${error?.message || error}`);
110
110
  metadata = await Image(resolvedSrc, imageOptions);
111
111
  } else {
112
112
  throw error;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Surface barrel
3
+ *
4
+ * Single entry point for everything Baseline registers against Eleventy that
5
+ * user templates can reach: filters, global functions, shortcodes.
6
+ */
7
+
8
+ import { registerDateGlobal } from './global-date-function.js';
9
+
10
+ // --- Filters ---
11
+ export { markdownFilter } from '../markdown/markdownify.js';
12
+ export { relatedPostsFilter } from './filters/related-posts.js';
13
+ export { isStringFilter } from './filters/isString.js';
14
+
15
+ // --- Shortcodes ---
16
+ export { imageShortcode } from './image-shortcode.js';
17
+
18
+ // --- Global functions (aggregator) ---
19
+ /** @param {import("@11ty/eleventy").UserConfig} eleventyConfig */
20
+ export function registerGlobals(eleventyConfig) {
21
+ registerDateGlobal(eleventyConfig);
22
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Helper function to add trailing slash to a path
3
+ * @param {string} path
4
+ * @returns {string}
5
+ */
6
+ export function addTrailingSlash(path) {
7
+ if (path.slice(-1) === '/') {
8
+ return path;
9
+ }
10
+ return path + '/';
11
+ }
@@ -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,28 @@
1
+ /**
2
+ * Normalize language input to an object map.
3
+ * Accepts an array of language codes or an object keyed by language code.
4
+ * Returns undefined if input is invalid or empty.
5
+ *
6
+ * @param {Object} settings - Options object containing languages.
7
+ * @param {import('../logging/index.js').BaselineLogger} [logger] - Logger for dropped-entry notice.
8
+ * @returns {Record<string, Object>|undefined} Normalized language map, or undefined.
9
+ */
10
+ export function normalizeLanguages(settings, logger) {
11
+ const normalizedLanguages = Array.isArray(settings.languages)
12
+ ? Object.fromEntries(
13
+ settings.languages
14
+ .filter((lang) => typeof lang === 'string' && lang.trim())
15
+ .map((lang) => [lang.toLowerCase().trim(), {}])
16
+ )
17
+ : settings.languages && typeof settings.languages === 'object'
18
+ ? settings.languages
19
+ : undefined;
20
+
21
+ if (logger && Array.isArray(settings.languages)) {
22
+ const normalizedCount = normalizedLanguages ? Object.keys(normalizedLanguages).length : 0;
23
+ if (normalizedCount !== settings.languages.length) {
24
+ logger.info('Some languages entries were invalid and were dropped.');
25
+ }
26
+ }
27
+ return normalizedLanguages;
28
+ }
@@ -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,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
+ );
@@ -1,8 +1,11 @@
1
- import { TemplatePath } from '@11ty/eleventy-utils';
2
- import { resolveSubdir } from './utils/helpers.js';
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('registerVirtualDir: `name` is required');
60
+ throw new Error('[baseline/virtual-dir] `name` is required');
60
61
  }
61
62
 
62
- const log = createLogger(SCOPE_NAME);
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[${key}] already defined; skipping`);
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 = TemplatePath.addLeadingDotSlash(dirs.input || './');
105
- const outputDir = TemplatePath.addLeadingDotSlash(dirs.output || './');
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).