@refrakt-md/content 0.14.4 → 0.16.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.
@@ -0,0 +1,4 @@
1
+ import type { PluginPipelineHooks } from '@refrakt-md/types';
2
+ /** Build the contributePages hook for a site's entityRoutes config. */
3
+ export declare function createEntityRoutesHooks(resolvePartial: (name: string) => string | undefined): PluginPipelineHooks;
4
+ //# sourceMappingURL=entity-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-routes.d.ts","sourceRoot":"","sources":["../src/entity-routes.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAKX,mBAAmB,EAEnB,MAAM,mBAAmB,CAAC;AAsC3B,uEAAuE;AACvE,wBAAgB,uBAAuB,CACtC,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAClD,mBAAmB,CA+CrB"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Built-in entityRoutes config-rules adapter (SPEC-069 / WORK-268).
3
+ *
4
+ * Turns `site.entityRoutes` into a `contributePages` hook: selects entities by
5
+ * `type` + `filter` (shared field-match grammar), substitutes `{name}`
6
+ * placeholders, renders the inline `render` string (or a `render-template`
7
+ * partial) per entity with `$item` bound, and back-fills each matched entity's
8
+ * `sourceUrl` so refs prefer the on-site page.
9
+ */
10
+ import { parseFieldMatch, matchesFieldMatch } from '@refrakt-md/runes';
11
+ /** Fields available to `{name}` substitution: top-level + everything in `data`. */
12
+ function substitutionFields(e) {
13
+ return { id: e.id, type: e.type, sourceUrl: e.sourceUrl, ...e.data };
14
+ }
15
+ /** Interpolate `{name}` placeholders from a field set. Missing → '' (with the
16
+ * caller deciding whether to warn). Optionally per-segment URL-encode. */
17
+ function substitute(template, fields, encode) {
18
+ return template.replace(/\{([^}]+)\}/g, (_m, name) => {
19
+ const raw = fields[name];
20
+ const value = raw == null ? '' : String(raw);
21
+ return encode ? value.split('/').map(encodeURIComponent).join('/') : value;
22
+ });
23
+ }
24
+ function substituteFrontmatter(fm, fields) {
25
+ if (!fm)
26
+ return undefined;
27
+ const out = {};
28
+ for (const [k, v] of Object.entries(fm)) {
29
+ out[k] = typeof v === 'string' ? substitute(v, fields, false) : v;
30
+ }
31
+ return out;
32
+ }
33
+ function project(e) {
34
+ const data = (e.data ?? {});
35
+ // Spread `data` so render templates can reference `$item.<field>` for the
36
+ // same fields available to URL `{name}` substitution. `id`, `type`, and
37
+ // `url` are canonical; `data` is preserved for templates that want the
38
+ // raw payload.
39
+ return { ...data, id: e.id, type: e.type, url: e.sourceUrl || String(data.url ?? ''), data };
40
+ }
41
+ /** Build the contributePages hook for a site's entityRoutes config. */
42
+ export function createEntityRoutesHooks(resolvePartial) {
43
+ return {
44
+ contributePages(ctx) {
45
+ const site = ctx.siteConfig;
46
+ const rules = site?.entityRoutes;
47
+ if (!rules || rules.length === 0)
48
+ return [];
49
+ const pages = [];
50
+ for (const rule of rules) {
51
+ const inline = rule.render;
52
+ const templateName = rule['render-template'];
53
+ if (inline && templateName) {
54
+ ctx.error(`entityRoutes: rule for type "${rule.type}" sets both render and render-template`);
55
+ continue;
56
+ }
57
+ let content = inline ?? '';
58
+ if (templateName) {
59
+ const loaded = resolvePartial(templateName);
60
+ if (loaded == null) {
61
+ ctx.error(`entityRoutes: render-template "${templateName}" could not be resolved`);
62
+ continue;
63
+ }
64
+ content = loaded;
65
+ }
66
+ const types = rule.type.split(',').map((s) => s.trim()).filter(Boolean);
67
+ const parsed = rule.filter ? parseFieldMatch(rule.filter) : undefined;
68
+ if (parsed)
69
+ for (const w of parsed.warnings)
70
+ ctx.warn(`entityRoutes filter: ${w}`);
71
+ let entities = [];
72
+ for (const type of types)
73
+ entities.push(...ctx.registry.getAll(type));
74
+ if (parsed)
75
+ entities = entities.filter((e) => matchesFieldMatch(e, parsed));
76
+ for (const entity of entities) {
77
+ const fields = substitutionFields(entity);
78
+ const url = substitute(rule.url, fields, true);
79
+ const title = rule.title ? substitute(rule.title, fields, false) : undefined;
80
+ const frontmatter = substituteFrontmatter(rule.frontmatter, fields);
81
+ pages.push({ url, title, frontmatter, content, variables: { item: project(entity) } });
82
+ // Back-fill sourceUrl so refs/expands prefer the on-site page.
83
+ // The registry stores live registration objects (pre-aggregate).
84
+ entity.sourceUrl = url;
85
+ }
86
+ }
87
+ return pages;
88
+ },
89
+ };
90
+ }
91
+ //# sourceMappingURL=entity-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-routes.js","sourceRoot":"","sources":["../src/entity-routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * File-roots scanning and resolution.
3
+ *
4
+ * A file root is a named directory that file-reading runes can reach via the
5
+ * `namespace:filename` syntax. The v1 consumer is Markdoc partials —
6
+ * `{% partial file="shared:footer.md" /%}` resolves from whatever directory
7
+ * is registered under `shared`. Snippet (SPEC-062 v2) and future file-reading
8
+ * runes plug into the same resolver.
9
+ *
10
+ * File roots originate from two sources:
11
+ * - **User config** — `refrakt.config.json#/fileRoots`, paths relative to the
12
+ * config file's directory.
13
+ * - **Plugins** — `Plugin.fileRoots` field, paths relative to the plugin's
14
+ * package directory (resolved by `loadPlugin` before reaching this layer).
15
+ *
16
+ * Plugin-vs-plugin collisions throw at merge time. User-vs-plugin collisions
17
+ * let the user win (see {@link mergeFileRoots}).
18
+ */
19
+ import type { PartialFile } from './content-tree.js';
20
+ export type FileRoots = Record<string, string>;
21
+ /** Result of merging user-config + plugin file roots. */
22
+ export interface MergedFileRoots {
23
+ /** Final namespace → absolute directory path map (user wins collisions). */
24
+ roots: FileRoots;
25
+ /** Soft diagnostics: warnings about plugin namespaces shadowed by user
26
+ * config. Adapters can surface these alongside other build warnings. */
27
+ warnings: string[];
28
+ }
29
+ /**
30
+ * Resolve user-config file roots against the config-file directory.
31
+ *
32
+ * Each value in `userFileRoots` is interpreted relative to `configDir`.
33
+ * Validates namespace names (rejects reserved + empty) and that each
34
+ * resolved directory exists.
35
+ */
36
+ export declare function resolveUserFileRoots(userFileRoots: FileRoots | undefined, configDir: string): FileRoots;
37
+ /**
38
+ * Merge plugin-registered file roots with user-config-resolved ones.
39
+ *
40
+ * Precedence: **user wins** any collision. Plugins whose namespaces are
41
+ * shadowed by user config are still loaded (the runes etc.) but their
42
+ * file-root contribution is silently dropped, with a soft warning so the
43
+ * shadowing is visible during development.
44
+ */
45
+ export declare function mergeFileRoots(userRoots: FileRoots, pluginRoots: FileRoots): MergedFileRoots;
46
+ /**
47
+ * Scan every registered file root, returning `namespace:filename` →
48
+ * `PartialFile`.
49
+ *
50
+ * Each root is validated to exist (throws otherwise — broken config should
51
+ * fail loud at load time). Inside each root, all `.md` files are picked up
52
+ * recursively; subdirectory paths flow through as part of the filename
53
+ * (`shared:legal/terms.md`).
54
+ */
55
+ export declare function readFileRoots(roots: FileRoots): Promise<Map<string, PartialFile>>;
56
+ /** Validate a `namespace:filename` reference against the registered roots.
57
+ *
58
+ * Returns the absolute file path for valid references; throws on:
59
+ * - Unknown namespace (listing the available ones).
60
+ * - Empty namespace (`:foo.md`).
61
+ * - Absolute paths (`shared:/abs.md`).
62
+ * - Traversal escapes (`shared:../escape.md`).
63
+ *
64
+ * Note: this validation runs at scan time (above), so by the time content
65
+ * authors hit a problem they see it at build, not at render. The function
66
+ * is exported for runes (like the snippet rune's v2) that need to validate
67
+ * ad-hoc references against the same rules.
68
+ */
69
+ export declare function validateNamespacedReference(ref: string, roots: FileRoots): string;
70
+ //# sourceMappingURL=file-roots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-roots.d.ts","sourceRoot":"","sources":["../src/file-roots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE/C,yDAAyD;AACzD,MAAM,WAAW,eAAe;IAC/B,4EAA4E;IAC5E,KAAK,EAAE,SAAS,CAAC;IACjB;6EACyE;IACzE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CACnC,aAAa,EAAE,SAAS,GAAG,SAAS,EACpC,SAAS,EAAE,MAAM,GACf,SAAS,CAsBX;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC7B,SAAS,EAAE,SAAS,EACpB,WAAW,EAAE,SAAS,GACpB,eAAe,CAYjB;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAmBvF;AAwBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CAC1C,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,SAAS,GACd,MAAM,CAgCR"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * File-roots scanning and resolution.
3
+ *
4
+ * A file root is a named directory that file-reading runes can reach via the
5
+ * `namespace:filename` syntax. The v1 consumer is Markdoc partials —
6
+ * `{% partial file="shared:footer.md" /%}` resolves from whatever directory
7
+ * is registered under `shared`. Snippet (SPEC-062 v2) and future file-reading
8
+ * runes plug into the same resolver.
9
+ *
10
+ * File roots originate from two sources:
11
+ * - **User config** — `refrakt.config.json#/fileRoots`, paths relative to the
12
+ * config file's directory.
13
+ * - **Plugins** — `Plugin.fileRoots` field, paths relative to the plugin's
14
+ * package directory (resolved by `loadPlugin` before reaching this layer).
15
+ *
16
+ * Plugin-vs-plugin collisions throw at merge time. User-vs-plugin collisions
17
+ * let the user win (see {@link mergeFileRoots}).
18
+ */
19
+ import * as fs from 'node:fs';
20
+ import * as path from 'node:path';
21
+ /**
22
+ * Resolve user-config file roots against the config-file directory.
23
+ *
24
+ * Each value in `userFileRoots` is interpreted relative to `configDir`.
25
+ * Validates namespace names (rejects reserved + empty) and that each
26
+ * resolved directory exists.
27
+ */
28
+ export function resolveUserFileRoots(userFileRoots, configDir) {
29
+ if (!userFileRoots || Object.keys(userFileRoots).length === 0)
30
+ return {};
31
+ const resolved = {};
32
+ for (const [namespace, relativePath] of Object.entries(userFileRoots)) {
33
+ if (namespace.length === 0) {
34
+ throw new Error(`refrakt.config.json#/fileRoots: namespace name is empty — namespaces must be non-empty strings.`);
35
+ }
36
+ if (namespace === 'site') {
37
+ throw new Error(`refrakt.config.json#/fileRoots: namespace "${namespace}" is reserved. Pick a different name.`);
38
+ }
39
+ if (typeof relativePath !== 'string' || relativePath.length === 0) {
40
+ throw new Error(`refrakt.config.json#/fileRoots["${namespace}"] must be a non-empty string path.`);
41
+ }
42
+ resolved[namespace] = path.resolve(configDir, relativePath);
43
+ }
44
+ return resolved;
45
+ }
46
+ /**
47
+ * Merge plugin-registered file roots with user-config-resolved ones.
48
+ *
49
+ * Precedence: **user wins** any collision. Plugins whose namespaces are
50
+ * shadowed by user config are still loaded (the runes etc.) but their
51
+ * file-root contribution is silently dropped, with a soft warning so the
52
+ * shadowing is visible during development.
53
+ */
54
+ export function mergeFileRoots(userRoots, pluginRoots) {
55
+ const merged = { ...pluginRoots };
56
+ const warnings = [];
57
+ for (const [namespace, absPath] of Object.entries(userRoots)) {
58
+ if (merged[namespace] && merged[namespace] !== absPath) {
59
+ warnings.push(`File-root namespace "${namespace}" is registered by both user config and a plugin. User config wins.`);
60
+ }
61
+ merged[namespace] = absPath;
62
+ }
63
+ return { roots: merged, warnings };
64
+ }
65
+ /**
66
+ * Scan every registered file root, returning `namespace:filename` →
67
+ * `PartialFile`.
68
+ *
69
+ * Each root is validated to exist (throws otherwise — broken config should
70
+ * fail loud at load time). Inside each root, all `.md` files are picked up
71
+ * recursively; subdirectory paths flow through as part of the filename
72
+ * (`shared:legal/terms.md`).
73
+ */
74
+ export async function readFileRoots(roots) {
75
+ const map = new Map();
76
+ for (const [namespace, absPath] of Object.entries(roots)) {
77
+ let stat;
78
+ try {
79
+ stat = await fs.promises.stat(absPath);
80
+ }
81
+ catch {
82
+ throw new Error(`File root "${namespace}" — directory does not exist: ${absPath}`);
83
+ }
84
+ if (!stat.isDirectory()) {
85
+ throw new Error(`File root "${namespace}" — expected a directory, got a file: ${absPath}`);
86
+ }
87
+ await scanRoot(absPath, absPath, namespace, map);
88
+ }
89
+ return map;
90
+ }
91
+ async function scanRoot(dirPath, rootPath, namespace, map) {
92
+ const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
93
+ for (const entry of entries) {
94
+ const fullPath = path.join(dirPath, entry.name);
95
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
96
+ await scanRoot(fullPath, rootPath, namespace, map);
97
+ }
98
+ else if (entry.isFile() && entry.name.endsWith('.md')) {
99
+ const raw = await fs.promises.readFile(fullPath, 'utf-8');
100
+ // Use POSIX-style slashes in the key regardless of host OS so that
101
+ // authoring stays consistent across platforms.
102
+ const relative = path.relative(rootPath, fullPath).split(path.sep).join('/');
103
+ const key = `${namespace}:${relative}`;
104
+ map.set(key, { name: key, filePath: fullPath, raw });
105
+ }
106
+ }
107
+ }
108
+ /** Validate a `namespace:filename` reference against the registered roots.
109
+ *
110
+ * Returns the absolute file path for valid references; throws on:
111
+ * - Unknown namespace (listing the available ones).
112
+ * - Empty namespace (`:foo.md`).
113
+ * - Absolute paths (`shared:/abs.md`).
114
+ * - Traversal escapes (`shared:../escape.md`).
115
+ *
116
+ * Note: this validation runs at scan time (above), so by the time content
117
+ * authors hit a problem they see it at build, not at render. The function
118
+ * is exported for runes (like the snippet rune's v2) that need to validate
119
+ * ad-hoc references against the same rules.
120
+ */
121
+ export function validateNamespacedReference(ref, roots) {
122
+ const colonIdx = ref.indexOf(':');
123
+ if (colonIdx <= 0) {
124
+ throw new Error(`File-root reference "${ref}" is missing a namespace prefix. Expected "<namespace>:<path>".`);
125
+ }
126
+ const namespace = ref.slice(0, colonIdx);
127
+ const relative = ref.slice(colonIdx + 1);
128
+ if (relative.length === 0) {
129
+ throw new Error(`File-root reference "${ref}" is missing a path after the colon.`);
130
+ }
131
+ const root = roots[namespace];
132
+ if (!root) {
133
+ const available = Object.keys(roots).join(', ') || '(none registered)';
134
+ throw new Error(`Unknown file-root namespace "${namespace}" in reference "${ref}". Available: ${available}.`);
135
+ }
136
+ if (relative.startsWith('/')) {
137
+ throw new Error(`File-root reference "${ref}" uses an absolute path; namespaced references must be relative to the root.`);
138
+ }
139
+ const resolved = path.resolve(root, relative);
140
+ const rootWithSep = root.endsWith(path.sep) ? root : root + path.sep;
141
+ if (!resolved.startsWith(rootWithSep) && resolved !== root) {
142
+ throw new Error(`File-root reference "${ref}" escapes its root directory. Paths must stay within "${root}".`);
143
+ }
144
+ return resolved;
145
+ }
146
+ //# sourceMappingURL=file-roots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-roots.js","sourceRoot":"","sources":["../src/file-roots.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -14,4 +14,5 @@ export { EntityRegistryImpl } from './registry.js';
14
14
  export { createRefraktLoader, createVirtualRefraktLoader, buildHighlightOptions, type RefraktLoader, type RefraktLoaderOptions, type VirtualRefraktLoaderOptions } from './refract-loader.js';
