getfilepress 0.0.0 → 0.1.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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +178 -4
  3. package/package.json +89 -7
  4. package/packages/app/package.json +31 -0
  5. package/packages/app/src/app.d.ts +12 -0
  6. package/packages/app/src/app.html +12 -0
  7. package/packages/app/src/critical-theme.d.ts +4 -0
  8. package/packages/app/src/lib/content.server.ts +8 -0
  9. package/packages/app/src/lib/empty-theme.css +1 -0
  10. package/packages/app/src/lib/genie/GenieHost.svelte +16 -0
  11. package/packages/app/src/lib/genie/GeniePanel.svelte +429 -0
  12. package/packages/app/src/lib/genie/ops.ts +237 -0
  13. package/packages/app/src/lib/genie/store.ts +217 -0
  14. package/packages/app/src/lib/genie/types.ts +61 -0
  15. package/packages/app/src/lib/pages.server.ts +7 -0
  16. package/packages/app/src/lib/site.server.ts +52 -0
  17. package/packages/app/src/lib/theme-entry.ts +7 -0
  18. package/packages/app/src/routes/+layout.svelte +29 -0
  19. package/packages/app/src/routes/+layout.ts +10 -0
  20. package/packages/app/src/routes/+page.server.ts +28 -0
  21. package/packages/app/src/routes/+page.svelte +67 -0
  22. package/packages/app/src/routes/[slug]/+page.server.ts +20 -0
  23. package/packages/app/src/routes/[slug]/+page.svelte +47 -0
  24. package/packages/app/src/routes/page/[n]/+page.server.ts +21 -0
  25. package/packages/app/src/routes/page/[n]/+page.svelte +36 -0
  26. package/packages/app/src/routes/posts/[slug]/+page.server.ts +24 -0
  27. package/packages/app/src/routes/posts/[slug]/+page.svelte +95 -0
  28. package/packages/app/src/routes/robots.txt/+server.ts +11 -0
  29. package/packages/app/src/routes/rss.xml/+server.ts +13 -0
  30. package/packages/app/src/routes/sitemap.xml/+server.ts +19 -0
  31. package/packages/app/src/routes/tags/+page.server.ts +4 -0
  32. package/packages/app/src/routes/tags/+page.svelte +26 -0
  33. package/packages/app/src/routes/tags/[tag]/+page.server.ts +15 -0
  34. package/packages/app/src/routes/tags/[tag]/+page.svelte +23 -0
  35. package/packages/app/src/routes/topics/+page.server.ts +28 -0
  36. package/packages/app/src/routes/topics/+page.svelte +47 -0
  37. package/packages/app/src/routes/writing/+page.server.ts +12 -0
  38. package/packages/app/src/routes/writing/+page.svelte +38 -0
  39. package/packages/app/src/site-theme.d.ts +2 -0
  40. package/packages/app/static/.gitkeep +0 -0
  41. package/packages/app/tsconfig.json +15 -0
  42. package/packages/app/vite-plugin-critical-theme.ts +70 -0
  43. package/packages/app/vite-plugin-genie.ts +114 -0
  44. package/packages/app/vite.config.ts +141 -0
  45. package/packages/core/package.json +51 -0
  46. package/packages/core/src/lib/assets/favicon.svg +1 -0
  47. package/packages/core/src/lib/components/Newsletter.svelte +13 -0
  48. package/packages/core/src/lib/components/PostCard.svelte +34 -0
  49. package/packages/core/src/lib/components/PostIndex.svelte +86 -0
  50. package/packages/core/src/lib/components/SiteFooter.svelte +15 -0
  51. package/packages/core/src/lib/components/SiteHeader.svelte +28 -0
  52. package/packages/core/src/lib/config.ts +154 -0
  53. package/packages/core/src/lib/content/content.ts +193 -0
  54. package/packages/core/src/lib/content/feeds.ts +102 -0
  55. package/packages/core/src/lib/content/markdown.ts +78 -0
  56. package/packages/core/src/lib/content/pages.ts +103 -0
  57. package/packages/core/src/lib/content/parse.ts +214 -0
  58. package/packages/core/src/lib/content/rehype-figure.ts +79 -0
  59. package/packages/core/src/lib/content/types.ts +88 -0
  60. package/packages/core/src/lib/format.ts +25 -0
  61. package/packages/core/src/lib/index.ts +27 -0
  62. package/packages/core/src/lib/server.ts +33 -0
  63. package/packages/core/src/lib/styles/fonts.css +72 -0
  64. package/packages/core/src/lib/styles/theme.css +762 -0
  65. package/packages/core/src/lib/theme.ts +5 -0
  66. package/packages/import/package.json +27 -0
  67. package/packages/import/src/cli.ts +346 -0
  68. package/packages/import/src/discover.ts +190 -0
  69. package/packages/import/src/extract.ts +378 -0
  70. package/packages/import/src/fetch.ts +63 -0
  71. package/packages/import/src/html-to-md.ts +21 -0
  72. package/packages/import/src/images.ts +247 -0
  73. package/packages/import/src/inspire.ts +417 -0
  74. package/packages/import/src/ir.ts +109 -0
  75. package/packages/import/src/ollama.ts +145 -0
  76. package/packages/import/src/stock.ts +218 -0
  77. package/packages/import/src/theme.ts +478 -0
  78. package/packages/import/src/write-site.ts +371 -0
  79. package/packages/import/tsconfig.json +13 -0
  80. package/pnpm-workspace.yaml +2 -0
  81. package/scripts/create-site.mjs +260 -0
  82. package/scripts/filepress.mjs +273 -0
  83. package/scripts/link-embedded-packages.mjs +40 -0
  84. package/scripts/postinstall.mjs +56 -0
