create-refrakt 0.14.2 → 0.14.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/template-astro/src/pages/[...slug].astro +2 -2
- package/template-astro/src/setup.ts +43 -77
- package/template-eleventy/eleventy.config.js +15 -1
- package/template-eleventy/src/_data/refrakt.js +15 -1
- package/template-eleventy/src/_includes/base.njk +1 -0
- package/template-html/build.ts +96 -18
- package/template-next/app/[...slug]/page.tsx +19 -8
- package/template-next/app/layout.tsx +13 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-refrakt",
|
|
3
3
|
"description": "Scaffold a new refrakt.md project",
|
|
4
|
-
"version": "0.14.
|
|
4
|
+
"version": "0.14.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@clack/prompts": "^1.2.0",
|
|
33
|
-
"@refrakt-md/plan": "0.14.
|
|
34
|
-
"@refrakt-md/runes": "0.14.
|
|
35
|
-
"@refrakt-md/transform": "0.14.
|
|
33
|
+
"@refrakt-md/plan": "0.14.4",
|
|
34
|
+
"@refrakt-md/runes": "0.14.4",
|
|
35
|
+
"@refrakt-md/transform": "0.14.4"
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { getTransform, getSite, getTheme, getHighlightTransform } from '../setup';
|
|
2
|
+
import { getTransform, getSite, getTheme, getHighlightTransform, seoSiteFields } from '../setup';
|
|
3
3
|
import { renderPage, buildSeoHead } from '@refrakt-md/astro';
|
|
4
4
|
import type { RendererNode } from '@refrakt-md/types';
|
|
5
5
|
|
|
@@ -51,7 +51,7 @@ export async function getStaticPaths() {
|
|
|
51
51
|
const { page, seo, highlightCss } = Astro.props;
|
|
52
52
|
const theme = await getTheme();
|
|
53
53
|
const html = renderPage({ theme, page });
|
|
54
|
-
const head = buildSeoHead({ title: page.title, frontmatter: page.frontmatter, seo });
|
|
54
|
+
const head = buildSeoHead({ title: page.title, frontmatter: page.frontmatter, seo, ...seoSiteFields });
|
|
55
55
|
const needsBehaviors = html.includes('data-layout-behaviors') || html.includes('data-rune=');
|
|
56
56
|
const contextData = JSON.stringify({ pages: page.pages, currentUrl: page.url });
|
|
57
57
|
---
|
|
@@ -1,91 +1,57 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { assembleThemeConfig, createTransform } from '@refrakt-md/transform';
|
|
1
|
+
import { createRefraktLoader } from '@refrakt-md/content';
|
|
3
2
|
import { loadRefraktConfig, resolveSite } from '@refrakt-md/transform/node';
|
|
4
|
-
import { loadPlugin, mergePlugins, runes as coreRunes } from '@refrakt-md/runes';
|
|
5
3
|
import { getThemePackage } from '@refrakt-md/types';
|
|
6
|
-
import type { Schema } from '@markdoc/markdoc';
|
|
7
4
|
import { readFileSync } from 'node:fs';
|
|
8
|
-
import
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
9
7
|
|
|
10
|
-
const
|
|
8
|
+
const configPath = resolve('refrakt.config.json');
|
|
9
|
+
const config = loadRefraktConfig(configPath);
|
|
11
10
|
const { site } = resolveSite(config);
|
|
12
|
-
const themePackage = getThemePackage(site.theme);
|
|
13
|
-
const contentDir = path.resolve(site.contentDir);
|
|
14
11
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
const loader = createRefraktLoader({ configPath });
|
|
13
|
+
|
|
14
|
+
export const getTransform = () => loader.getTransform();
|
|
15
|
+
export const getHighlightTransform = () => loader.getHighlightTransform();
|
|
16
|
+
export const getSite = () => loader.getSite();
|
|
17
|
+
|
|
18
|
+
/** Site-level SEO fields surfaced for `buildSeoHead`. Read once from refrakt.config.json. */
|
|
19
|
+
export const seoSiteFields = {
|
|
20
|
+
siteName: site.siteName,
|
|
21
|
+
baseUrl: site.baseUrl,
|
|
22
|
+
defaultImage: site.defaultImage,
|
|
23
|
+
logo: site.logo,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Build the AstroTheme `{ manifest, layouts }` shape for `renderPage`.
|
|
28
|
+
*
|
|
29
|
+
* Loads the theme package's manifest + layouts and bakes site-level fields
|
|
30
|
+
* (`routeRules`, `siteName`, `baseUrl`, `defaultImage`, `logo`) into the
|
|
31
|
+
* manifest so the SEO and route-resolution paths have what they need.
|
|
32
|
+
*
|
|
33
|
+
* Memoised — the theme is resolved once per process.
|
|
34
|
+
*/
|
|
19
35
|
let _theme: { manifest: any; layouts: any } | null = null;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
async function init() {
|
|
24
|
-
if (_transform) return;
|
|
36
|
+
export async function getTheme() {
|
|
37
|
+
if (_theme) return _theme;
|
|
25
38
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
import(themePackage + '/layouts'),
|
|
29
|
-
]);
|
|
39
|
+
const themePackage = getThemePackage(site.theme);
|
|
40
|
+
const layouts = (await import(themePackage + '/layouts')).layouts;
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
const { createRequire: cr } = await import('node:module');
|
|
33
|
-
const manifestPath = cr(import.meta.url).resolve(themePackage + '/manifest');
|
|
42
|
+
const manifestPath = createRequire(import.meta.url).resolve(themePackage + '/manifest');
|
|
34
43
|
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
35
44
|
|
|
36
45
|
_theme = {
|
|
37
|
-
manifest: {
|
|
38
|
-
|
|
46
|
+
manifest: {
|
|
47
|
+
...manifest,
|
|
48
|
+
routeRules: site.routeRules ?? [{ pattern: '**', layout: 'default' }],
|
|
49
|
+
...(site.siteName && { siteName: site.siteName }),
|
|
50
|
+
...(site.baseUrl && { baseUrl: site.baseUrl }),
|
|
51
|
+
...(site.defaultImage && { defaultImage: site.defaultImage }),
|
|
52
|
+
...(site.logo && { logo: site.logo }),
|
|
53
|
+
},
|
|
54
|
+
layouts,
|
|
39
55
|
};
|
|
40
|
-
|
|
41
|
-
const themeConfig = themeModule.themeConfig ?? themeModule.luminaConfig ?? themeModule.default;
|
|
42
|
-
|
|
43
|
-
let transformConfig = themeConfig;
|
|
44
|
-
|
|
45
|
-
const pluginNames = site.plugins ?? config.plugins ?? [];
|
|
46
|
-
if (pluginNames.length > 0) {
|
|
47
|
-
const loaded = await Promise.all(
|
|
48
|
-
pluginNames.map((name: string) => loadPlugin(name))
|
|
49
|
-
);
|
|
50
|
-
const coreRuneNames = new Set(Object.keys(coreRunes));
|
|
51
|
-
const merged = mergePlugins(loaded, coreRuneNames, site.runes?.prefer);
|
|
52
|
-
|
|
53
|
-
_communityTags = Object.keys(merged.tags).length > 0 ? merged.tags : undefined;
|
|
54
|
-
_packages = loaded.map((l: any) => l.pkg);
|
|
55
|
-
|
|
56
|
-
const { config: assembledConfig } = assembleThemeConfig({
|
|
57
|
-
coreConfig: themeConfig,
|
|
58
|
-
pluginRunes: merged.themeRunes,
|
|
59
|
-
pluginIcons: merged.themeIcons,
|
|
60
|
-
pluginBackgrounds: merged.themeBackgrounds,
|
|
61
|
-
extensions: merged.extensions as any,
|
|
62
|
-
provenance: merged.provenance,
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
transformConfig = assembledConfig;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
_transform = createTransform(transformConfig);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export async function getTransform() {
|
|
72
|
-
await init();
|
|
73
|
-
return _transform!;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export async function getTheme() {
|
|
77
|
-
await init();
|
|
78
|
-
return _theme!;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export async function getHighlightTransform() {
|
|
82
|
-
if (_hl) return _hl;
|
|
83
|
-
const { createHighlightTransform } = await import('@refrakt-md/highlight');
|
|
84
|
-
_hl = await createHighlightTransform(buildHighlightOptions(site));
|
|
85
|
-
return _hl;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export async function getSite() {
|
|
89
|
-
await init();
|
|
90
|
-
return loadContent(contentDir, '/', {}, _communityTags, _packages);
|
|
56
|
+
return _theme;
|
|
91
57
|
}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import { refraktPlugin } from '@refrakt-md/eleventy';
|
|
1
|
+
import { refraktPlugin, writeSiteTokensCss } from '@refrakt-md/eleventy';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
// Compose site-level token overrides (SPEC-048 presets + tokens + modes,
|
|
5
|
+
// SPEC-056 scoped tint projections) once at config-load time and write the
|
|
6
|
+
// CSS to a build-input directory. Eleventy's passthrough copy picks it up
|
|
7
|
+
// and ships it as `/css/site-tokens.css`.
|
|
8
|
+
await writeSiteTokensCss(
|
|
9
|
+
resolve('refrakt.config.json'),
|
|
10
|
+
resolve('src/_generated/site-tokens.css'),
|
|
11
|
+
);
|
|
2
12
|
|
|
3
13
|
export default function (eleventyConfig) {
|
|
4
14
|
eleventyConfig.addPlugin(refraktPlugin, {
|
|
@@ -6,11 +16,15 @@ export default function (eleventyConfig) {
|
|
|
6
16
|
cssPrefix: '/css',
|
|
7
17
|
behaviorFile: 'node_modules/@refrakt-md/behaviors/dist/index.js',
|
|
8
18
|
jsPrefix: '/js',
|
|
19
|
+
// Watch content (and any sandbox examples) for --serve mode so edits
|
|
20
|
+
// trigger a rebuild + browser reload.
|
|
21
|
+
contentDir: resolve('content'),
|
|
9
22
|
});
|
|
10
23
|
|
|
11
24
|
eleventyConfig.addPassthroughCopy({
|
|
12
25
|
'node_modules/@refrakt-md/lumina/tokens': '/css/tokens',
|
|
13
26
|
'node_modules/@refrakt-md/lumina/styles': '/css/styles',
|
|
27
|
+
'src/_generated/site-tokens.css': '/css/site-tokens.css',
|
|
14
28
|
});
|
|
15
29
|
|
|
16
30
|
return {
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { createDataFile } from '@refrakt-md/eleventy';
|
|
2
|
+
import { loadRefraktConfig, resolveSite } from '@refrakt-md/transform/node';
|
|
2
3
|
import manifest from '@refrakt-md/lumina/manifest';
|
|
3
4
|
import { layouts } from '@refrakt-md/lumina/layouts';
|
|
5
|
+
import { resolve } from 'node:path';
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
const config = loadRefraktConfig(resolve('refrakt.config.json'));
|
|
8
|
+
const { site } = resolveSite(config);
|
|
9
|
+
|
|
10
|
+
export default createDataFile({
|
|
11
|
+
theme: { manifest, layouts },
|
|
12
|
+
contentDir: site.contentDir,
|
|
13
|
+
seo: {
|
|
14
|
+
siteName: site.siteName,
|
|
15
|
+
baseUrl: site.baseUrl,
|
|
16
|
+
defaultImage: site.defaultImage,
|
|
17
|
+
logo: site.logo,
|
|
18
|
+
},
|
|
19
|
+
});
|
package/template-html/build.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
loadContent,
|
|
3
|
+
buildHighlightOptions,
|
|
4
|
+
analyzeRuneUsage,
|
|
5
|
+
formatPipelineSummary,
|
|
6
|
+
} from '@refrakt-md/content';
|
|
7
|
+
import {
|
|
8
|
+
renderFullPage,
|
|
9
|
+
composeSiteTokensCss,
|
|
10
|
+
computeUsedCssBlocks,
|
|
11
|
+
buildUsedCssImports,
|
|
12
|
+
} from '@refrakt-md/html';
|
|
3
13
|
import type { HtmlTheme } from '@refrakt-md/html';
|
|
4
14
|
import { assembleThemeConfig, createTransform, defaultLayout } from '@refrakt-md/transform';
|
|
5
15
|
import { loadRefraktConfig, resolveSite } from '@refrakt-md/transform/node';
|
|
@@ -9,13 +19,16 @@ import { getThemePackage } from '@refrakt-md/types';
|
|
|
9
19
|
import type { RendererNode } from '@refrakt-md/types';
|
|
10
20
|
import type { Schema } from '@markdoc/markdoc';
|
|
11
21
|
import { mkdirSync, writeFileSync, cpSync, existsSync } from 'node:fs';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
12
23
|
import * as path from 'node:path';
|
|
13
24
|
|
|
14
25
|
// --- Configuration -------------------------------------------------------
|
|
15
26
|
|
|
16
|
-
const
|
|
27
|
+
const configPath = path.resolve('refrakt.config.json');
|
|
28
|
+
const config = loadRefraktConfig(configPath);
|
|
17
29
|
const { site } = resolveSite(config);
|
|
18
30
|
const contentDir = path.resolve(site.contentDir);
|
|
31
|
+
const configDir = path.dirname(configPath);
|
|
19
32
|
const outDir = 'build';
|
|
20
33
|
|
|
21
34
|
// --- Helpers --------------------------------------------------------------
|
|
@@ -88,6 +101,10 @@ async function build() {
|
|
|
88
101
|
// Create highlight transform
|
|
89
102
|
const hl = await createHighlightTransform(buildHighlightOptions(site));
|
|
90
103
|
|
|
104
|
+
// Compose site-level token overrides CSS (SPEC-048 + SPEC-056).
|
|
105
|
+
// Empty string when the site has no overrides; safe to inline either way.
|
|
106
|
+
const siteTokensCss = await composeSiteTokensCss(site, configDir);
|
|
107
|
+
|
|
91
108
|
// Build theme object for HTML adapter
|
|
92
109
|
const themeManifestModule = await import(themePackage + '/manifest', { with: { type: 'json' } });
|
|
93
110
|
const manifest = themeManifestModule.default;
|
|
@@ -102,13 +119,55 @@ async function build() {
|
|
|
102
119
|
},
|
|
103
120
|
};
|
|
104
121
|
|
|
105
|
-
// Load content
|
|
106
|
-
|
|
122
|
+
// Load content. The HTML adapter's build script doesn't surface
|
|
123
|
+
// `security` / `variables` via a CLI flag — for hosted-product use, edit
|
|
124
|
+
// this file to pass them through to `loadContent` (matches the option
|
|
125
|
+
// shape `createRefraktLoader` accepts for the Vite-based adapters).
|
|
126
|
+
const loadedSite = await loadContent(contentDir, '/', icons, communityTags);
|
|
127
|
+
|
|
128
|
+
// Print the standard Phase 1/2/3/4 + warnings summary so the HTML build
|
|
129
|
+
// gets the same visibility into the cross-page pipeline that the SvelteKit
|
|
130
|
+
// reference adapter prints.
|
|
131
|
+
process.stderr.write(
|
|
132
|
+
formatPipelineSummary(loadedSite.pipelineStats, loadedSite.pipelineWarnings),
|
|
133
|
+
);
|
|
107
134
|
|
|
108
135
|
mkdirSync(outDir, { recursive: true });
|
|
109
136
|
|
|
137
|
+
// Tree-shake per-rune CSS: only ship blocks that actually appear in the
|
|
138
|
+
// page corpus. Falls back to the theme barrel if analysis fails.
|
|
139
|
+
const usageReport = analyzeRuneUsage(loadedSite.pages);
|
|
140
|
+
let stylesheets: string[];
|
|
141
|
+
let blocksToCopy: { src: string; dest: string }[] = [];
|
|
142
|
+
try {
|
|
143
|
+
const { usedBlocks, stylesDir } = await computeUsedCssBlocks(
|
|
144
|
+
usageReport.allTypes,
|
|
145
|
+
finalConfig,
|
|
146
|
+
themePackage,
|
|
147
|
+
);
|
|
148
|
+
const themeEntryUrl = import.meta.resolve(themePackage);
|
|
149
|
+
const themeDir = path.dirname(fileURLToPath(themeEntryUrl));
|
|
150
|
+
stylesheets = ['/base.css'];
|
|
151
|
+
blocksToCopy.push({
|
|
152
|
+
src: path.join(themeDir, 'base.css'),
|
|
153
|
+
dest: path.join(outDir, 'base.css'),
|
|
154
|
+
});
|
|
155
|
+
for (const block of [...usedBlocks].sort()) {
|
|
156
|
+
stylesheets.push(`/styles/runes/${block}.css`);
|
|
157
|
+
blocksToCopy.push({
|
|
158
|
+
src: path.join(stylesDir, `${block}.css`),
|
|
159
|
+
dest: path.join(outDir, 'styles', 'runes', `${block}.css`),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
} catch (err) {
|
|
163
|
+
console.warn(
|
|
164
|
+
`Tree-shaking skipped (${(err as Error).message}); shipping full theme barrel.`,
|
|
165
|
+
);
|
|
166
|
+
stylesheets = ['/styles.css'];
|
|
167
|
+
}
|
|
168
|
+
|
|
110
169
|
// Collect page metadata for navigation
|
|
111
|
-
const pages =
|
|
170
|
+
const pages = loadedSite.pages
|
|
112
171
|
.filter(p => !p.route.draft)
|
|
113
172
|
.map(p => ({
|
|
114
173
|
url: p.route.url,
|
|
@@ -118,7 +177,7 @@ async function build() {
|
|
|
118
177
|
|
|
119
178
|
let count = 0;
|
|
120
179
|
|
|
121
|
-
for (const page of
|
|
180
|
+
for (const page of loadedSite.pages) {
|
|
122
181
|
if (page.route.draft) continue;
|
|
123
182
|
|
|
124
183
|
// Serialize → identity transform → highlight
|
|
@@ -147,9 +206,17 @@ async function build() {
|
|
|
147
206
|
},
|
|
148
207
|
},
|
|
149
208
|
{
|
|
150
|
-
stylesheets
|
|
151
|
-
|
|
209
|
+
stylesheets,
|
|
210
|
+
// Order matters: highlight CSS first, site-tokens CSS second so
|
|
211
|
+
// site-level `--rf-*` overrides resolve last in the cascade.
|
|
212
|
+
headExtra:
|
|
213
|
+
(hl.css ? `<style>${hl.css}</style>` : '') +
|
|
214
|
+
(siteTokensCss ? `<style>${siteTokensCss}</style>` : ''),
|
|
152
215
|
seo: page.seo,
|
|
216
|
+
baseUrl: site.baseUrl,
|
|
217
|
+
siteName: site.siteName,
|
|
218
|
+
defaultImage: site.defaultImage,
|
|
219
|
+
logo: site.logo,
|
|
153
220
|
},
|
|
154
221
|
);
|
|
155
222
|
|
|
@@ -163,16 +230,27 @@ async function build() {
|
|
|
163
230
|
count++;
|
|
164
231
|
}
|
|
165
232
|
|
|
166
|
-
// Copy
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
233
|
+
// Copy the per-rune CSS files (or the theme barrel as fallback)
|
|
234
|
+
if (blocksToCopy.length > 0) {
|
|
235
|
+
for (const { src, dest } of blocksToCopy) {
|
|
236
|
+
if (existsSync(src)) {
|
|
237
|
+
mkdirSync(path.dirname(dest), { recursive: true });
|
|
238
|
+
cpSync(src, dest);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
try {
|
|
243
|
+
const themePkg = themePackage;
|
|
244
|
+
const themeDir = path.dirname(require.resolve(themePkg + '/package.json'));
|
|
245
|
+
const cssPath = path.join(themeDir, 'index.css');
|
|
246
|
+
if (existsSync(cssPath)) {
|
|
247
|
+
cpSync(cssPath, path.join(outDir, 'styles.css'));
|
|
248
|
+
}
|
|
249
|
+
} catch {
|
|
250
|
+
console.warn(
|
|
251
|
+
'Warning: Could not copy theme CSS. Add a styles.css to the build directory manually.',
|
|
252
|
+
);
|
|
173
253
|
}
|
|
174
|
-
} catch {
|
|
175
|
-
console.warn('Warning: Could not copy theme CSS. Add a styles.css to the build directory manually.');
|
|
176
254
|
}
|
|
177
255
|
|
|
178
256
|
console.log(`Built ${count} pages to ${outDir}/`);
|
|
@@ -1,25 +1,35 @@
|
|
|
1
1
|
import { loadContent } from '@refrakt-md/content';
|
|
2
2
|
import { assembleThemeConfig, createTransform } from '@refrakt-md/transform';
|
|
3
|
+
import { loadRefraktConfig, resolveSite } from '@refrakt-md/transform/node';
|
|
3
4
|
import { loadPlugin, mergePlugins, runes as coreRunes } from '@refrakt-md/runes';
|
|
5
|
+
import { getThemePackage } from '@refrakt-md/types';
|
|
4
6
|
import manifest from '@refrakt-md/lumina/manifest';
|
|
5
7
|
import { layouts } from '@refrakt-md/lumina/layouts';
|
|
6
8
|
const theme = { manifest, layouts };
|
|
7
|
-
import { RefraktContent, buildMetadata, buildUrlFromParams, hasInteractiveRunes } from '@refrakt-md/next';
|
|
9
|
+
import { RefraktContent, buildMetadata, buildJsonLd, buildUrlFromParams, hasInteractiveRunes } from '@refrakt-md/next';
|
|
8
10
|
import { BehaviorInit } from '@refrakt-md/next/client';
|
|
9
11
|
import type { RendererNode } from '@refrakt-md/types';
|
|
10
|
-
import type { RefraktConfig } from '@refrakt-md/types';
|
|
11
12
|
import type { Schema } from '@markdoc/markdoc';
|
|
12
|
-
import { readFileSync } from 'node:fs';
|
|
13
13
|
import * as path from 'node:path';
|
|
14
14
|
|
|
15
|
-
const config
|
|
16
|
-
const
|
|
15
|
+
const config = loadRefraktConfig(path.resolve('refrakt.config.json'));
|
|
16
|
+
const { site } = resolveSite(config);
|
|
17
|
+
const contentDir = path.resolve(site.contentDir);
|
|
18
|
+
|
|
19
|
+
// Site-level SEO fields surfaced into buildMetadata + buildJsonLd.
|
|
20
|
+
const seoSite = {
|
|
21
|
+
siteName: site.siteName,
|
|
22
|
+
baseUrl: site.baseUrl,
|
|
23
|
+
defaultImage: site.defaultImage,
|
|
24
|
+
logo: site.logo,
|
|
25
|
+
};
|
|
17
26
|
|
|
18
27
|
async function getTransformAndTags() {
|
|
19
|
-
const
|
|
28
|
+
const themePackage = getThemePackage(site.theme);
|
|
29
|
+
const themeModule = await import(themePackage + '/transform');
|
|
20
30
|
const themeConfig = themeModule.themeConfig ?? themeModule.luminaConfig ?? themeModule.default;
|
|
21
31
|
|
|
22
|
-
const pluginNames =
|
|
32
|
+
const pluginNames = site.plugins ?? [];
|
|
23
33
|
if (pluginNames.length === 0) {
|
|
24
34
|
return { transform: createTransform(themeConfig), communityTags: undefined };
|
|
25
35
|
}
|
|
@@ -28,7 +38,7 @@ async function getTransformAndTags() {
|
|
|
28
38
|
pluginNames.map((name: string) => loadPlugin(name))
|
|
29
39
|
);
|
|
30
40
|
const coreRuneNames = new Set(Object.keys(coreRunes));
|
|
31
|
-
const merged = mergePlugins(loaded, coreRuneNames,
|
|
41
|
+
const merged = mergePlugins(loaded, coreRuneNames, site.runes?.prefer);
|
|
32
42
|
|
|
33
43
|
const communityTags: Record<string, Schema> | undefined =
|
|
34
44
|
Object.keys(merged.tags).length > 0 ? merged.tags : undefined;
|
|
@@ -73,6 +83,7 @@ export async function generateMetadata({ params }: { params: Promise<{ slug?: st
|
|
|
73
83
|
title: (page.frontmatter.title as string) ?? '',
|
|
74
84
|
frontmatter: page.frontmatter,
|
|
75
85
|
seo: page.seo,
|
|
86
|
+
...seoSite,
|
|
76
87
|
});
|
|
77
88
|
}
|
|
78
89
|
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import '@refrakt-md/lumina';
|
|
2
|
+
import { getSiteTokensCss } from '@refrakt-md/next';
|
|
2
3
|
import type { ReactNode } from 'react';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
// Site-level token overrides (SPEC-048 + SPEC-056). Computed once per request
|
|
6
|
+
// in the Server Component scope; the resulting CSS layers on top of the
|
|
7
|
+
// theme package's barrel so `--rf-*` overrides resolve last.
|
|
8
|
+
const siteTokensCssPromise = getSiteTokensCss();
|
|
9
|
+
|
|
10
|
+
export default async function RootLayout({ children }: { children: ReactNode }) {
|
|
11
|
+
const siteTokensCss = await siteTokensCssPromise;
|
|
5
12
|
return (
|
|
6
13
|
<html lang="en">
|
|
14
|
+
<head>
|
|
15
|
+
{siteTokensCss && (
|
|
16
|
+
<style dangerouslySetInnerHTML={{ __html: siteTokensCss }} />
|
|
17
|
+
)}
|
|
18
|
+
</head>
|
|
7
19
|
<body>{children}</body>
|
|
8
20
|
</html>
|
|
9
21
|
);
|