15
15
  export { runPipeline, type HookSet, type PipelineResult, type PipelineStats } from './pipeline.js';
16
16
  export { formatPipelineSummary } from './format.js';
17
+ export { readFileRoots, resolveUserFileRoots, mergeFileRoots, validateNamespacedReference, type FileRoots, type MergedFileRoots, } from './file-roots.js';
17
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC7H,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC5F,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EACL,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,GACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC3F,OAAO,EAAE,eAAe,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,KAAK,0BAA0B,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAC5I,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,KAAK,UAAU,EAAE,KAAK,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAChJ,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC9G,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,KAAK,aAAa,EAAE,KAAK,oBAAoB,EAAE,KAAK,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAC9L,OAAO,EAAE,WAAW,EAAE,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC7H,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC5F,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EACL,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,GACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC3F,OAAO,EAAE,eAAe,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,KAAK,0BAA0B,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAC5I,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,KAAK,UAAU,EAAE,KAAK,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAChJ,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC9G,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,KAAK,aAAa,EAAE,KAAK,oBAAoB,EAAE,KAAK,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAC9L,OAAO,EAAE,WAAW,EAAE,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,2BAA2B,EAC3B,KAAK,SAAS,EACd,KAAK,eAAe,GACrB,MAAM,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -14,4 +14,5 @@ export { EntityRegistryImpl } from './registry.js';
14
14
  export { createRefraktLoader, createVirtualRefraktLoader, buildHighlightOptions } from './refract-loader.js';
