meowpress 0.1.1-alpha.1 → 0.2.0-alpha.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.
- package/README.md +32 -1
- package/dist/src/build.d.ts +14 -1
- package/dist/src/build.js +66 -26
- package/dist/src/content.d.ts +5 -0
- package/dist/src/content.js +7 -7
- package/dist/src/pages.d.ts +28 -0
- package/dist/src/pages.js +10 -0
- package/dist/src/site.d.ts +12 -9
- package/dist/src/site.js +79 -45
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
`meowpress` provides content reading, page rendering, themes and the build pipeline for static sites.
|
|
4
4
|
Consumers own the site configuration and content and build through this package; the package does not own the application's posts or output directory.
|
|
5
5
|
|
|
6
|
-
`0.1.1-alpha.0`
|
|
6
|
+
Published history: `0.1.1-alpha.0` was accepted from the registry, followed by `0.1.1-alpha.1` with the English site and CLI defaults; `latest` remained `0.1.0`. The current workspace package is `0.2.0-alpha.0`, and its required CSS and JSX peer ranges accept `^0.2.0-alpha.0`. The host selects the shared CSS/JSX modules, and this package does not privately install another CSS copy; it does not depend on Meowtail or MeowSlides. Out-of-package builds and theme identity acceptance are bound to exact package archives, and peer contracts are not bypassed through overrides. Publishing requires separate explicit authorization.
|
|
7
7
|
|
|
8
8
|
This package is a standalone content publishing library with source in `packages/meowpress`; it already has a public build entry, independent tests and typechecking,
|
|
9
9
|
and does not depend on Assistant, WorkCell, Deno or the content and runtime state of a specific Blog. Style generation comes from `meowcss`,
|
|
@@ -25,6 +25,37 @@ export default meowSite({
|
|
|
25
25
|
});
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
## Pages a consumer owns
|
|
29
|
+
|
|
30
|
+
A consumer that supplies `pages` owns the whole site map. The provider receives the content that was
|
|
31
|
+
already loaded and returns complete documents; the package writes them at their paths, adds them to
|
|
32
|
+
the agent-facing indexes, and does not publish its own blog routes next to them. One site map, one
|
|
33
|
+
owner.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { meowSite, type SitePage } from "meowpress/build";
|
|
37
|
+
|
|
38
|
+
export default meowSite({
|
|
39
|
+
content: [{ directory: "/path/to/content" }],
|
|
40
|
+
outputDirectory: "/path/to/dist",
|
|
41
|
+
themeStylesheet: "site-theme.css",
|
|
42
|
+
pages: ({ posts, theme }): SitePage[] =>
|
|
43
|
+
posts.map((post) => ({
|
|
44
|
+
path: post.slug,
|
|
45
|
+
title: post.title,
|
|
46
|
+
html: renderDocument(post, theme),
|
|
47
|
+
})),
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- `pages` is a provider of complete HTML documents. A path that resolves above the site root is
|
|
52
|
+
refused rather than clamped.
|
|
53
|
+
- `mdxComponents` adds named components to the MDX content pages; the built-in `Callout` stays
|
|
54
|
+
available.
|
|
55
|
+
- `themeStylesheet` names the theme stylesheet the build writes, so a consumer page can link it. It
|
|
56
|
+
applies to the built-in pages as well, so a renamed stylesheet never leaves a page linking a file
|
|
57
|
+
that was not written.
|
|
58
|
+
|
|
28
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.
|
|
29
60
|
The whole theme can be replaced with `theme: defineTheme(...)`; all static pages output that theme's root attributes and default options.
|
|
30
61
|
The build writes `theme.cssText` to a temporary CSS file, then lets Bun handle fonts and other assets, and finally delivers through a stylesheet link,
|
package/dist/src/build.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { type ThemeDefinition } from "meowcss";
|
|
2
2
|
import { type SiteIdentity } from "./identity.js";
|
|
3
|
-
import { type ContentSourceInput } from "./content.js";
|
|
3
|
+
import { type ContentSourceInput, type MdxComponent } from "./content.js";
|
|
4
|
+
import { type SitePageProvider } from "./pages.js";
|
|
5
|
+
export type { SitePage, SitePageContext, SitePageProvider } from "./pages.js";
|
|
6
|
+
export type { ContentItem } from "./content.js";
|
|
7
|
+
export type { SiteIdentity } from "./identity.js";
|
|
4
8
|
export interface SiteConfig {
|
|
5
9
|
/** One or more roots containing `posts/`, `thoughts/`, and optional assets. */
|
|
6
10
|
content: ContentSourceInput | readonly ContentSourceInput[];
|
|
@@ -14,6 +18,12 @@ export interface SiteConfig {
|
|
|
14
18
|
projectRootDirectory?: string;
|
|
15
19
|
/** Site name, description, repository links and About copy; unset fields use neutral defaults. */
|
|
16
20
|
site?: Partial<SiteIdentity>;
|
|
21
|
+
/** Consumer-supplied pages; the site writes and indexes them next to its own. */
|
|
22
|
+
pages?: SitePageProvider;
|
|
23
|
+
/** Extra components available to MDX content pages. */
|
|
24
|
+
mdxComponents?: Record<string, MdxComponent>;
|
|
25
|
+
/** Stylesheet file name for the theme; defaults to "blog-theme.css". */
|
|
26
|
+
themeStylesheet?: string;
|
|
17
27
|
}
|
|
18
28
|
export interface ResolvedSiteConfig {
|
|
19
29
|
content: readonly ContentSourceInput[];
|
|
@@ -22,6 +32,9 @@ export interface ResolvedSiteConfig {
|
|
|
22
32
|
theme: ThemeDefinition;
|
|
23
33
|
projectRootDirectory?: string;
|
|
24
34
|
site: SiteIdentity;
|
|
35
|
+
pages?: SitePageProvider;
|
|
36
|
+
mdxComponents: Record<string, MdxComponent>;
|
|
37
|
+
themeStylesheet: string;
|
|
25
38
|
}
|
|
26
39
|
/** Define a site config in the app package without taking on the build pipeline. */
|
|
27
40
|
export declare function meowSite(config: SiteConfig): ResolvedSiteConfig;
|
package/dist/src/build.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { blogTheme } from "./theme.js";
|
|
2
2
|
import { DEFAULT_SITE } from "./identity.js";
|
|
3
3
|
import { mkdir, rm, writeFile, cp } from "node:fs/promises";
|
|
4
|
-
import { join, resolve } from "node:path";
|
|
4
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { collectContentAssets, isDraftAsset, loadSection, normalizeContentSources, } from "./content.js";
|
|
7
|
-
import { aboutPage, agentMarkdown, homePage, listPage, llmText, postPage, skillPage, skillsIndexPage, thoughtPage, } from "./site.js";
|
|
7
|
+
import { aboutPage, agentMarkdown, DEFAULT_THEME_STYLESHEET, homePage, listPage, llmText, postPage, skillPage, skillsIndexPage, thoughtPage, } from "./site.js";
|
|
8
8
|
import { loadSkills } from "./skills.js";
|
|
9
|
+
import { sitePageFile } from "./pages.js";
|
|
9
10
|
/** Define a site config in the app package without taking on the build pipeline. */
|
|
10
11
|
export function meowSite(config) {
|
|
11
12
|
return {
|
|
@@ -17,6 +18,9 @@ export function meowSite(config) {
|
|
|
17
18
|
? resolve(config.projectRootDirectory)
|
|
18
19
|
: undefined,
|
|
19
20
|
site: { ...DEFAULT_SITE, ...config.site },
|
|
21
|
+
pages: config.pages,
|
|
22
|
+
mdxComponents: config.mdxComponents ?? {},
|
|
23
|
+
themeStylesheet: config.themeStylesheet ?? DEFAULT_THEME_STYLESHEET,
|
|
20
24
|
};
|
|
21
25
|
}
|
|
22
26
|
async function writePage(generatedDirectory, pathname, html) {
|
|
@@ -58,13 +62,30 @@ async function buildStaticHtmlFromEntrypoints(entrypoints, generatedDirectory, o
|
|
|
58
62
|
throw new Error("Unable to build site static HTML");
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
|
-
|
|
65
|
+
/** Consumer pages are written inside the generated directory and never above it. */
|
|
66
|
+
function consumerPageFile(generatedDirectory, path) {
|
|
67
|
+
const file = sitePageFile(path);
|
|
68
|
+
const target = resolve(generatedDirectory, file);
|
|
69
|
+
const inside = relative(generatedDirectory, target);
|
|
70
|
+
if (!inside || inside.startsWith("..") || isAbsolute(inside)) {
|
|
71
|
+
throw new Error(`Site page path stays inside the site root: ${path}`);
|
|
72
|
+
}
|
|
73
|
+
return file;
|
|
74
|
+
}
|
|
75
|
+
async function writeConsumerPages(generatedDirectory, pages) {
|
|
76
|
+
const entrypoints = [];
|
|
77
|
+
for (const page of pages) {
|
|
78
|
+
entrypoints.push(await writePage(generatedDirectory, consumerPageFile(generatedDirectory, page.path), page.html));
|
|
79
|
+
}
|
|
80
|
+
return entrypoints;
|
|
81
|
+
}
|
|
82
|
+
async function writeContentPages(generatedDirectory, posts, thoughts, theme, site, stylesheet) {
|
|
62
83
|
const entrypoints = [];
|
|
63
84
|
for (const post of posts) {
|
|
64
|
-
entrypoints.push(await writePage(generatedDirectory, `posts/${post.slug}/index.html`, postPage(post, posts, thoughts, theme, site)));
|
|
85
|
+
entrypoints.push(await writePage(generatedDirectory, `posts/${post.slug}/index.html`, postPage(post, posts, thoughts, theme, site, stylesheet)));
|
|
65
86
|
}
|
|
66
87
|
for (const thought of thoughts) {
|
|
67
|
-
entrypoints.push(await writePage(generatedDirectory, `thoughts/${thought.slug}/index.html`, thoughtPage(thought, theme, site)));
|
|
88
|
+
entrypoints.push(await writePage(generatedDirectory, `thoughts/${thought.slug}/index.html`, thoughtPage(thought, theme, site, stylesheet)));
|
|
68
89
|
}
|
|
69
90
|
return entrypoints;
|
|
70
91
|
}
|
|
@@ -72,30 +93,47 @@ export async function buildSite(input) {
|
|
|
72
93
|
const config = meowSite(input);
|
|
73
94
|
const generatedDirectory = resolve(config.outputDirectory, "../.generated");
|
|
74
95
|
const [posts, thoughts, skills] = await Promise.all([
|
|
75
|
-
loadSection(config.content, "post"),
|
|
76
|
-
loadSection(config.content, "thought"),
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
96
|
+
loadSection(config.content, "post", { components: config.mdxComponents }),
|
|
97
|
+
loadSection(config.content, "thought", { components: config.mdxComponents }),
|
|
98
|
+
config.pages
|
|
99
|
+
? Promise.resolve([])
|
|
100
|
+
: loadSkills(config.skillsDirectory, {
|
|
101
|
+
projectRootDirectory: config.projectRootDirectory,
|
|
102
|
+
}),
|
|
80
103
|
]);
|
|
81
104
|
const timeline = [...posts, ...thoughts].sort((left, right) => (right.date?.getTime() ?? 0) - (left.date?.getTime() ?? 0));
|
|
82
105
|
await rm(config.outputDirectory, { recursive: true, force: true });
|
|
83
106
|
await rm(generatedDirectory, { recursive: true, force: true });
|
|
84
107
|
await mkdir(config.outputDirectory, { recursive: true });
|
|
85
108
|
await mkdir(generatedDirectory, { recursive: true });
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
// A consumer that supplies its own pages owns the whole site map; the package's
|
|
110
|
+
// own blog routes are not published alongside them.
|
|
111
|
+
const pages = config.pages
|
|
112
|
+
? await config.pages({
|
|
113
|
+
posts,
|
|
114
|
+
thoughts,
|
|
115
|
+
site: config.site,
|
|
116
|
+
theme: config.theme,
|
|
117
|
+
})
|
|
118
|
+
: undefined;
|
|
119
|
+
const entrypoints = [];
|
|
120
|
+
if (pages) {
|
|
121
|
+
entrypoints.push(...(await writeConsumerPages(generatedDirectory, pages)));
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
entrypoints.push(...(await Promise.all([
|
|
125
|
+
writePage(generatedDirectory, "index.html", homePage(timeline, config.theme, config.site, config.themeStylesheet)),
|
|
126
|
+
writePage(generatedDirectory, "about/index.html", aboutPage(config.theme, config.site, config.themeStylesheet)),
|
|
127
|
+
writePage(generatedDirectory, "posts/index.html", listPage("post", posts, config.theme, config.site, config.themeStylesheet)),
|
|
128
|
+
writePage(generatedDirectory, "thoughts/index.html", listPage("thought", thoughts, config.theme, config.site, config.themeStylesheet)),
|
|
129
|
+
writePage(generatedDirectory, "skills/index.html", skillsIndexPage(skills, config.theme, config.site, config.themeStylesheet)),
|
|
130
|
+
])), ...(await writeContentPages(generatedDirectory, posts, thoughts, config.theme, config.site, config.themeStylesheet)));
|
|
131
|
+
for (const skill of skills) {
|
|
132
|
+
const skillPath = skill.scope === "public" ? `skills/${skill.slug}` : `skills/project/${skill.slug}`;
|
|
133
|
+
entrypoints.push(await writePage(generatedDirectory, `${skillPath}/index.html`, skillPage(skill, config.theme, config.site, config.themeStylesheet)));
|
|
134
|
+
}
|
|
97
135
|
}
|
|
98
|
-
await writeFile(join(generatedDirectory,
|
|
136
|
+
await writeFile(join(generatedDirectory, config.themeStylesheet), config.theme.cssText.replaceAll('"@fontsource-variable/geist"', JSON.stringify(fileURLToPath(import.meta.resolve("@fontsource-variable/geist")))), "utf8");
|
|
99
137
|
await stageAssets(generatedDirectory, config.content);
|
|
100
138
|
await buildStaticHtmlFromEntrypoints(entrypoints, generatedDirectory, config.outputDirectory);
|
|
101
139
|
if (config.theme.cssText.includes('"@fontsource-variable/geist"')) {
|
|
@@ -105,10 +143,12 @@ export async function buildSite(input) {
|
|
|
105
143
|
}
|
|
106
144
|
await copyPostAssets(config.outputDirectory, config.content);
|
|
107
145
|
await Promise.all([
|
|
108
|
-
writeFile(join(config.outputDirectory, "llm.txt"), llmText(posts, thoughts, config.site), "utf8"),
|
|
109
|
-
writeFile(join(config.outputDirectory, "llms.txt"), llmText(posts, thoughts, config.site), "utf8"),
|
|
110
|
-
writeFile(join(config.outputDirectory, "agents.md"), agentMarkdown(posts, thoughts, config.site), "utf8"),
|
|
146
|
+
writeFile(join(config.outputDirectory, "llm.txt"), llmText(posts, thoughts, config.site, pages), "utf8"),
|
|
147
|
+
writeFile(join(config.outputDirectory, "llms.txt"), llmText(posts, thoughts, config.site, pages), "utf8"),
|
|
148
|
+
writeFile(join(config.outputDirectory, "agents.md"), agentMarkdown(posts, thoughts, config.site, pages), "utf8"),
|
|
111
149
|
]);
|
|
112
150
|
await rm(generatedDirectory, { recursive: true, force: true });
|
|
113
|
-
console.log(
|
|
151
|
+
console.log(pages
|
|
152
|
+
? `Built ${posts.length} content items and ${pages.length} site pages into site dist/.`
|
|
153
|
+
: `Built ${posts.length} posts and ${thoughts.length} thoughts into site dist/.`);
|
|
114
154
|
}
|
package/dist/src/content.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Child, type Props } from "meowjsx/runtime";
|
|
1
2
|
export type ContentKind = "post" | "thought";
|
|
2
3
|
export type Frontmatter = Record<string, unknown>;
|
|
3
4
|
/** A content root containing `posts/`, `thoughts/`, and optional asset folders. */
|
|
@@ -11,7 +12,11 @@ export type DuplicateContentPolicy = "first-wins" | "error";
|
|
|
11
12
|
export interface LoadSectionOptions {
|
|
12
13
|
/** The first source wins by default, so the source order is an explicit overlay order. */
|
|
13
14
|
duplicate?: DuplicateContentPolicy;
|
|
15
|
+
/** Extra components available to MDX content pages. */
|
|
16
|
+
components?: Record<string, MdxComponent>;
|
|
14
17
|
}
|
|
18
|
+
/** A component an MDX content page may use by name. */
|
|
19
|
+
export type MdxComponent = (props: Props) => Child;
|
|
15
20
|
export interface ContentItem {
|
|
16
21
|
kind: ContentKind;
|
|
17
22
|
slug: string;
|
package/dist/src/content.js
CHANGED
|
@@ -112,7 +112,7 @@ function Callout(props) {
|
|
|
112
112
|
const MDX_COMPONENTS = {
|
|
113
113
|
Callout,
|
|
114
114
|
};
|
|
115
|
-
async function renderMdx(source) {
|
|
115
|
+
async function renderMdx(source, components) {
|
|
116
116
|
const compiled = await compile({ value: source }, {
|
|
117
117
|
outputFormat: "function-body",
|
|
118
118
|
remarkPlugins: [remarkGfm],
|
|
@@ -123,15 +123,15 @@ async function renderMdx(source) {
|
|
|
123
123
|
jsxs: h,
|
|
124
124
|
});
|
|
125
125
|
const component = module.default;
|
|
126
|
-
return renderToHtml(component({ components: MDX_COMPONENTS }));
|
|
126
|
+
return renderToHtml(component({ components: { ...MDX_COMPONENTS, ...components } }));
|
|
127
127
|
}
|
|
128
|
-
async function renderSource(source, sourcePath) {
|
|
128
|
+
async function renderSource(source, sourcePath, components) {
|
|
129
129
|
if (sourcePath.endsWith(".md")) {
|
|
130
130
|
return Bun.markdown.html(source, {
|
|
131
131
|
tagFilter: true,
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
|
-
return renderMdx(source);
|
|
134
|
+
return renderMdx(source, components);
|
|
135
135
|
}
|
|
136
136
|
async function collectFiles(directory) {
|
|
137
137
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
@@ -186,7 +186,7 @@ export function normalizeContentSources(input) {
|
|
|
186
186
|
function duplicateContentError(kind, slug) {
|
|
187
187
|
return new Error(`Duplicate ${kind} slug "${slug}" while merging content sources.`);
|
|
188
188
|
}
|
|
189
|
-
async function loadSectionFromSource(source, kind) {
|
|
189
|
+
async function loadSectionFromSource(source, kind, options) {
|
|
190
190
|
const sectionDirectory = resolve(source.directory, `${kind}s`);
|
|
191
191
|
let sourceFiles;
|
|
192
192
|
try {
|
|
@@ -217,7 +217,7 @@ async function loadSectionFromSource(source, kind) {
|
|
|
217
217
|
const bodyWithoutLeadingHeading = document.body.replace(/^\s*#\s+.+?(?:\r?\n|$)+/, "");
|
|
218
218
|
const plainText = stripMarkdown(bodyWithoutLeadingHeading);
|
|
219
219
|
const title = asText(document.metadata.title) ?? titleFromBody(document.body, slug);
|
|
220
|
-
const html = withoutLeadingHeading(await renderSource(document.body, sourcePath));
|
|
220
|
+
const html = withoutLeadingHeading(await renderSource(document.body, sourcePath, options.components ?? {}));
|
|
221
221
|
items.push({
|
|
222
222
|
kind,
|
|
223
223
|
slug,
|
|
@@ -241,7 +241,7 @@ export async function loadSection(sources, kind, options = {}) {
|
|
|
241
241
|
const merged = new Map();
|
|
242
242
|
const duplicatePolicy = options.duplicate ?? "first-wins";
|
|
243
243
|
for (const source of normalizeContentSources(sources)) {
|
|
244
|
-
const items = await loadSectionFromSource(source, kind);
|
|
244
|
+
const items = await loadSectionFromSource(source, kind, options);
|
|
245
245
|
for (const item of items) {
|
|
246
246
|
if (merged.has(item.slug)) {
|
|
247
247
|
if (duplicatePolicy === "error")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ThemeDefinition } from "meowcss";
|
|
2
|
+
import type { ContentItem } from "./content.js";
|
|
3
|
+
import type { SiteIdentity } from "./identity.js";
|
|
4
|
+
/** A page supplied by the consumer of the site. */
|
|
5
|
+
export interface SitePage {
|
|
6
|
+
/** Route path below the site root; "packages/meowtail" becomes /packages/meowtail/. */
|
|
7
|
+
path: string;
|
|
8
|
+
title: string;
|
|
9
|
+
/** A complete HTML document for the route. */
|
|
10
|
+
html: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
/** Grouping used by the site index and the agent-facing indexes. */
|
|
13
|
+
group?: string;
|
|
14
|
+
order?: number;
|
|
15
|
+
}
|
|
16
|
+
/** What a page provider receives when the site is built. */
|
|
17
|
+
export interface SitePageContext {
|
|
18
|
+
posts: readonly ContentItem[];
|
|
19
|
+
thoughts: readonly ContentItem[];
|
|
20
|
+
site: SiteIdentity;
|
|
21
|
+
theme: ThemeDefinition;
|
|
22
|
+
}
|
|
23
|
+
/** Supplies the consumer's own pages for the site. */
|
|
24
|
+
export type SitePageProvider = (context: SitePageContext) => readonly SitePage[] | Promise<readonly SitePage[]>;
|
|
25
|
+
/** The route a page is published under, with a leading and a trailing slash. */
|
|
26
|
+
export declare function sitePageUrl(path: string): string;
|
|
27
|
+
/** The output file a page is written to, relative to the site root. */
|
|
28
|
+
export declare function sitePageFile(path: string): string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The route a page is published under, with a leading and a trailing slash. */
|
|
2
|
+
export function sitePageUrl(path) {
|
|
3
|
+
const normalized = path.replace(/^\/+|\/+$/g, "");
|
|
4
|
+
return normalized ? `/${normalized}/` : "/";
|
|
5
|
+
}
|
|
6
|
+
/** The output file a page is written to, relative to the site root. */
|
|
7
|
+
export function sitePageFile(path) {
|
|
8
|
+
const normalized = path.replace(/^\/+|\/+$/g, "");
|
|
9
|
+
return normalized ? `${normalized}/index.html` : "index.html";
|
|
10
|
+
}
|
package/dist/src/site.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { type ThemeDefinition } from "meowcss";
|
|
2
2
|
import { type ContentItem } from "./content.js";
|
|
3
3
|
import { type SiteIdentity } from "./identity.js";
|
|
4
|
+
import { type SitePage } from "./pages.js";
|
|
4
5
|
import type { Skill } from "./skills.js";
|
|
5
|
-
|
|
6
|
-
export declare
|
|
7
|
-
export declare function
|
|
8
|
-
export declare function
|
|
9
|
-
export declare function
|
|
10
|
-
export declare function
|
|
11
|
-
export declare function
|
|
12
|
-
export declare function
|
|
13
|
-
export declare function
|
|
6
|
+
/** The name of the theme stylesheet the package writes by default. */
|
|
7
|
+
export declare const DEFAULT_THEME_STYLESHEET = "blog-theme.css";
|
|
8
|
+
export declare function homePage(items: ContentItem[], theme?: ThemeDefinition, site?: SiteIdentity, stylesheet?: string): string;
|
|
9
|
+
export declare function listPage(kind: "post" | "thought", items: ContentItem[], theme?: ThemeDefinition, site?: SiteIdentity, stylesheet?: string): string;
|
|
10
|
+
export declare function postPage(item: ContentItem, posts: ContentItem[], thoughts: ContentItem[], theme?: ThemeDefinition, site?: SiteIdentity, stylesheet?: string): string;
|
|
11
|
+
export declare function thoughtPage(item: ContentItem, theme?: ThemeDefinition, site?: SiteIdentity, stylesheet?: string): string;
|
|
12
|
+
export declare function aboutPage(theme?: ThemeDefinition, site?: SiteIdentity, stylesheet?: string): string;
|
|
13
|
+
export declare function skillsIndexPage(skills: Skill[], theme?: ThemeDefinition, site?: SiteIdentity, stylesheet?: string): string;
|
|
14
|
+
export declare function skillPage(skill: Skill, theme?: ThemeDefinition, site?: SiteIdentity, stylesheet?: string): string;
|
|
15
|
+
export declare function agentMarkdown(posts: ContentItem[], thoughts: ContentItem[], site?: SiteIdentity, pages?: readonly SitePage[]): string;
|
|
16
|
+
export declare function llmText(posts: ContentItem[], thoughts: ContentItem[], site?: SiteIdentity, pages?: readonly SitePage[]): string;
|
package/dist/src/site.js
CHANGED
|
@@ -4,7 +4,18 @@ import { Fragment, h } from "meowjsx/runtime";
|
|
|
4
4
|
import { renderToHtml } from "meowjsx/web";
|
|
5
5
|
import { withoutLeadingHeading } from "./content.js";
|
|
6
6
|
import { DEFAULT_SITE } from "./identity.js";
|
|
7
|
+
import { sitePageUrl } from "./pages.js";
|
|
7
8
|
import { layoutSkillGraph } from "./skill-graph.js";
|
|
9
|
+
/** The name of the theme stylesheet the package writes by default. */
|
|
10
|
+
export const DEFAULT_THEME_STYLESHEET = "blog-theme.css";
|
|
11
|
+
/** The href of the theme stylesheet for a page at a given depth. */
|
|
12
|
+
function styleHref(depth, stylesheet) {
|
|
13
|
+
return `${"../".repeat(depth)}${stylesheet}`;
|
|
14
|
+
}
|
|
15
|
+
/** The number of directories a slug puts a page below its section. */
|
|
16
|
+
function slugDepth(slug) {
|
|
17
|
+
return slug.split("/").filter(Boolean).length;
|
|
18
|
+
}
|
|
8
19
|
function SiteHeader({ activePath, site }) {
|
|
9
20
|
return (h("header", { className: "site" },
|
|
10
21
|
h("a", { className: "site-name", href: "/" }, site.name),
|
|
@@ -240,12 +251,12 @@ const FILTER_SCRIPT = `(() => {
|
|
|
240
251
|
}
|
|
241
252
|
}));
|
|
242
253
|
})();`;
|
|
243
|
-
export function homePage(items, theme = blogTheme, site = DEFAULT_SITE) {
|
|
254
|
+
export function homePage(items, theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
|
|
244
255
|
return pageHtml({
|
|
245
256
|
site,
|
|
246
257
|
theme,
|
|
247
258
|
title: site.name,
|
|
248
|
-
styleHref:
|
|
259
|
+
styleHref: styleHref(0, stylesheet),
|
|
249
260
|
children: (h(Fragment, null,
|
|
250
261
|
h(Hero, { image: site.images?.hero ? `./${site.images.hero}` : undefined }),
|
|
251
262
|
h(Filter, { posts: items.filter((item) => item.kind === "post").length, thoughts: items.filter((item) => item.kind === "thought").length }),
|
|
@@ -253,14 +264,14 @@ export function homePage(items, theme = blogTheme, site = DEFAULT_SITE) {
|
|
|
253
264
|
h("script", { dangerouslySetInnerHTML: { __html: FILTER_SCRIPT } }))),
|
|
254
265
|
});
|
|
255
266
|
}
|
|
256
|
-
export function listPage(kind, items, theme = blogTheme, site = DEFAULT_SITE) {
|
|
267
|
+
export function listPage(kind, items, theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
|
|
257
268
|
const isPost = kind === "post";
|
|
258
269
|
return pageHtml({
|
|
259
270
|
site,
|
|
260
271
|
theme,
|
|
261
272
|
title: isPost ? "Posts" : "Thoughts",
|
|
262
273
|
activePath: isPost ? "/posts/" : "/thoughts/",
|
|
263
|
-
styleHref:
|
|
274
|
+
styleHref: styleHref(1, stylesheet),
|
|
264
275
|
children: (h("main", { className: "listing" },
|
|
265
276
|
h("header", { className: "listing-head" },
|
|
266
277
|
h("span", { className: "kind" }, isPost ? "Posts" : "Thoughts"),
|
|
@@ -293,7 +304,7 @@ function RelatedThoughts({ item, thoughts, }) {
|
|
|
293
304
|
h("h2", null, "Thoughts from the same day"),
|
|
294
305
|
related.map((thought) => (h(ThoughtCard, { item: thought, linked: true })))));
|
|
295
306
|
}
|
|
296
|
-
export function postPage(item, posts, thoughts, theme = blogTheme, site = DEFAULT_SITE) {
|
|
307
|
+
export function postPage(item, posts, thoughts, theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
|
|
297
308
|
const category = typeof item.metadata.category === "string" ? item.metadata.category : "Post";
|
|
298
309
|
return pageHtml({
|
|
299
310
|
site,
|
|
@@ -301,7 +312,7 @@ export function postPage(item, posts, thoughts, theme = blogTheme, site = DEFAUL
|
|
|
301
312
|
title: item.title,
|
|
302
313
|
description: item.excerpt,
|
|
303
314
|
activePath: "/posts/",
|
|
304
|
-
styleHref:
|
|
315
|
+
styleHref: styleHref(1 + slugDepth(item.slug), stylesheet),
|
|
305
316
|
children: (h("main", null,
|
|
306
317
|
h("article", null,
|
|
307
318
|
h("header", { className: "post-head" },
|
|
@@ -319,14 +330,14 @@ export function postPage(item, posts, thoughts, theme = blogTheme, site = DEFAUL
|
|
|
319
330
|
h(PostNavigation, { item: item, posts: posts })))),
|
|
320
331
|
});
|
|
321
332
|
}
|
|
322
|
-
export function thoughtPage(item, theme = blogTheme, site = DEFAULT_SITE) {
|
|
333
|
+
export function thoughtPage(item, theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
|
|
323
334
|
return pageHtml({
|
|
324
335
|
site,
|
|
325
336
|
theme,
|
|
326
337
|
title: item.title,
|
|
327
338
|
description: item.excerpt,
|
|
328
339
|
activePath: "/thoughts/",
|
|
329
|
-
styleHref:
|
|
340
|
+
styleHref: styleHref(1 + slugDepth(item.slug), stylesheet),
|
|
330
341
|
children: (h("main", null,
|
|
331
342
|
h("article", null,
|
|
332
343
|
h("header", { className: "post-head" },
|
|
@@ -338,14 +349,14 @@ export function thoughtPage(item, theme = blogTheme, site = DEFAULT_SITE) {
|
|
|
338
349
|
h("div", { className: "prose", dangerouslySetInnerHTML: { __html: item.html } })))),
|
|
339
350
|
});
|
|
340
351
|
}
|
|
341
|
-
export function aboutPage(theme = blogTheme, site = DEFAULT_SITE) {
|
|
352
|
+
export function aboutPage(theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
|
|
342
353
|
const about = site.about;
|
|
343
354
|
return pageHtml({
|
|
344
355
|
site,
|
|
345
356
|
theme,
|
|
346
357
|
title: "About",
|
|
347
358
|
activePath: "/about/",
|
|
348
|
-
styleHref:
|
|
359
|
+
styleHref: styleHref(1, stylesheet),
|
|
349
360
|
children: (h("main", { className: "about" },
|
|
350
361
|
h("div", { className: "avatar", "aria-hidden": "true" }, site.images?.about ? (h("img", { src: `../${site.images.about}`, alt: "", decoding: "async" })) : (h("span", { className: "avatar-initial" }, Array.from(site.name.trim())[0] ?? "M"))),
|
|
351
362
|
about ? (h(Fragment, null,
|
|
@@ -594,7 +605,7 @@ const SKILL_BROWSER_SCRIPT = `(() => {
|
|
|
594
605
|
});
|
|
595
606
|
});
|
|
596
607
|
})();`;
|
|
597
|
-
export function skillsIndexPage(skills, theme = blogTheme, site = DEFAULT_SITE) {
|
|
608
|
+
export function skillsIndexPage(skills, theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
|
|
598
609
|
const publicSkills = skills.filter((skill) => skill.scope === "public");
|
|
599
610
|
const projectSkills = skills.filter((skill) => skill.scope === "project");
|
|
600
611
|
return pageHtml({
|
|
@@ -603,7 +614,7 @@ export function skillsIndexPage(skills, theme = blogTheme, site = DEFAULT_SITE)
|
|
|
603
614
|
title: "Skills",
|
|
604
615
|
description: "Methods and working conventions that can be handed to an Agent.",
|
|
605
616
|
activePath: "/skills/",
|
|
606
|
-
styleHref:
|
|
617
|
+
styleHref: styleHref(1, stylesheet),
|
|
607
618
|
children: (h("main", { className: "skills-index" },
|
|
608
619
|
h("header", { className: "skills-head" },
|
|
609
620
|
h("span", { className: "skill-path" }, "~/skills"),
|
|
@@ -624,7 +635,7 @@ export function skillsIndexPage(skills, theme = blogTheme, site = DEFAULT_SITE)
|
|
|
624
635
|
h("p", { className: "skills-prototype-note" }, "The reference graph starts from SKILL.md and is generated at build time from Markdown links."))),
|
|
625
636
|
});
|
|
626
637
|
}
|
|
627
|
-
export function skillPage(skill, theme = blogTheme, site = DEFAULT_SITE) {
|
|
638
|
+
export function skillPage(skill, theme = blogTheme, site = DEFAULT_SITE, stylesheet = DEFAULT_THEME_STYLESHEET) {
|
|
628
639
|
const firstFile = skill.files[0];
|
|
629
640
|
const command = skill.scope === "public" ? skillInstallCommand(site, skill) : skill.sourceBasePath;
|
|
630
641
|
return pageHtml({
|
|
@@ -633,7 +644,9 @@ export function skillPage(skill, theme = blogTheme, site = DEFAULT_SITE) {
|
|
|
633
644
|
title: skill.name,
|
|
634
645
|
description: skill.description,
|
|
635
646
|
activePath: "/skills/",
|
|
636
|
-
styleHref: skill.scope === "public"
|
|
647
|
+
styleHref: skill.scope === "public"
|
|
648
|
+
? styleHref(1 + slugDepth(skill.slug), stylesheet)
|
|
649
|
+
: styleHref(2 + slugDepth(skill.slug), stylesheet),
|
|
637
650
|
children: (h("main", { className: "skill-page" },
|
|
638
651
|
h("a", { className: "skill-back", href: "/skills/" }, "\u2190 All Skills"),
|
|
639
652
|
h("header", { className: "skill-browser-head" },
|
|
@@ -660,7 +673,16 @@ export function skillPage(skill, theme = blogTheme, site = DEFAULT_SITE) {
|
|
|
660
673
|
h("script", { dangerouslySetInnerHTML: { __html: SKILL_BROWSER_SCRIPT } }))),
|
|
661
674
|
});
|
|
662
675
|
}
|
|
663
|
-
export function agentMarkdown(posts, thoughts, site = DEFAULT_SITE) {
|
|
676
|
+
export function agentMarkdown(posts, thoughts, site = DEFAULT_SITE, pages) {
|
|
677
|
+
const siteMap = pages
|
|
678
|
+
? pages.map((page) => `- [${page.title}](${sitePageUrl(page.path)})`)
|
|
679
|
+
: [
|
|
680
|
+
"- [Home](/)",
|
|
681
|
+
"- [Posts](/posts/)",
|
|
682
|
+
"- [Thoughts](/thoughts/)",
|
|
683
|
+
"- [Skills](/skills/) (browse files and the auto-generated Markdown reference graph)",
|
|
684
|
+
"- [About](/about/)",
|
|
685
|
+
];
|
|
664
686
|
const lines = [
|
|
665
687
|
`# ${site.name}`,
|
|
666
688
|
"",
|
|
@@ -668,11 +690,7 @@ export function agentMarkdown(posts, thoughts, site = DEFAULT_SITE) {
|
|
|
668
690
|
"",
|
|
669
691
|
"## Site map",
|
|
670
692
|
"",
|
|
671
|
-
|
|
672
|
-
"- [Posts](/posts/)",
|
|
673
|
-
"- [Thoughts](/thoughts/)",
|
|
674
|
-
"- [Skills](/skills/) (browse files and the auto-generated Markdown reference graph)",
|
|
675
|
-
"- [About](/about/)",
|
|
693
|
+
...siteMap,
|
|
676
694
|
"",
|
|
677
695
|
"## Content model",
|
|
678
696
|
"",
|
|
@@ -681,41 +699,57 @@ export function agentMarkdown(posts, thoughts, site = DEFAULT_SITE) {
|
|
|
681
699
|
"- Content files support `.md` and `.mdx`, and may start with YAML frontmatter.",
|
|
682
700
|
"- Frontmatter currently has no required fields; `title`, `date`, `created_at`, `category` and `tags` are recognized by the site.",
|
|
683
701
|
"",
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
702
|
+
// A consumer that owns the site map owns the routes of its content too.
|
|
703
|
+
...(pages
|
|
704
|
+
? []
|
|
705
|
+
: [
|
|
706
|
+
"## Public content",
|
|
707
|
+
"",
|
|
708
|
+
`Public posts: ${posts.length}.`,
|
|
709
|
+
`Public thoughts: ${thoughts.length}.`,
|
|
710
|
+
"",
|
|
711
|
+
"### Posts",
|
|
712
|
+
"",
|
|
713
|
+
...posts.map((post) => `- [${post.title}](${itemUrl(post)}) — ${post.dateKey}`),
|
|
714
|
+
"",
|
|
715
|
+
"### Thoughts",
|
|
716
|
+
"",
|
|
717
|
+
...thoughts.map((thought) => `- [${thought.title}](${itemUrl(thought)}) — ${thought.dateKey}`),
|
|
718
|
+
"",
|
|
719
|
+
]),
|
|
697
720
|
];
|
|
698
721
|
return lines.join("\n");
|
|
699
722
|
}
|
|
700
|
-
export function llmText(posts, thoughts, site = DEFAULT_SITE) {
|
|
723
|
+
export function llmText(posts, thoughts, site = DEFAULT_SITE, pages) {
|
|
724
|
+
const routes = pages
|
|
725
|
+
? pages.map((page) => `- ${page.title}: ${sitePageUrl(page.path)}`)
|
|
726
|
+
: [
|
|
727
|
+
"- Home: /",
|
|
728
|
+
"- Posts: /posts/",
|
|
729
|
+
"- Thoughts: /thoughts/",
|
|
730
|
+
"- Skills: /skills/",
|
|
731
|
+
"- About: /about/",
|
|
732
|
+
];
|
|
701
733
|
return [
|
|
702
734
|
`# ${site.name}`,
|
|
703
735
|
`> ${site.description}`,
|
|
704
736
|
"",
|
|
705
737
|
"## Pages",
|
|
706
|
-
|
|
707
|
-
"- Posts: /posts/",
|
|
708
|
-
"- Thoughts: /thoughts/",
|
|
709
|
-
"- Skills: /skills/",
|
|
710
|
-
"- About: /about/",
|
|
738
|
+
...routes,
|
|
711
739
|
"- Agent Markdown: /agents.md",
|
|
712
740
|
"",
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
741
|
+
// A consumer that owns the site map owns the routes of its content too;
|
|
742
|
+
// listing the content under routes the site does not publish would be wrong.
|
|
743
|
+
...(pages
|
|
744
|
+
? []
|
|
745
|
+
: [
|
|
746
|
+
"## Posts",
|
|
747
|
+
...posts.map((post) => `- ${post.title}: ${itemUrl(post)}`),
|
|
748
|
+
"",
|
|
749
|
+
"## Thoughts",
|
|
750
|
+
...thoughts.map((thought) => `- ${thought.title}: ${itemUrl(thought)}`),
|
|
751
|
+
"",
|
|
752
|
+
]),
|
|
719
753
|
"## Rules",
|
|
720
754
|
"- Only the public build output is considered presentable content.",
|
|
721
755
|
"- Drafts whose names start with _ under posts are not published or rendered.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meowpress",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-alpha.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -37,11 +37,11 @@
|
|
|
37
37
|
"yaml": "^2.9.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"meowcss": "^0.
|
|
41
|
-
"meowjsx": "^0.
|
|
40
|
+
"meowcss": "^0.2.0-alpha.0",
|
|
41
|
+
"meowjsx": "^0.2.0-alpha.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"meowjsx": "0.
|
|
45
|
-
"meowcss": "0.
|
|
44
|
+
"meowjsx": "0.2.0-alpha.0",
|
|
45
|
+
"meowcss": "0.2.0-alpha.0"
|
|
46
46
|
}
|
|
47
47
|
}
|