@refrakt-md/content 0.14.3 → 0.15.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,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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"}
@@ -0,0 +1,27 @@
1
+ import type { PipelineWarning } from '@refrakt-md/types';
2
+ import type { PipelineStats } from './pipeline.js';
3
+ /**
4
+ * Format the cross-page pipeline's Phase 1/2/3/4 stats and the collected
5
+ * warnings as a single multi-line string suitable for writing to stderr.
6
+ *
7
+ * Adapters call this after `loadContent` (or equivalent) completes so users
8
+ * see the same build summary across every framework — currently the
9
+ * SvelteKit plugin prints this; other adapters were silent.
10
+ *
11
+ * The function is pure — caller decides where to write. Empty / minimal
12
+ * builds produce a single status line; builds with warnings interleave
13
+ * each warning on its own line.
14
+ *
15
+ * ```
16
+ * Phase 1: Parse 162 pages
17
+ * Phase 2: Register 847 entities
18
+ * Phase 3: Aggregate 14 packages
19
+ * Phase 4: Post-process 162 pages
20
+ *
21
+ * ⚠ warn some message /some/url
22
+ *
23
+ * ✓ Build complete (0 errors, 1 warning)
24
+ * ```
25
+ */
26
+ export declare function formatPipelineSummary(stats: PipelineStats, warnings: PipelineWarning[]): string;
27
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAUnD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CACpC,KAAK,EAAE,aAAa,EACpB,QAAQ,EAAE,eAAe,EAAE,GACzB,MAAM,CAyBR"}
package/dist/format.js ADDED
@@ -0,0 +1,50 @@
1
+ const ICON = {
2
+ error: '✗ error',
3
+ warning: '⚠ warn ',
4
+ info: 'ℹ info ',
5
+ };
6
+ const pad = (s, n) => s + ' '.repeat(Math.max(0, n - s.length));
7
+ /**
8
+ * Format the cross-page pipeline's Phase 1/2/3/4 stats and the collected
9
+ * warnings as a single multi-line string suitable for writing to stderr.
10
+ *
11
+ * Adapters call this after `loadContent` (or equivalent) completes so users
12
+ * see the same build summary across every framework — currently the
13
+ * SvelteKit plugin prints this; other adapters were silent.
14
+ *
15
+ * The function is pure — caller decides where to write. Empty / minimal
16
+ * builds produce a single status line; builds with warnings interleave
17
+ * each warning on its own line.
18
+ *
19
+ * ```
20
+ * Phase 1: Parse 162 pages
21
+ * Phase 2: Register 847 entities
22
+ * Phase 3: Aggregate 14 packages
23
+ * Phase 4: Post-process 162 pages
24
+ *
25
+ * ⚠ warn some message /some/url
26
+ *
27
+ * ✓ Build complete (0 errors, 1 warning)
28
+ * ```
29
+ */
30
+ export function formatPipelineSummary(stats, warnings) {
31
+ const lines = [];
32
+ lines.push(` ${pad('Phase 1: Parse', 30)} ${stats.pageCount} pages`);
33
+ lines.push(` ${pad('Phase 2: Register', 30)} ${stats.entityCount} entities`);
34
+ lines.push(` ${pad('Phase 3: Aggregate', 30)} ${stats.packageCount} packages`);
35
+ lines.push(` ${pad('Phase 4: Post-process', 30)} ${stats.pageCount} pages`);
36
+ const errorCount = warnings.filter(w => w.severity === 'error').length;
37
+ const warnCount = warnings.filter(w => w.severity === 'warning').length;
38
+ for (const w of warnings) {
39
+ const icon = ICON[w.severity];
40
+ const location = w.url ? ` ${w.url}` : '';
41
+ lines.push('');
42
+ lines.push(` ${icon} ${w.message}${location}`);
43
+ }
44
+ const status = errorCount > 0 ? '✗' : '✓';
45
+ lines.push('');
46
+ lines.push(` ${status} Build complete (${errorCount} error${errorCount !== 1 ? 's' : ''}, ${warnCount} warning${warnCount !== 1 ? 's' : ''})`);
47
+ lines.push('');
48
+ return lines.map(l => l + '\n').join('');
49
+ }
50
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAGA,MAAM,IAAI,GAAG;IACZ,KAAK,EAAE,UAAU;IACjB,OAAO,EAAE,UAAU;IACnB,IAAI,EAAE,UAAU;CACP,CAAC;AAEX,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAEhF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,qBAAqB,CACpC,KAAoB,EACpB,QAA2B;IAE3B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,gBAAgB,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,SAAS,QAAQ,CAAC,CAAC;IACtE,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,mBAAmB,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,WAAW,WAAW,CAAC,CAAC;IAC9E,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,oBAAoB,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,YAAY,WAAW,CAAC,CAAC;IAChF,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,uBAAuB,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,SAAS,QAAQ,CAAC,CAAC;IAE7E,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM,CAAC;IACvE,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;IAExE,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC9B,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3C,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC,OAAO,GAAG,QAAQ,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1C,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CACT,KAAK,MAAM,qBAAqB,UAAU,SAAS,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,WAAW,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CACpI,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC1C,CAAC"}
package/dist/index.d.ts CHANGED
@@ -13,4 +13,6 @@ export { getGitTimestamps, getStatTimestamps, resolveTimestamps, type FileTimest
13
13
  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
+ export { formatPipelineSummary } from './format.js';
17
+ export { readFileRoots, resolveUserFileRoots, mergeFileRoots, validateNamespacedReference, type FileRoots, type MergedFileRoots, } from './file-roots.js';
16
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"}
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
@@ -13,4 +13,6 @@ export { getGitTimestamps, getStatTimestamps, resolveTimestamps } from './timest
13
13
  export { EntityRegistryImpl } from './registry.js';
14
14
  export { createRefraktLoader, createVirtualRefraktLoader, buildHighlightOptions } from './refract-loader.js';
15
15
  export { runPipeline } from './pipeline.js';
16
+ export { formatPipelineSummary } from './format.js';
17
+ export { readFileRoots, resolveUserFileRoots, mergeFileRoots, validateNamespacedReference, } from './file-roots.js';
16
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"}
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;
@@ -11,6 +13,19 @@ export interface SiteLoaderOptions {
11
13
  sandboxExamplesDir?: string;
12
14
  /** Site-wide Markdoc variables available in content via {% $name %} syntax. */
13
15
  variables?: Record<string, unknown>;
16
+ /** Security policy for untrusted author content. Default: `'trusted'`. */
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;
14
29
  /** When true, every load() call re-reads from disk (no caching). Default: false. */
15
30
  dev?: boolean;
16
31
  }
@@ -36,6 +51,13 @@ export interface VirtualSiteLoaderOptions {
36
51
  /** Optional async reader for ad-hoc lookups. Forward-compatibility hook —
37
52
  * see `LoadContentFromTreeOptions.reader` for details. */
38
53
  reader?: VirtualReader;
54
+ /** Absolute path to the project root (where `refrakt.config.json` lives).
55
+ * Used to compute `$file.path` as a project-root-relative POSIX path. */
56
+ projectRoot?: string;
57
+ /** Compiled xref patterns from `refrakt.config.json#/xrefs`. */
58
+ xrefPatterns?: CompiledXrefPattern[];
59
+ /** Registered file roots — namespace → absolute directory path. */
60
+ fileRoots?: FileRoots;
39
61
  /** When true, every load() call re-runs the pipeline against the current
40
62
  * tree (no caching). Use when the host swaps the tree's contents in place. */
41
63
  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,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,CAsBvE;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,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,CA0BvE;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;mFAC+E;IAC/E,GAAG,CAAC,EAAE,OAAO,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,UAAU,CAyBrF"}
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);
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);
9
9
  if (!options.dev)