15
15
  export { runPipeline } from './pipeline.js';
16
16
  export { formatPipelineSummary } from './format.js';
17
+ export { readFileRoots, resolveUserFileRoots, mergeFileRoots, validateNamespacedReference, } from './file-roots.js';
17
18
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA+E,MAAM,mBAAmB,CAAC;AAC7H,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAoB,MAAM,kBAAkB,CAAC;AAC5F,OAAO,EAAE,MAAM,EAAc,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,cAAc,EAAoC,MAAM,aAAa,CAAC;AAC/E,OAAO,EACL,kBAAkB,GAGnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC3F,OAAO,EAAE,eAAe,EAA6C,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAiF,MAAM,WAAW,CAAC;AAC5I,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAA0E,MAAM,aAAa,CAAC;AAChJ,OAAO,EAAE,eAAe,EAAqB,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAwB,MAAM,cAAc,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EAAuB,MAAM,iBAAiB,CAAC;AAC9G,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,qBAAqB,EAAmF,MAAM,qBAAqB,CAAC;AAC9L,OAAO,EAAE,WAAW,EAAyD,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA+E,MAAM,mBAAmB,CAAC;AAC7H,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAoB,MAAM,kBAAkB,CAAC;AAC5F,OAAO,EAAE,MAAM,EAAc,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,cAAc,EAAoC,MAAM,aAAa,CAAC;AAC/E,OAAO,EACL,kBAAkB,GAGnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC3F,OAAO,EAAE,eAAe,EAA6C,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAiF,MAAM,WAAW,CAAC;AAC5I,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAA0E,MAAM,aAAa,CAAC;AAChJ,OAAO,EAAE,eAAe,EAAqB,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAwB,MAAM,cAAc,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EAAuB,MAAM,iBAAiB,CAAC;AAC9G,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,qBAAqB,EAAmF,MAAM,qBAAqB,CAAC;AAC9L,OAAO,EAAE,WAAW,EAAyD,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,2BAA2B,GAG5B,MAAM,iBAAiB,CAAC"}
package/dist/loader.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import type { Schema } from '@markdoc/markdoc';
2
2
  import type { Plugin, SecurityPolicy } from '@refrakt-md/types';