@@ -0,0 +1,79 @@
1
+ /**
2
+ * A tiny rehype transform: a paragraph whose only meaningful child is an image
3
+ * becomes a `<figure>`, and the image's `title` (falling back to `alt`) becomes
4
+ * a `<figcaption>`. This gives Ben Evans–style captioned images from plain
5
+ * Markdown (`![alt](src "Caption")`) with no new authoring syntax.
6
+ *
7
+ * Kept dependency-free (no unist-util-visit) so the content pipeline stays lean
8
+ * and easy to unit-test.
9
+ */
10
+
11
+ interface HastNode {
12
+ type: string;
13
+ tagName?: string;
14
+ value?: string;
15
+ properties?: Record<string, unknown>;
16
+ children?: HastNode[];
17
+ }
18
+
19
+ function isWhitespaceText(node: HastNode): boolean {
20
+ return node.type === 'text' && (node.value ?? '').trim() === '';
21
+ }
22
+
23
+ /** The single <img> element inside a node's children, or null if it isn't image-only. */
24
+ function loneImage(children: HastNode[]): HastNode | null {
25
+ const meaningful = children.filter((c) => !isWhitespaceText(c));
26
+ if (meaningful.length !== 1) return null;
27
+ const only = meaningful[0];
28
+ if (only.type === 'element' && only.tagName === 'img') return only;
29
+ // Support a linked image: <a><img/></a>
30
+ if (
31
+ only.type === 'element' &&
32
+ only.tagName === 'a' &&
33
+ only.children &&
34
+ only.children.length === 1 &&
35
+ only.children[0].type === 'element' &&
36
+ only.children[0].tagName === 'img'
37
+ ) {
38
+ return only.children[0];
39
+ }
40
+ return null;
41
+ }
42
+
43
+ function captionText(img: HastNode): string {
44
+ const props = img.properties ?? {};
45
+ const title = typeof props.title === 'string' ? props.title.trim() : '';
46
+ if (title) return title;
47
+ const alt = typeof props.alt === 'string' ? props.alt.trim() : '';
48
+ return alt;
49
+ }
50
+
51
+ export function rehypeFigure() {
52
+ return (tree: HastNode) => {
53
+ const children = tree.children;
54
+ if (!children) return;
55
+ for (let i = 0; i < children.length; i++) {
56
+ const node = children[i];
57
+ if (node.type !== 'element' || node.tagName !== 'p' || !node.children) continue;
58
+ const img = loneImage(node.children);
59
+ if (!img) continue;
60
+
61
+ const caption = captionText(img);
62
+ const figureChildren: HastNode[] = [...node.children];
63
+ if (caption) {
64
+ figureChildren.push({
65
+ type: 'element',
66
+ tagName: 'figcaption',
67
+ properties: {},
68
+ children: [{ type: 'text', value: caption }]
69
+ });
70
+ }
71
+ children[i] = {
72
+ type: 'element',
73
+ tagName: 'figure',
74
+ properties: {},
75
+ children: figureChildren
76
+ };
77
+ }
78
+ };
79
+ }
@@ -0,0 +1,88 @@
1
+ /** Frontmatter as authored in a post's YAML block (before validation). */
2
+ export interface RawFrontmatter {
3
+ title?: unknown;
4
+ date?: unknown;
5
+ slug?: unknown;
6
+ description?: unknown;
7
+ excerpt?: unknown;
8
+ tags?: unknown;
9
+ draft?: unknown;
10
+ updated?: unknown;
11
+ author?: unknown;
12
+ }
13
+
14
+ /** Validated, normalized metadata for a single post. */
15
+ export interface PostMeta {
16
+ /** URL path segment, e.g. "my-first-post". */
17
+ slug: string;
18
+ title: string;
19
+ /** Publish date, strict YYYY-MM-DD. */
20
+ date: string;
21
+ /** Optional last-updated date, strict YYYY-MM-DD. */
22
+ updated: string | null;
23
+ /** Short summary for listings and SEO; null if not provided. */
24
+ description: string | null;
25
+ /** Normalized (lowercased, trimmed, de-duplicated) tags. */
26
+ tags: string[];
27
+ /** Optional per-post author byline; null falls back to the site author in views. */
28
+ author: string | null;
29
+ draft: boolean;
30
+ /** Source file path relative to the repo root, e.g. "/posts/foo.md". */
31
+ sourcePath: string;
32
+ }
33
+
34
+ /** A post's metadata plus its raw (uncompiled) Markdown body. */
35
+ export interface PostSource extends PostMeta {
36
+ body: string;
37
+ }
38
+
39
+ /** A fully rendered post: metadata plus compiled HTML body. */
40
+ export interface RenderedPost extends PostMeta {
41
+ html: string;
42
+ }
43
+
44
+ /** Frontmatter as authored in a static page's YAML block (before validation). */
45
+ export interface RawPageFrontmatter {
46
+ title?: unknown;
47
+ slug?: unknown;
48
+ description?: unknown;
49
+ excerpt?: unknown;
50
+ draft?: unknown;
51
+ /** Optional nav/sort weight; lower first. */
52
+ order?: unknown;
53
+ }
54
+
55
+ /** Validated metadata for a static Markdown page (`pages/*.md` → `/<slug>`). */
56
+ export interface PageMeta {
57
+ slug: string;
58
+ title: string;
59
+ description: string | null;
60
+ draft: boolean;
61
+ /** Sort weight for listings/nav helpers; default 0. */
62
+ order: number;
63
+ /** Source file path, e.g. "/pages/about.md". */
64
+ sourcePath: string;
65
+ }
66
+
67
+ export interface PageSource extends PageMeta {
68
+ body: string;
69
+ }
70
+
71
+ export interface RenderedPage extends PageMeta {
72
+ html: string;
73
+ }
74
+
75
+ /**
76
+ * Path segments reserved by the engine. A `pages/<slug>.md` file must not use
77
+ * these (or collide with another page slug).
78
+ */
79
+ export const RESERVED_PAGE_SLUGS = [
80
+ 'posts',
81
+ 'writing',
82
+ 'tags',
83
+ 'topics',
84
+ 'page',
85
+ 'rss.xml',
86
+ 'sitemap.xml',
87
+ 'robots.txt'
88
+ ] as const;
@@ -0,0 +1,25 @@
1
+ const MONTHS = [
2
+ 'January',
3
+ 'February',
4
+ 'March',
5
+ 'April',
6
+ 'May',
7
+ 'June',
8
+ 'July',
9
+ 'August',
10
+ 'September',
11
+ 'October',
12
+ 'November',
13
+ 'December'
14
+ ];
15
+
16
+ /**
17
+ * Format a strict YYYY-MM-DD string as "4 July 2026". Parsed as UTC parts so the
18
+ * displayed date never shifts by a day due to the runner's timezone (see the
19
+ * SSR-vs-build Date lesson) — and since the site is fully prerendered, this is
20
+ * computed once at build time.
21
+ */
22
+ export function formatDate(iso: string): string {
23
+ const [y, m, d] = iso.split('-').map(Number);
24
+ return `${d} ${MONTHS[m - 1]} ${y}`;
25
+ }
@@ -0,0 +1,27 @@
1
+ // Client-safe public API for @filepress/core. No filesystem or env access here,
2
+ // so it is safe to import from universal/client code. Server-only helpers
3
+ // (content loader, feed builders) live in `@filepress/core/server`.
4
+
5
+ export { default as PostCard } from './components/PostCard.svelte';
6
+ export { default as PostIndex } from './components/PostIndex.svelte';
7
+ export { default as Newsletter } from './components/Newsletter.svelte';
8
+ export { default as SiteHeader } from './components/SiteHeader.svelte';
9
+ export { default as SiteFooter } from './components/SiteFooter.svelte';
10
+
11
+ export {
12
+ defineFilepressConfig,
13
+ absoluteUrl,
14
+ ogImageUrl,
15
+ postsIndexPath
16
+ } from './config';
17
+ export type {
18
+ SiteConfig,
19
+ SiteConfigInput,
20
+ NewsletterConfig,
21
+ Topic,
22
+ NavItem
23
+ } from './config';
24
+
25
+ export { formatDate } from './format';
26
+
27
+ export type { PostMeta, PostSource, RenderedPost, RawFrontmatter } from './content/types';
@@ -0,0 +1,33 @@
1
+ // Server-only public API for @filepress/core. These modules touch the
2
+ // filesystem (content loader) and must only be imported from server code
3
+ // (`+page.server.ts`, `+server.ts`, or a `*.server.ts` lib module).
4
+
5
+ export { createContent, resolveListDrafts } from './content/content';
6
+ export type { CreateContentOptions } from './content/content';
7
+ export type { ContentApi } from './content/content';
8
+ export { createPages } from './content/pages';
9
+ export type { CreatePagesOptions, PagesApi } from './content/pages';
10
+ export { renderMarkdown } from './content/markdown';
11
+ export { buildRssXml, buildSitemapXml, buildRobotsTxt } from './content/feeds';
12
+ export {
13
+ ContentError,
14
+ parsePost,
15
+ parsePage,
16
+ slugify,
17
+ normalizeTag,
18
+ assertUniqueSlugs,
19
+ assertValidDate,
20
+ filenameOf
21
+ } from './content/parse';
22
+
23
+ export { absoluteUrl, ogImageUrl } from './config';
24
+ export type { SiteConfig } from './config';
25
+ export type {
26
+ PostMeta,
27
+ PostSource,
28
+ RenderedPost,
29
+ PageMeta,
30
+ PageSource,
31
+ RenderedPage
32
+ } from './content/types';
33
+ export { RESERVED_PAGE_SLUGS } from './content/types';
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Latin + latin-ext only — avoids shipping cyrillic/greek/vietnamese slices
3
+ * from the full @fontsource-variable index CSS (~half the demo font payload).
4
+ */
5
+ @font-face {
6
+ font-family: 'Inter Variable';
7
+ font-style: normal;
8
+ font-display: swap;
9
+ font-weight: 100 900;
10
+ src: url('@fontsource-variable/inter/files/inter-latin-ext-wght-normal.woff2')
11
+ format('woff2-variations');
12
+ unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
13
+ U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
14
+ U+2113, U+2C60-2C7F, U+A720-A7FF;
15
+ }
16
+
17
+ @font-face {
18
+ font-family: 'Inter Variable';
19
+ font-style: normal;
20
+ font-display: swap;
21
+ font-weight: 100 900;
22
+ src: url('@fontsource-variable/inter/files/inter-latin-wght-normal.woff2')
23
+ format('woff2-variations');
24
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
25
+ U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
26
+ }
27
+
28
+ @font-face {
29
+ font-family: 'Newsreader Variable';
30
+ font-style: normal;
31
+ font-display: swap;
32
+ font-weight: 200 800;
33
+ src: url('@fontsource-variable/newsreader/files/newsreader-latin-ext-wght-normal.woff2')
34
+ format('woff2-variations');
35
+ unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
36
+ U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
37
+ U+2113, U+2C60-2C7F, U+A720-A7FF;
38
+ }
39
+
40
+ @font-face {
41
+ font-family: 'Newsreader Variable';
42
+ font-style: normal;
43
+ font-display: swap;
44
+ font-weight: 200 800;
45
+ src: url('@fontsource-variable/newsreader/files/newsreader-latin-wght-normal.woff2')
46
+ format('woff2-variations');
47
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
48
+ U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
49
+ }
50
+
51
+ @font-face {
52
+ font-family: 'Newsreader Variable';
53
+ font-style: italic;
54
+ font-display: swap;
55
+ font-weight: 200 800;
56
+ src: url('@fontsource-variable/newsreader/files/newsreader-latin-ext-standard-italic.woff2')
57
+ format('woff2-variations');
58
+ unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
59
+ U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
60
+ U+2113, U+2C60-2C7F, U+A720-A7FF;
61
+ }
62
+
63
+ @font-face {
64
+ font-family: 'Newsreader Variable';
65
+ font-style: italic;
66
+ font-display: swap;
67
+ font-weight: 200 800;
68
+ src: url('@fontsource-variable/newsreader/files/newsreader-latin-standard-italic.woff2')
69
+ format('woff2-variations');
70
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
71
+ U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
72
+ }