10
10
  cached = promise;
11
11
  return promise;
@@ -35,6 +35,9 @@ 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,
38
41
  });
39
42
  if (!options.dev)
40
43
  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;AAuB5F,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,CACjB,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;AAqC5F,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,CACjB,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;AA6BD;;;;;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;aAC5B,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,4 +1,5 @@
1
- import type { SiteConfig } from '@refrakt-md/types';
1
+ import type { SiteConfig, SecurityPolicy, XrefPattern } from '@refrakt-md/types';
2
+ import { type FileRoots } from './file-roots.js';
2
3
  import type { ContentTree } from './content-tree.js';
3
4
  import type { Site, VirtualReader } from './site.js';
4
5
  export interface RefraktLoaderOptions {
@@ -9,6 +10,11 @@ export interface RefraktLoaderOptions {
9
10
  site?: string;
10
11
  /** Markdoc variables available in content via {% $name %} syntax. */
11
12
  variables?: Record<string, unknown>;
13
+ /** Security policy for untrusted author content. Defaults to `'trusted'`
14
+ * — no sanitisation. Set to `'strict'` (or a custom `SecurityPolicy` object)
15
+ * when authoring content comes from untrusted sources (hosted product,
16
+ * external editors, etc.). Forwarded to `loadContent`'s `securityPolicy`. */
17
+ security?: SecurityPolicy;
12
18
  /** Skip caching — re-read on every load(). Default: false. */
13
19
  dev?: boolean;
14
20
  }
@@ -54,8 +60,22 @@ export interface VirtualRefraktLoaderOptions {
54
60
  reader?: VirtualReader;
55
61
  /** Markdoc variables available in content via {% $name %} syntax. */
56
62
  variables?: Record<string, unknown>;
63
+ /** Security policy for untrusted author content. Defaults to `'trusted'`. */
64
+ security?: SecurityPolicy;
57
65
  /** URL base path for the Router. Default: `'/'`. */
58
66
  basePath?: string;
67
+ /** Absolute path to the project root (where `refrakt.config.json` lives, or
68
+ * the conceptual root in a virtual environment). Used to compute
69
+ * `$file.path`. When omitted, `$file.path` falls back to the page's
70
+ * content-root-relative path. */
71
+ projectRoot?: string;
72
+ /** Xref patterns to compile and use as URL-resolution fallback. */
73
+ xrefs?: XrefPattern[];
74
+ /** File roots — namespace → absolute directory path. Hosts that have a
75
+ * conceptual project root should resolve their fileRoots config against
76
+ * it before passing the result here (the virtual loader doesn't read a
77
+ * config file). Plugin-declared roots merge in automatically. */
78
+ fileRoots?: FileRoots;
59
79
  /** Skip caching — re-run the pipeline on every load(). Default: false. */
60
80
  dev?: boolean;
61
81
  }
@@ -1 +1 @@
1
- {"version":3,"file":"refract-loader.d.ts","sourceRoot":"","sources":["../src/refract-loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAU,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAS5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAErD,MAAM,WAAW,oBAAoB;IACpC,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;2DACuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,8DAA8D;IAC9D,GAAG,CAAC,EAAE,OAAO,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC7B,wDAAwD;IACxD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,yDAAyD;IACzD,YAAY,IAAI,OAAO,CAAC,CAAC,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC,CAAC;IAC5C,8CAA8C;IAC9C,qBAAqB,IAAI,OAAO,CAAC;QAAE,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpE,uEAAuE;IACvE,cAAc,IAAI,IAAI,CAAC;CACvB;AASD;;;;;sDAKsD;AACtD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,UAAU;;;;;;EAQrD;AAiHD,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,aAAa,CAwDjF;AAED,MAAM,WAAW,2BAA2B;IAC3C;;;;iFAI6E;IAC7E,IAAI,EAAE,UAAU,CAAC;IACjB;uCACmC;IACnC,IAAI,EAAE,WAAW,CAAC;IAClB;iCAC6B;IAC7B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,GAAG,CAAC,EAAE,OAAO,CAAC;CACd;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,2BAA2B,GAAG,aAAa,CAkD9F"}
1
+ {"version":3,"file":"refract-loader.d.ts","sourceRoot":"","sources":["../src/refract-loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAU,UAAU,EAAE,cAAc,EAAiB,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAKxG,OAAO,EAAwC,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAMvF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAErD,MAAM,WAAW,oBAAoB;IACpC,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;2DACuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC;;;kFAG8E;IAC9E,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,8DAA8D;IAC9D,GAAG,CAAC,EAAE,OAAO,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC7B,wDAAwD;IACxD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,yDAAyD;IACzD,YAAY,IAAI,OAAO,CAAC,CAAC,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC,CAAC;IAC5C,8CAA8C;IAC9C,qBAAqB,IAAI,OAAO,CAAC;QAAE,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpE,uEAAuE;IACvE,cAAc,IAAI,IAAI,CAAC;CACvB;AA6BD;;;;;sDAKsD;AACtD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,UAAU;;;;;;EAQrD;AAoHD,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,aAAa,CAmGjF;AAED,MAAM,WAAW,2BAA2B;IAC3C;;;;iFAI6E;IAC7E,IAAI,EAAE,UAAU,CAAC;IACjB;uCACmC;IACnC,IAAI,EAAE,WAAW,CAAC;IAClB;iCAC6B;IAC7B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;sCAGkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;IACtB;;;sEAGkE;IAClE,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,0EAA0E;IAC1E,GAAG,CAAC,EAAE,OAAO,CAAC;CACd;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,2BAA2B,GAAG,aAAa,CAkF9F"}
@@ -2,7 +2,23 @@ import { readFileSync } from 'node:fs';
2
2
  import { dirname, resolve } from 'node:path';
3
3
  import { getThemePackage } from '@refrakt-md/types';
4
4
  import { normalizeRefraktConfig, resolveSite, loadPresets } from '@refrakt-md/transform/node';
5
+ import { compileXrefPatterns } from '@refrakt-md/runes';
6
+ import { mergeFileRoots, resolveUserFileRoots } from './file-roots.js';
5
7
  import { createSiteLoader, createVirtualSiteLoader, } from './loader.js';
8
+ /** Compile xref patterns from a raw config, logging any diagnostics to
9
+ * stderr so the build surface remains visible. Errors don't throw — they
10
+ * produce a permissively-empty pattern set so the rest of the load
11
+ * succeeds and the user can fix the config without losing the whole site. */
12
+ function compileConfiguredXrefPatterns(patterns) {
13
+ const result = compileXrefPatterns(patterns);
14
+ for (const warning of result.warnings) {
15
+ process.stderr.write(`refrakt: xref pattern warning — ${warning}\n`);
16
+ }
17
+ for (const error of result.errors) {
18
+ process.stderr.write(`refrakt: xref pattern error — ${error}\n`);
19
+ }
20
+ return result.patterns;
21
+ }
6
22
  /** Compose the options bag handed to `createHighlightTransform`. Merges the
7
23
  * site's `highlight.*` block with theme-level code settings (`theme.code.*`)
8
24
  * so a single object reaches the transform — keeps adapter call sites tidy
@@ -83,6 +99,7 @@ async function assembleSiteContext(site, opts = {}) {
83
99
  communityTags: undefined,
84
100
  communityPackages: undefined,
85
101
  icons,
102
+ pluginFileRoots: {},
86
103
  };
87
104
  }
88
105
  const { config: assembledConfig } = assembleThemeConfig({
@@ -95,6 +112,7 @@ async function assembleSiteContext(site, opts = {}) {
95
112
  communityTags: undefined,
96
113
  communityPackages: undefined,
97
114
  icons,
115
+ pluginFileRoots: {},
98
116
  };
99
117
  }
100
118
  const { loadPlugin, mergePlugins, runes: coreRunes } = await import('@refrakt-md/runes');
@@ -116,6 +134,7 @@ async function assembleSiteContext(site, opts = {}) {
116
134
  communityTags: Object.keys(merged.tags).length > 0 ? merged.tags : undefined,
117
135
  communityPackages: merged.plugins,
118
136
  icons,
137
+ pluginFileRoots: merged.fileRoots,
119
138
  };
120
139
  }
121
140
  export function createRefraktLoader(options) {
@@ -127,6 +146,15 @@ export function createRefraktLoader(options) {
127
146
  const normalized = normalizeRefraktConfig(rawConfig, { configDir });
128
147
  const { site } = resolveSite(normalized, options?.site);
129
148
  const contentDir = resolve(site.contentDir);
149
+ // Compile xref patterns once at loader construction. Diagnostics
150
+ // (invalid regex, unknown placeholders, etc.) are surfaced via
151
+ // stderr — adapters can intercept via their own pipeline-warnings
152
+ // formatter once SPEC-058 wiring is fully in place.
153
+ const xrefPatterns = compileConfiguredXrefPatterns(rawConfig.xrefs);
154
+ // Resolve user-config file roots against the config-file directory.
155
+ // Plugin-contributed roots are merged in at init time (after plugins
156
+ // load); user roots win any namespace collision (warning surfaced).
157
+ const userFileRoots = resolveUserFileRoots(rawConfig.fileRoots, configDir);
130
158
  let _initPromise = null;
131
159
  let _transform = null;
132
160
  let _loader = null;
@@ -137,6 +165,30 @@ export function createRefraktLoader(options) {
137
165
  _initPromise = (async () => {
138
166
  const ctx = await assembleSiteContext(site, { configDir });
139
167
  _transform = ctx.transform;
168
+ // Run each plugin's `configure` lifecycle hook before any pipeline
169
+ // phases. Plugins that need build-time config (e.g. plan reading
170
+ // plan.dir for its unconditional-scan path) wire it up here. The
171
+ // `registerFileRoot` callback lets plugins dynamically register
172
+ // file-root namespaces whose paths depend on user config (the
173
+ // plan plugin uses this to expose the user's plan.dir as `plan:`).
174
+ const dynamicFileRoots = {};
175
+ const registerFileRoot = (namespace, absolutePath) => {
176
+ dynamicFileRoots[namespace] = absolutePath;
177
+ };
178
+ for (const pkg of ctx.communityPackages ?? []) {
179
+ if (pkg.pipeline?.configure) {
180
+ await pkg.pipeline.configure({
181
+ config: rawConfig,
182
+ configDir,
183
+ registerFileRoot,
184
+ });
185
+ }
186
+ }
187
+ const pluginRoots = { ...ctx.pluginFileRoots, ...dynamicFileRoots };
188
+ const { roots: fileRoots, warnings: fileRootWarnings } = mergeFileRoots(userFileRoots, pluginRoots);
189
+ for (const warning of fileRootWarnings) {
190
+ process.stderr.write(`refrakt: ${warning}\n`);
191
+ }
140
192
  _loader = createSiteLoader({
141
193
  dirPath: contentDir,
142
194
  basePath: '/',
@@ -144,6 +196,10 @@ export function createRefraktLoader(options) {
144
196
  additionalTags: ctx.communityTags,
145
197
  plugins: ctx.communityPackages,
146
198
  variables: options?.variables,
199
+ securityPolicy: options?.security,
200
+ projectRoot: configDir,
201
+ xrefPatterns,
202
+ fileRoots: Object.keys(fileRoots).length > 0 ? fileRoots : undefined,
147
203
  dev: options?.dev ?? false,
148
204
  });
149
205
  })();
@@ -187,7 +243,9 @@ export function createRefraktLoader(options) {
187
243
  * dependencies in the host environment.
188
244
  */
189
245
  export function createVirtualRefraktLoader(options) {
190
- const { site, tree, reader, variables, basePath, dev } = options;
246
+ const { site, tree, reader, variables, security, basePath, projectRoot, xrefs, fileRoots: userFileRootsOption, dev } = options;
247
+ const xrefPatterns = compileConfiguredXrefPatterns(xrefs);
248
+ const userFileRoots = userFileRootsOption ?? {};
191
249
  let _initPromise = null;
192
250
  let _transform = null;
193
251
  let _loader = null;
@@ -198,6 +256,27 @@ export function createVirtualRefraktLoader(options) {
198
256
  _initPromise = (async () => {
199
257
  const ctx = await assembleSiteContext(site);
200
258
  _transform = ctx.transform;
259
+ // Plugin `configure` lifecycle — same as in createRefraktLoader.
260
+ // Virtual hosts pass the pre-resolved SiteConfig directly; plugins
261
+ // see the per-site config object rather than a full RefraktConfig.
262
+ const dynamicFileRoots = {};
263
+ const registerFileRoot = (namespace, absolutePath) => {
264
+ dynamicFileRoots[namespace] = absolutePath;
265
+ };
266
+ for (const pkg of ctx.communityPackages ?? []) {
267
+ if (pkg.pipeline?.configure) {
268
+ await pkg.pipeline.configure({
269
+ config: site,
270
+ configDir: projectRoot ?? '',
271
+ registerFileRoot,
272
+ });
273
+ }
274
+ }
275
+ const pluginRoots = { ...ctx.pluginFileRoots, ...dynamicFileRoots };
276
+ const { roots: fileRoots, warnings: fileRootWarnings } = mergeFileRoots(userFileRoots, pluginRoots);
277
+ for (const warning of fileRootWarnings) {
278
+ process.stderr.write(`refrakt: ${warning}\n`);
279
+ }
201
280
  _loader = createVirtualSiteLoader({
202
281
  tree,
203
282
  basePath: basePath ?? '/',
@@ -205,7 +284,11 @@ export function createVirtualRefraktLoader(options) {
205
284
  additionalTags: ctx.communityTags,
206
285
  plugins: ctx.communityPackages,
207
286
  variables,
287
+ securityPolicy: security,
208
288
  reader,
289
+ projectRoot,
290
+ xrefPatterns,
291
+ fileRoots: Object.keys(fileRoots).length > 0 ? fileRoots : undefined,
209
292
  dev: dev ?? false,
210
293
  });
211
294
  })();