meowpress 0.3.0-alpha.0 → 0.3.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -5
- package/dist/src/build.d.ts +8 -0
- package/dist/src/build.js +15 -9
- package/dist/src/fonts.d.ts +21 -0
- package/dist/src/fonts.js +56 -0
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.js +1 -1
- package/dist/src/theme.d.ts +8 -6
- package/dist/src/theme.js +5 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -58,9 +58,17 @@ export default meowSite({
|
|
|
58
58
|
|
|
59
59
|
Directories in the configuration are provided explicitly by the application; the default theme resolves from this package and does not depend on the startup directory.
|
|
60
60
|
The whole theme can be replaced with `theme: defineTheme(...)`; all static pages output that theme's root attributes and default options.
|
|
61
|
-
The build writes `theme.cssText` to a temporary CSS file, then lets Bun handle
|
|
62
|
-
so the theme applies without client-side JavaScript.
|
|
63
|
-
|
|
61
|
+
The build writes `theme.cssText` to a temporary CSS file, then lets Bun handle the other assets, and finally delivers through a stylesheet link,
|
|
62
|
+
so the theme applies without client-side JavaScript.
|
|
63
|
+
|
|
64
|
+
`fonts` lists the web fonts the theme names, as Fontsource variable packages
|
|
65
|
+
(`{ family, package, styles }`, the same shape as Meowtail's `journalFonts`). The build publishes
|
|
66
|
+
each package's weight-axis `woff2` files into `fonts/` with a content hash, puts their `@font-face`
|
|
67
|
+
rules first in the theme stylesheet with each subset's `unicode-range`, and copies each license to
|
|
68
|
+
`licenses/<name>-OFL.txt`. A browser fetches only the files a page's text and families use, and
|
|
69
|
+
nothing for a family the page does not use, so a site with a theme switch pays only for the active
|
|
70
|
+
theme. The default Blog theme defaults to `blogFonts` (Geist); any other theme defaults to none.
|
|
71
|
+
Install the listed packages in the site; nothing depends on an external font service.
|
|
64
72
|
Default illustrations use in-package vectors and CSS and require no business images. The `hero`, `feed` and `about` keys of `site.images`
|
|
65
73
|
can explicitly set decorative image paths, for example `images/cover.svg`; paths are relative to the content root's `assets/`, the library handles each page depth,
|
|
66
74
|
and assets are delivered along the existing asset build flow. When no About image is set, the first letter of the site name is used.
|
|
@@ -75,8 +83,8 @@ After installing `meowpress` and its `meowcss` and `meowjsx` dependencies, run i
|
|
|
75
83
|
bunx --no-install meowpress --config ./site.config.ts
|
|
76
84
|
```
|
|
77
85
|
|
|
78
|
-
The CLI requires Bun, and the output location is determined by `outputDirectory`. Font
|
|
79
|
-
|
|
86
|
+
The CLI requires Bun, and the output location is determined by `outputDirectory`. Font packages resolve from the startup directory first, then from
|
|
87
|
+
this package, which carries the default Geist, so consumers are not required to copy source, install workspace-root dependencies or access an external font service.
|
|
80
88
|
|
|
81
89
|
## Independent verification
|
|
82
90
|
|
package/dist/src/build.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { type ThemeDefinition } from "meowcss";
|
|
2
|
+
import { type SiteFont } from "./fonts.js";
|
|
2
3
|
import { type SiteIdentity } from "./identity.js";
|
|
3
4
|
import { type ContentSourceInput, type MdxComponent } from "./content.js";
|
|
4
5
|
import { type SitePageProvider } from "./pages.js";
|
|
5
6
|
export type { SitePage, SitePageContext, SitePageProvider } from "./pages.js";
|
|
6
7
|
export type { ContentItem } from "./content.js";
|
|
8
|
+
export type { SiteFont } from "./fonts.js";
|
|
7
9
|
export type { SiteIdentity } from "./identity.js";
|
|
8
10
|
export interface SiteConfig {
|
|
9
11
|
/** One or more roots containing `posts/`, `thoughts/`, and optional assets. */
|
|
@@ -24,6 +26,11 @@ export interface SiteConfig {
|
|
|
24
26
|
mdxComponents?: Record<string, MdxComponent>;
|
|
25
27
|
/** Stylesheet file name for the theme; defaults to "blog-theme.css". */
|
|
26
28
|
themeStylesheet?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Web fonts the theme names, published as separate files with their licenses. Defaults to Geist
|
|
31
|
+
* for the default Blog theme and to none for any other theme.
|
|
32
|
+
*/
|
|
33
|
+
fonts?: readonly SiteFont[];
|
|
27
34
|
}
|
|
28
35
|
export interface ResolvedSiteConfig {
|
|
29
36
|
content: readonly ContentSourceInput[];
|
|
@@ -35,6 +42,7 @@ export interface ResolvedSiteConfig {
|
|
|
35
42
|
pages?: SitePageProvider;
|
|
36
43
|
mdxComponents: Record<string, MdxComponent>;
|
|
37
44
|
themeStylesheet: string;
|
|
45
|
+
fonts: readonly SiteFont[];
|
|
38
46
|
}
|
|
39
47
|
/** Define a site config in the app package without taking on the build pipeline. */
|
|
40
48
|
export declare function meowSite(config: SiteConfig): ResolvedSiteConfig;
|
package/dist/src/build.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { removeLegacyIsFallbacks } from "meowcss/bundle";
|
|
2
|
+
import { blogFonts, blogTheme } from "./theme.js";
|
|
3
|
+
import { FONT_EXTERNAL, publishFonts } from "./fonts.js";
|
|
2
4
|
import { DEFAULT_SITE } from "./identity.js";
|
|
3
|
-
import { mkdir, rm, writeFile, cp } from "node:fs/promises";
|
|
5
|
+
import { mkdir, readFile, rm, writeFile, cp } from "node:fs/promises";
|
|
4
6
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { collectContentAssets, isDraftAsset, loadSection, normalizeContentSources, } from "./content.js";
|
|
7
8
|
import { aboutPage, agentMarkdown, DEFAULT_THEME_STYLESHEET, homePage, listPage, llmText, postPage, skillPage, skillsIndexPage, thoughtPage, } from "./site.js";
|
|
8
9
|
import { loadSkills } from "./skills.js";
|
|
@@ -21,6 +22,7 @@ export function meowSite(config) {
|
|
|
21
22
|
pages: config.pages,
|
|
22
23
|
mdxComponents: config.mdxComponents ?? {},
|
|
23
24
|
themeStylesheet: config.themeStylesheet ?? DEFAULT_THEME_STYLESHEET,
|
|
25
|
+
fonts: config.fonts ?? (config.theme === undefined || config.theme === blogTheme ? blogFonts : []),
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
28
|
async function writePage(generatedDirectory, pathname, html) {
|
|
@@ -56,11 +58,17 @@ async function buildStaticHtmlFromEntrypoints(entrypoints, generatedDirectory, o
|
|
|
56
58
|
outdir: outputDirectory,
|
|
57
59
|
target: "browser",
|
|
58
60
|
minify: true,
|
|
61
|
+
// Published font files stay separate, so subsets load by unicode-range instead of inline.
|
|
62
|
+
external: [FONT_EXTERNAL],
|
|
59
63
|
});
|
|
60
64
|
if (!result.success) {
|
|
61
65
|
console.error(result.logs);
|
|
62
66
|
throw new Error("Unable to build site static HTML");
|
|
63
67
|
}
|
|
68
|
+
// Bun writes legacy :-webkit-any()/:-moz-any() copies of every :is() rule; Safari misreads them.
|
|
69
|
+
for (const output of result.outputs)
|
|
70
|
+
if (output.path.endsWith(".css"))
|
|
71
|
+
await writeFile(output.path, removeLegacyIsFallbacks(await readFile(output.path, "utf8")));
|
|
64
72
|
}
|
|
65
73
|
/** Consumer pages are written inside the generated directory and never above it. */
|
|
66
74
|
function consumerPageFile(generatedDirectory, path) {
|
|
@@ -133,14 +141,12 @@ export async function buildSite(input) {
|
|
|
133
141
|
entrypoints.push(await writePage(generatedDirectory, `${skillPath}/index.html`, skillPage(skill, config.theme, config.site, config.themeStylesheet)));
|
|
134
142
|
}
|
|
135
143
|
}
|
|
136
|
-
|
|
144
|
+
// Font faces come first in the one theme stylesheet; the bundled stylesheet sits at the output
|
|
145
|
+
// root, next to the published font directory its rules reference.
|
|
146
|
+
const fontFaces = await publishFonts(config.fonts, config.outputDirectory);
|
|
147
|
+
await writeFile(join(generatedDirectory, config.themeStylesheet), fontFaces ? `${fontFaces}\n${config.theme.cssText}` : config.theme.cssText, "utf8");
|
|
137
148
|
await stageAssets(generatedDirectory, config.content);
|
|
138
149
|
await buildStaticHtmlFromEntrypoints(entrypoints, generatedDirectory, config.outputDirectory);
|
|
139
|
-
if (config.theme.cssText.includes('"@fontsource-variable/geist"')) {
|
|
140
|
-
const licensesDirectory = join(config.outputDirectory, "licenses");
|
|
141
|
-
await mkdir(licensesDirectory, { recursive: true });
|
|
142
|
-
await cp(fileURLToPath(import.meta.resolve("@fontsource-variable/geist/LICENSE")), join(licensesDirectory, "geist-OFL.txt"));
|
|
143
|
-
}
|
|
144
150
|
await copyPostAssets(config.outputDirectory, config.content);
|
|
145
151
|
await Promise.all([
|
|
146
152
|
writeFile(join(config.outputDirectory, "llm.txt"), llmText(posts, thoughts, config.site, pages), "utf8"),
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A web font a site loads, as a Fontsource variable package: `family` is the name the theme's
|
|
3
|
+
* stacks use, `package` the npm package with its weight-axis files, and `styles` which of them to
|
|
4
|
+
* load (`italic` loads `wght-italic.css`). The same shape as Meowtail's `ThemeFont`, so a theme's
|
|
5
|
+
* manifest (such as `journalFonts`) can be passed as it is.
|
|
6
|
+
*/
|
|
7
|
+
export interface SiteFont {
|
|
8
|
+
readonly family: string;
|
|
9
|
+
readonly package: string;
|
|
10
|
+
readonly styles: readonly ("normal" | "italic")[];
|
|
11
|
+
}
|
|
12
|
+
/** Font files are published here, next to the bundled stylesheet at the output root. */
|
|
13
|
+
export declare const FONT_DIRECTORY = "fonts";
|
|
14
|
+
/** The bundler leaves these references alone; the build copies the files itself. */
|
|
15
|
+
export declare const FONT_EXTERNAL = "./fonts/*";
|
|
16
|
+
/**
|
|
17
|
+
* Publish each font's files into `<output>/fonts/` with a content hash and its license into
|
|
18
|
+
* `<output>/licenses/`, and return the `@font-face` rules that reference them. Each subset keeps
|
|
19
|
+
* its `unicode-range`, so a browser fetches only the files a page's text and families use.
|
|
20
|
+
*/
|
|
21
|
+
export declare function publishFonts(fonts: readonly SiteFont[], outputDirectory: string): Promise<string>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile, cp } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
/** Font files are published here, next to the bundled stylesheet at the output root. */
|
|
5
|
+
export const FONT_DIRECTORY = "fonts";
|
|
6
|
+
/** The bundler leaves these references alone; the build copies the files itself. */
|
|
7
|
+
export const FONT_EXTERNAL = `./${FONT_DIRECTORY}/*`;
|
|
8
|
+
/**
|
|
9
|
+
* Resolve a package file from the site first (where a host installs its theme's fonts), then from
|
|
10
|
+
* this package (which carries the default Geist).
|
|
11
|
+
*/
|
|
12
|
+
function resolveFontFile(specifier) {
|
|
13
|
+
for (const from of [process.cwd(), dirname(fileURLToPath(import.meta.url))]) {
|
|
14
|
+
try {
|
|
15
|
+
return Bun.resolveSync(specifier, from);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Try the next root.
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
throw new Error(`Cannot resolve the font file ${specifier}; install its package in the site`);
|
|
22
|
+
}
|
|
23
|
+
const contentHash = (bytes) => new Bun.CryptoHasher("sha256").update(bytes).digest("hex").slice(0, 8);
|
|
24
|
+
/**
|
|
25
|
+
* Publish each font's files into `<output>/fonts/` with a content hash and its license into
|
|
26
|
+
* `<output>/licenses/`, and return the `@font-face` rules that reference them. Each subset keeps
|
|
27
|
+
* its `unicode-range`, so a browser fetches only the files a page's text and families use.
|
|
28
|
+
*/
|
|
29
|
+
export async function publishFonts(fonts, outputDirectory) {
|
|
30
|
+
if (!fonts.length)
|
|
31
|
+
return "";
|
|
32
|
+
const fontDirectory = join(outputDirectory, FONT_DIRECTORY);
|
|
33
|
+
const licenseDirectory = join(outputDirectory, "licenses");
|
|
34
|
+
await mkdir(fontDirectory, { recursive: true });
|
|
35
|
+
await mkdir(licenseDirectory, { recursive: true });
|
|
36
|
+
const rules = [];
|
|
37
|
+
for (const font of fonts) {
|
|
38
|
+
for (const style of font.styles) {
|
|
39
|
+
const sheet = resolveFontFile(`${font.package}/${style === "italic" ? "wght-italic" : "wght"}.css`);
|
|
40
|
+
const source = await readFile(sheet, "utf8");
|
|
41
|
+
const files = new Map();
|
|
42
|
+
for (const [, path] of source.matchAll(/url\(\.\/(files\/[^)]+\.woff2)\)/g)) {
|
|
43
|
+
if (files.has(path))
|
|
44
|
+
continue;
|
|
45
|
+
const bytes = await readFile(join(dirname(sheet), path));
|
|
46
|
+
const name = `${basename(path, ".woff2")}-${contentHash(bytes)}.woff2`;
|
|
47
|
+
await writeFile(join(fontDirectory, name), bytes);
|
|
48
|
+
files.set(path, name);
|
|
49
|
+
}
|
|
50
|
+
rules.push(source.replace(/url\(\.\/(files\/[^)]+\.woff2)\)/g, (_match, path) => `url(./${FONT_DIRECTORY}/${files.get(path)})`));
|
|
51
|
+
}
|
|
52
|
+
const license = resolveFontFile(`${font.package}/LICENSE`);
|
|
53
|
+
await cp(license, join(licenseDirectory, `${font.package.split("/").pop()}-OFL.txt`));
|
|
54
|
+
}
|
|
55
|
+
return rules.join("\n");
|
|
56
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ export { collectContentAssets, collectAssetFiles, isDraftAsset, loadSection, nor
|
|
|
2
2
|
export { buildSite, meowSite, type ResolvedSiteConfig, type SiteConfig } from "./build.js";
|
|
3
3
|
export { aboutPage, agentMarkdown, homePage, listPage, llmText, postPage, skillPage, skillsIndexPage, thoughtPage, } from "./site.js";
|
|
4
4
|
export { DEFAULT_SITE, type SiteAbout, type SiteIdentity, type SiteProfileLink } from "./identity.js";
|
|
5
|
-
export { blogTheme, blogTokens, blogOptions } from "./theme.js";
|
|
5
|
+
export { blogFonts, blogTheme, blogTokens, blogOptions } from "./theme.js";
|
|
6
|
+
export type { SiteFont } from "./fonts.js";
|
|
6
7
|
export { loadSkills, type LoadSkillsOptions, type Skill, type SkillFile, type SkillScope, } from "./skills.js";
|
package/dist/src/index.js
CHANGED
|
@@ -2,5 +2,5 @@ export { collectContentAssets, collectAssetFiles, isDraftAsset, loadSection, nor
|
|
|
2
2
|
export { buildSite, meowSite } from "./build.js";
|
|
3
3
|
export { aboutPage, agentMarkdown, homePage, listPage, llmText, postPage, skillPage, skillsIndexPage, thoughtPage, } from "./site.js";
|
|
4
4
|
export { DEFAULT_SITE } from "./identity.js";
|
|
5
|
-
export { blogTheme, blogTokens, blogOptions } from "./theme.js";
|
|
5
|
+
export { blogFonts, blogTheme, blogTokens, blogOptions } from "./theme.js";
|
|
6
6
|
export { loadSkills, } from "./skills.js";
|
package/dist/src/theme.d.ts
CHANGED
|
@@ -37,9 +37,11 @@ export declare const blogTokens: import("meowcss").TokenRefs<{
|
|
|
37
37
|
readonly fontMono: "\"SF Mono\", \"Cascadia Code\", Menlo, Consolas, ui-monospace, monospace";
|
|
38
38
|
readonly paperGrain: "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160' viewBox='0 0 160 160'%3E%3Cfilter id='grain'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.78' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23grain)' opacity='.065'/%3E%3C/svg%3E\")";
|
|
39
39
|
}> & import("meowcss").TokenSet;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
/** Static HTML owns the theme root; CSS remains available without JavaScript. */
|
|
41
|
+
export declare const blogTheme: import("meowcss").ThemeDefinition;
|
|
42
|
+
/** The web font the Blog theme names; the build publishes it with its license. */
|
|
43
|
+
export declare const blogFonts: readonly [{
|
|
44
|
+
readonly family: "Geist Variable";
|
|
45
|
+
readonly package: "@fontsource-variable/geist";
|
|
46
|
+
readonly styles: readonly ["normal"];
|
|
47
|
+
}];
|
package/dist/src/theme.js
CHANGED
|
@@ -2217,14 +2217,13 @@ const styles = css `
|
|
|
2217
2217
|
}
|
|
2218
2218
|
`;
|
|
2219
2219
|
/** Static HTML owns the theme root; CSS remains available without JavaScript. */
|
|
2220
|
-
const
|
|
2220
|
+
export const blogTheme = defineTheme({
|
|
2221
2221
|
options: blogOptions,
|
|
2222
2222
|
defaults: { scheme: "auto" },
|
|
2223
2223
|
tokens: [blogTokens],
|
|
2224
2224
|
styles,
|
|
2225
2225
|
});
|
|
2226
|
-
|
|
2227
|
-
export const
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
});
|
|
2226
|
+
/** The web font the Blog theme names; the build publishes it with its license. */
|
|
2227
|
+
export const blogFonts = [
|
|
2228
|
+
{ family: "Geist Variable", package: "@fontsource-variable/geist", styles: ["normal"] },
|
|
2229
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meowpress",
|
|
3
|
-
"version": "0.3.0-alpha.
|
|
3
|
+
"version": "0.3.0-alpha.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"yaml": "^2.9.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"meowcss": "^0.3.0-alpha.
|
|
40
|
+
"meowcss": "^0.3.0-alpha.1",
|
|
41
41
|
"meowjsx": "^0.3.0-alpha.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|