3
+ import type { CompiledXrefPattern } from '@refrakt-md/runes';
3
4
  import { type Site, type VirtualReader } from './site.js';
4
5
  import type { ContentTree } from './content-tree.js';
6
+ import type { FileRoots } from './file-roots.js';
5
7
  export interface SiteLoaderOptions {
6
8
  dirPath: string;
7
9
  basePath?: string;
@@ -13,6 +15,22 @@ export interface SiteLoaderOptions {
13
15
  variables?: Record<string, unknown>;
14
16
  /** Security policy for untrusted author content. Default: `'trusted'`. */
15
17
  securityPolicy?: SecurityPolicy;
18
+ /** Absolute path to the project root (where `refrakt.config.json` lives).
19
+ * Used to compute `$file.path` as a project-root-relative POSIX path.
20
+ * When omitted, defaults to `dirPath`'s parent — adapters that resolve a
21
+ * config file should pass `dirname(configPath)` explicitly. */
22
+ projectRoot?: string;
23
+ /** Compiled xref patterns from `refrakt.config.json#/xrefs`. Adapters
24
+ * that read the config should compile via `compileXrefPatterns` and
25
+ * pass the result here. */
26
+ xrefPatterns?: CompiledXrefPattern[];
27
+ /** Registered file roots — namespace → absolute directory path. */
28
+ fileRoots?: FileRoots;
29
+ /** Per-site config slice — passed to contributePages hooks so the built-in
30
+ * entityRoutes adapter can read `entityRoutes` and other site-scoped
31
+ * config. Without this, `entityRoutes` rules in the project's config
32
+ * silently produce no pages. */
33
+ siteConfig?: unknown;
16
34
  /** When true, every load() call re-reads from disk (no caching). Default: false. */
17
35
  dev?: boolean;
18
36
  }
@@ -38,6 +56,16 @@ export interface VirtualSiteLoaderOptions {
38
56
  /** Optional async reader for ad-hoc lookups. Forward-compatibility hook —
39
57
  * see `LoadContentFromTreeOptions.reader` for details. */
40
58
  reader?: VirtualReader;
59
+ /** Absolute path to the project root (where `refrakt.config.json` lives).
60
+ * Used to compute `$file.path` as a project-root-relative POSIX path. */
61
+ projectRoot?: string;
62
+ /** Compiled xref patterns from `refrakt.config.json#/xrefs`. */
63
+ xrefPatterns?: CompiledXrefPattern[];
64
+ /** Registered file roots — namespace → absolute directory path. */
65
+ fileRoots?: FileRoots;
66
+ /** Per-site config slice — passed to contributePages hooks. See
67
+ * {@link SiteLoaderOptions.siteConfig}. */
68
+ siteConfig?: unknown;
41
69
  /** When true, every load() call re-runs the pipeline against the current
42
70
  * tree (no caching). Use when the host swaps the tree's contents in place. */
43
71
  dev?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAoC,KAAK,IAAI,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAC5F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,0EAA0E;IAC1E,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,oFAAoF;IACpF,GAAG,CAAC,EAAE,OAAO,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IAC1B,0DAA0D;IAC1D,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,sEAAsE;IACtE,UAAU,IAAI,IAAI,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAuBvE;AAED,MAAM,WAAW,wBAAwB;IACxC;uDACmD;IACnD,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,+DAA+D;IAC/D,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;+DAC2D;IAC3D,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB;mFAC+E;IAC/E,GAAG,CAAC,EAAE,OAAO,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,UAAU,CAsBrF"}
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAoC,KAAK,IAAI,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAC5F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,0EAA0E;IAC1E,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;oEAGgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;gCAE4B;IAC5B,YAAY,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACrC,mEAAmE;IACnE,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;qCAGiC;IACjC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oFAAoF;IACpF,GAAG,CAAC,EAAE,OAAO,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IAC1B,0DAA0D;IAC1D,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,sEAAsE;IACtE,UAAU,IAAI,IAAI,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CA2BvE;AAED,MAAM,WAAW,wBAAwB;IACxC;uDACmD;IACnD,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,+DAA+D;IAC/D,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;+DAC2D;IAC3D,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB;8EAC0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,YAAY,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACrC,mEAAmE;IACnE,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;gDAC4C;IAC5C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;mFAC+E;IAC/E,GAAG,CAAC,EAAE,OAAO,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,UAAU,CA0BrF"}
package/dist/loader.js CHANGED
@@ -5,7 +5,7 @@ export function createSiteLoader(options) {
5
5
  load() {
6
6
  if (!options.dev && cached)
7
7
  return cached;
8
- const promise = loadContent(options.dirPath, options.basePath, options.icons, options.additionalTags, options.plugins, options.sandboxExamplesDir, options.variables, options.securityPolicy);
8
+ const promise = loadContent(options.dirPath, options.basePath, options.icons, options.additionalTags, options.plugins, options.sandboxExamplesDir, options.variables, options.securityPolicy, options.projectRoot, options.xrefPatterns, options.fileRoots, options.siteConfig);
9
9
  if (!options.dev)
10
10
  cached = promise;
11
11
  return promise;
@@ -35,6 +35,10 @@ export function createVirtualSiteLoader(options) {
35
35
  variables: options.variables,
36
36
  securityPolicy: options.securityPolicy,
37
37
  reader: options.reader,
38
+ projectRoot: options.projectRoot,
39
+ xrefPatterns: options.xrefPatterns,
40
+ fileRoots: options.fileRoots,
41
+ siteConfig: options.siteConfig,
38
42
  });
39
43
  if (!options.dev)
40
44
  cached = promise;
@@ -1 +1 @@
1
- {"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAiC,MAAM,WAAW,CAAC;AAyB5F,MAAM,UAAU,gBAAgB,CAAC,OAA0B;IAC1D,IAAI,MAAM,GAAyB,IAAI,CAAC;IAExC,OAAO;QACN,IAAI;YACH,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAC;YAC1C,MAAM,OAAO,GAAG,WAAW,CAC1B,OAAO,CAAC,OAAO,EACf,OAAO,CAAC,QAAQ,EAChB,OAAO,CAAC,KAAK,EACb,OAAO,CAAC,cAAc,EACtB,OAAO,CAAC,OAAO,EACf,OAAO,CAAC,kBAAkB,EAC1B,OAAO,CAAC,SAAS,EACjB,OAAO,CAAC,cAAc,CACtB,CAAC;YACF,IAAI,CAAC,OAAO,CAAC,GAAG;gBAAE,MAAM,GAAG,OAAO,CAAC;YACnC,OAAO,OAAO,CAAC;QAChB,CAAC;QACD,UAAU;YACT,MAAM,GAAG,IAAI,CAAC;QACf,CAAC;KACD,CAAC;AACH,CAAC;AAsBD;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAiC;IACxE,IAAI,MAAM,GAAyB,IAAI,CAAC;IAExC,OAAO;QACN,IAAI;YACH,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAC;YAC1C,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,CAAC,IAAI,EAAE;gBACjD,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,cAAc,EAAE,OAAO,CAAC,cAAc;gBACtC,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,cAAc,EAAE,OAAO,CAAC,cAAc;gBACtC,MAAM,EAAE,OAAO,CAAC,MAAM;aACtB,CAAC,CAAC;YACH,IAAI,CAAC,OAAO,CAAC,GAAG;gBAAE,MAAM,GAAG,OAAO,CAAC;YACnC,OAAO,OAAO,CAAC;QAChB,CAAC;QACD,UAAU;YACT,MAAM,GAAG,IAAI,CAAC;QACf,CAAC;KACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAiC,MAAM,WAAW,CAAC;AA0C5F,MAAM,UAAU,gBAAgB,CAAC,OAA0B;IAC1D,IAAI,MAAM,GAAyB,IAAI,CAAC;IAExC,OAAO;QACN,IAAI;YACH,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAC;YAC1C,MAAM,OAAO,GAAG,WAAW,CAC1B,OAAO,CAAC,OAAO,EACf,OAAO,CAAC,QAAQ,EAChB,OAAO,CAAC,KAAK,EACb,OAAO,CAAC,cAAc,EACtB,OAAO,CAAC,OAAO,EACf,OAAO,CAAC,kBAAkB,EAC1B,OAAO,CAAC,SAAS,EACjB,OAAO,CAAC,cAAc,EACtB,OAAO,CAAC,WAAW,EACnB,OAAO,CAAC,YAAY,EACpB,OAAO,CAAC,SAAS,EACjB,OAAO,CAAC,UAAU,CAClB,CAAC;YACF,IAAI,CAAC,OAAO,CAAC,GAAG;gBAAE,MAAM,GAAG,OAAO,CAAC;YACnC,OAAO,OAAO,CAAC;QAChB,CAAC;QACD,UAAU;YACT,MAAM,GAAG,IAAI,CAAC;QACf,CAAC;KACD,CAAC;AACH,CAAC;AAgCD;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAiC;IACxE,IAAI,MAAM,GAAyB,IAAI,CAAC;IAExC,OAAO;QACN,IAAI;YACH,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAC;YAC1C,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,CAAC,IAAI,EAAE;gBACjD,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,cAAc,EAAE,OAAO,CAAC,cAAc;gBACtC,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,cAAc,EAAE,OAAO,CAAC,cAAc;gBACtC,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,YAAY,EAAE,OAAO,CAAC,YAAY;gBAClC,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,UAAU,EAAE,OAAO,CAAC,UAAU;aAC9B,CAAC,CAAC;YACH,IAAI,CAAC,OAAO,CAAC,GAAG;gBAAE,MAAM,GAAG,OAAO,CAAC;YACnC,OAAO,OAAO,CAAC;QAChB,CAAC;QACD,UAAU;YACT,MAAM,GAAG,IAAI,CAAC;QACf,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -1,10 +1,20 @@
1
- import type { PluginPipelineHooks, AggregatedData, PipelineWarning } from '@refrakt-md/types';
1
+ import type { PluginPipelineHooks, AggregatedData, PipelineWarning, ContributedPage } from '@refrakt-md/types';
2
2
  import type { SitePage } from './site.js';
3
3
  /** A package and its pipeline hooks, ready to run */
4
4
  export interface HookSet {
5
5
  pluginName: string;
6
6
  hooks: PluginPipelineHooks;
7
7
  }
8
+ /** Options for the contribution phase (SPEC-069). */
9
+ export interface RunPipelineOptions {
10
+ /** Render a contributed page's source into a full SitePage (provided by the
11
+ * content loader, which owns the parse + transform machinery). */
12
+ renderContributed?: (cp: ContributedPage) => SitePage;
13
+ /** Absolute project root, passed to contributePages hooks. */
14
+ projectRoot?: string;
15
+ /** Per-site config slice, passed to contributePages hooks. */
16
+ siteConfig?: unknown;
17
+ }
8
18
  /** Build-phase statistics from the pipeline run */
9
19
  export interface PipelineStats {
10
20
  /** Total pages processed */
@@ -36,5 +46,5 @@ export interface PipelineResult {
36
46
  * Errors in individual hooks are caught, converted to PipelineWarning entries,
37
47
  * and the pipeline continues with the next hook / page.
38
48
  */
39
- export declare function runPipeline(pages: SitePage[], hookSets: HookSet[]): Promise<PipelineResult>;
49
+ export declare function runPipeline(pages: SitePage[], hookSets: HookSet[], options?: RunPipelineOptions): Promise<PipelineResult>;
40
50
  //# sourceMappingURL=pipeline.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,mBAAmB,EAGnB,cAAc,EACd,eAAe,EAEf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAG1C,qDAAqD;AACrD,MAAM,WAAW,OAAO;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,mBAAmB,CAAC;CAC3B;AAED,mDAAmD;AACnD,MAAM,WAAW,aAAa;IAC7B,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,gDAAgD;AAChD,MAAM,WAAW,cAAc;IAC9B,8DAA8D;IAC9D,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,6EAA6E;IAC7E,UAAU,EAAE,cAAc,CAAC;IAC3B,8CAA8C;IAC9C,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,sCAAsC;IACtC,KAAK,EAAE,aAAa,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAChC,KAAK,EAAE,QAAQ,EAAE,EACjB,QAAQ,EAAE,OAAO,EAAE,GACjB,OAAO,CAAC,cAAc,CAAC,CAqEzB"}
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,mBAAmB,EAGnB,cAAc,EACd,eAAe,EAEf,eAAe,EACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAG1C,qDAAqD;AACrD,MAAM,WAAW,OAAO;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,mBAAmB,CAAC;CAC3B;AAED,qDAAqD;AACrD,MAAM,WAAW,kBAAkB;IAClC;uEACmE;IACnE,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,eAAe,KAAK,QAAQ,CAAC;IACtD,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,mDAAmD;AACnD,MAAM,WAAW,aAAa;IAC7B,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,gDAAgD;AAChD,MAAM,WAAW,cAAc;IAC9B,8DAA8D;IAC9D,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,6EAA6E;IAC7E,UAAU,EAAE,cAAc,CAAC;IAC3B,8CAA8C;IAC9C,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,sCAAsC;IACtC,KAAK,EAAE,aAAa,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAChC,KAAK,EAAE,QAAQ,EAAE,EACjB,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,GAAE,kBAAuB,GAC9B,OAAO,CAAC,cAAc,CAAC,CAqIzB"}
package/dist/pipeline.js CHANGED
@@ -10,11 +10,13 @@ import { EntityRegistryImpl } from './registry.js';
10
10
  * Errors in individual hooks are caught, converted to PipelineWarning entries,
11
11
  * and the pipeline continues with the next hook / page.
12
12
  */
13
- export async function runPipeline(pages, hookSets) {
13
+ export async function runPipeline(pages, hookSets, options = {}) {
14
14
  const warnings = [];
15
15
  const registry = new EntityRegistryImpl();
16
+ // Working page set — grows as plugins contribute pages (SPEC-069).
17
+ const allPages = [...pages];
16
18
  // Convert SitePage[] → TransformedPage[] (lightweight view, no deep copy)
17
- const transformedPages = pages.map(pageToTransformed);
19
+ const transformedPages = allPages.map(pageToTransformed);
18
20
  // ─── Phase 2: Register ───
19
21
  for (const { pluginName, hooks } of hookSets) {
20
22
  if (!hooks.register)
@@ -27,6 +29,66 @@ export async function runPipeline(pages, hookSets) {
27
29
  ctx.error(err.message);
28
30
  }
29
31
  }
32
+ // ─── Phase 2.5: Contribute pages (SPEC-069) ───
33
+ // Plugins synthesize virtual pages (often from registered entities). They run
34
+ // after register so the registry is populated; contributed pages then flow
35
+ // through register (a second pass, for their own entities) → aggregate →
36
+ // postProcess like file pages. Contributed pages cannot trigger another
37
+ // contribution phase (the graph is one level deep, by design).
38
+ if (options.renderContributed) {
39
+ const fileUrls = new Set(allPages.map((p) => p.route.url));
40
+ const contributedUrls = new Map();
41
+ const newPages = [];
42
+ for (const { pluginName, hooks } of hookSets) {
43
+ if (!hooks.contributePages)
44
+ continue;
45
+ const ctx = makeContext(warnings, 'contribute', pluginName);
46
+ try {
47
+ const contributed = await hooks.contributePages({
48
+ ...ctx,
49
+ registry: registry,
50
+ projectRoot: options.projectRoot,
51
+ siteConfig: options.siteConfig,
52
+ });
53
+ contributed.forEach((cp, ruleIndex) => {
54
+ if (fileUrls.has(cp.url)) {
55
+ ctx.warn(`contributed page "${cp.url}" collides with a file-backed page; the file wins`, cp.url);
56
+ return;
57
+ }
58
+ const prior = contributedUrls.get(cp.url);
59
+ if (prior) {
60
+ ctx.error(`contributed page "${cp.url}" collides with one from "${prior}"`, cp.url);
61
+ return;
62
+ }
63
+ contributedUrls.set(cp.url, pluginName);
64
+ const sp = options.renderContributed({ ...cp, source: { plugin: pluginName, ruleIndex } });
65
+ newPages.push(sp);
66
+ });
67
+ }
68
+ catch (err) {
69
+ ctx.error(err.message);
70
+ }
71
+ }
72
+ if (newPages.length > 0) {
73
+ const newTransformed = newPages.map(pageToTransformed);
74
+ allPages.push(...newPages);
75
+ transformedPages.push(...newTransformed);
76
+ // Second register pass: index entities the contributed pages themselves
77
+ // declare (so refs/expands elsewhere resolve them). They do not trigger
78
+ // another contribution round.
79
+ for (const { pluginName, hooks } of hookSets) {
80
+ if (!hooks.register)
81
+ continue;
82
+ const ctx = makeContext(warnings, 'register', pluginName);
83
+ try {
84
+ hooks.register(newTransformed, registry, ctx);
85
+ }
86
+ catch (err) {
87
+ ctx.error(err.message);
88
+ }
89
+ }
90
+ }
91
+ }
30
92
  // ─── Phase 3: Aggregate ───
31
93
  const aggregated = {};
32
94
  const frozenRegistry = registry;
@@ -57,17 +119,22 @@ export async function runPipeline(pages, hookSets) {
57
119
  }
58
120
  });
59
121
  }
60
- // Merge post-processed renderables back into the original SitePage objects.
61
- // Cast from unknown back to RenderableTreeNodes — postProcess hooks are responsible
62
- // for returning the same AST node type they received.
63
- const resultPages = pages.map((page, i) => ({
122
+ // Merge post-processed renderables back into the SitePage objects (file +
123
+ // contributed). Cast from unknown back to RenderableTreeNodes — postProcess
124
+ // hooks are responsible for returning the same AST node type they received.
125
+ const resultPages = allPages.map((page, i) => ({
64
126
  ...page,
65
127
  renderable: working[i].renderable,
128
+ // `postProcess` can refresh headings (e.g. expand `level=N` inlines
129
+ // new headings that parse-time `extractHeadings` couldn't see). Merge
130
+ // the latest version back so the layout TOC builder sees what's in
131
+ // the final renderable.
132
+ headings: working[i].headings,
66
133
  }));
67
134
  // Tally entity count across all registered types
68
135
  const entityCount = registry.getTypes().reduce((sum, type) => sum + registry.getAll(type).length, 0);
69
136
  const stats = {
70
- pageCount: pages.length,
137
+ pageCount: allPages.length,
71
138
  entityCount,
72
139
  packageCount: hookSets.filter(hs => hs.hooks.register || hs.hooks.aggregate || hs.hooks.postProcess).length,
73
140
  };
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AA8BnD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAChC,KAAiB,EACjB,QAAmB;IAEnB,MAAM,QAAQ,GAAsB,EAAE,CAAC;IACvC,MAAM,QAAQ,GAAG,IAAI,kBAAkB,EAAE,CAAC;IAE1C,0EAA0E;IAC1E,MAAM,gBAAgB,GAAsB,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAEzE,4BAA4B;IAC5B,KAAK,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC9C,IAAI,CAAC,KAAK,CAAC,QAAQ;YAAE,SAAS;QAC9B,MAAM,GAAG,GAAG,WAAW,CAAC,QAAQ,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;QAC1D,IAAI,CAAC;YACJ,KAAK,CAAC,QAAQ,CAAC,gBAAgB,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;QACjD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,GAAG,CAAC,KAAK,CAAE,GAAa,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;IACF,CAAC;IAED,6BAA6B;IAC7B,MAAM,UAAU,GAAmB,EAAE,CAAC;IACtC,MAAM,cAAc,GAAG,QAAoC,CAAC;IAC5D,KAAK,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC9C,IAAI,CAAC,KAAK,CAAC,SAAS;YAAE,SAAS;QAC/B,MAAM,GAAG,GAAG,WAAW,CAAC,QAAQ,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;QAC3D,IAAI,CAAC;YACJ,UAAU,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;QAC/D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,GAAG,CAAC,KAAK,CAAE,GAAa,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;IACF,CAAC;IAED,gCAAgC;IAChC,IAAI,OAAO,GAAG,CAAC,GAAG,gBAAgB,CAAC,CAAC;IACpC,KAAK,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC9C,IAAI,CAAC,KAAK,CAAC,WAAW;YAAE,SAAS;QACjC,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;YACjC,MAAM,GAAG,GAAG,WAAW,CAAC,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;YACvE,IAAI,CAAC;gBACJ,OAAO,KAAK,CAAC,WAAY,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;YAClD,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACd,GAAG,CAAC,KAAK,CAAE,GAAa,CAAC,OAAO,CAAC,CAAC;gBAClC,OAAO,IAAI,CAAC,CAAC,2BAA2B;YACzC,CAAC;QACF,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,oFAAoF;IACpF,sDAAsD;IACtD,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAC3C,GAAG,IAAI;QACP,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,UAAoC;KAC3D,CAAC,CAAC,CAAC;IAEJ,iDAAiD;IACjD,MAAM,WAAW,GAAG,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,CAC7C,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EACjD,CAAC,CACD,CAAC;IAEF,MAAM,KAAK,GAAkB;QAC5B,SAAS,EAAE,KAAK,CAAC,MAAM;QACvB,WAAW;QACX,YAAY,EAAE,QAAQ,CAAC,MAAM,CAC5B,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,KAAK,CAAC,WAAW,CACrE,CAAC,MAAM;KACR,CAAC;IAEF,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAC5D,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAc;IACxC,OAAO;QACN,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG;QACnB,KAAK,EAAG,IAAI,CAAC,WAAW,CAAC,KAA4B,IAAI,EAAE;QAC3D,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,WAAW,EAAE,IAAI,CAAC,WAAsC;QACxD,UAAU,EAAE,IAAI,CAAC,UAAU;KAC3B,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CACnB,QAA2B,EAC3B,KAA+B,EAC/B,UAAkB,EAClB,GAAY;IAEZ,OAAO;QACN,IAAI,CAAC,OAAO,EAAE,OAAO;YACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,IAAI,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;QACtF,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,OAAO;YACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,IAAI,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;QACzF,CAAC;QACD,KAAK,CAAC,OAAO,EAAE,MAAM;YACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,IAAI,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;QACtF,CAAC;KACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"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"}