folderblog 0.0.1 → 0.0.2

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 (42) hide show
  1. package/README.md +109 -48
  2. package/dist/chunk-24MKFHML.cjs +143 -0
  3. package/dist/chunk-3RG5ZIWI.js +8 -0
  4. package/dist/chunk-4ZJGUMHS.cjs +78 -0
  5. package/dist/chunk-HMQIQUPB.cjs +387 -0
  6. package/dist/chunk-IXP35S24.js +1715 -0
  7. package/dist/chunk-OBGZSXTJ.cjs +10 -0
  8. package/dist/chunk-PARGDJNY.js +76 -0
  9. package/dist/chunk-QA4KPPTA.cjs +1787 -0
  10. package/dist/chunk-XP5J4LFJ.js +127 -0
  11. package/dist/chunk-ZRUBI3GH.js +370 -0
  12. package/dist/cli/bin.cjs +25 -0
  13. package/dist/cli/bin.d.cts +1 -0
  14. package/dist/cli/bin.d.ts +1 -0
  15. package/dist/cli/bin.js +23 -0
  16. package/dist/cli/index.cjs +22 -0
  17. package/dist/cli/index.d.cts +39 -0
  18. package/dist/cli/index.d.ts +39 -0
  19. package/dist/cli/index.js +15 -0
  20. package/dist/config-DFr-htlO.d.cts +887 -0
  21. package/dist/config-DFr-htlO.d.ts +887 -0
  22. package/dist/index.cjs +488 -1
  23. package/dist/index.d.cts +76 -8
  24. package/dist/index.d.ts +76 -8
  25. package/dist/index.js +153 -1
  26. package/dist/processor/index.cjs +337 -0
  27. package/dist/processor/index.d.cts +491 -0
  28. package/dist/processor/index.d.ts +491 -0
  29. package/dist/processor/index.js +4 -0
  30. package/dist/processor/plugins.cjs +51 -0
  31. package/dist/processor/plugins.d.cts +174 -0
  32. package/dist/processor/plugins.d.ts +174 -0
  33. package/dist/processor/plugins.js +2 -0
  34. package/dist/processor/types.cjs +67 -0
  35. package/dist/processor/types.d.cts +47 -0
  36. package/dist/processor/types.d.ts +47 -0
  37. package/dist/processor/types.js +2 -0
  38. package/dist/server/index.cjs +36 -0
  39. package/dist/server/index.d.cts +56 -0
  40. package/dist/server/index.d.ts +56 -0
  41. package/dist/server/index.js +34 -0
  42. package/package.json +63 -11
package/README.md CHANGED
@@ -1,87 +1,148 @@
1
1
  # folderblog
2
2
 
3
- TypeScript SDK for **[folder.blog](https://folder.blog)**git-native blogging.
4
-
5
- Zero dependencies. Works in Node.js, Deno, Bun, and browsers.
3
+ Official SDK for [folder.blog](https://folder.blog) — API client, server middleware, markdown processor, and CLI in one package.
6
4
 
7
5
  ## Install
8
6
 
9
7
  ```bash
10
- npm install folderblog
8
+ npm i folderblog
11
9
  ```
12
10
 
13
- ## Usage
11
+ ## API Client
12
+
13
+ Read posts from any folder.blog site:
14
14
 
15
- ```typescript
16
- import { folderBlog } from 'folderblog';
15
+ ```ts
16
+ import { folderBlog } from "folderblog";
17
17
 
18
- const blog = folderBlog('yourname.folder.blog');
18
+ const blog = folderBlog("yourname.folder.blog");
19
19
 
20
20
  const posts = await blog.posts.list();
21
- const post = await blog.posts.get('hello-world');
21
+ const post = await blog.posts.get("hello-world");
22
22
  const site = await blog.site.get();
23
+ const tags = await blog.tags.list();
24
+ const rss = await blog.feed.rss();
23
25
  ```
24
26
 
25
- ## API
27
+ ## Markdown Processor
26
28
 
27
- ### `blog.posts.list()`
29
+ Process a folder of markdown files into structured JSON:
28
30
 
29
- ```typescript
30
- const posts = await blog.posts.list();
31
+ ```ts
32
+ import { processFolder } from "folderblog";
33
+
34
+ const result = await processFolder({
35
+ dir: { input: "./content", output: "./dist" },
36
+ });
31
37
 
32
- posts[0].slug // "hello-world"
33
- posts[0].title // "Hello World"
34
- posts[0].date // "2024-01-15"
35
- posts[0].tags // ["intro"]
36
- posts[0].excerpt // "My first post..."
37
- posts[0].url // "/api/posts/hello-world"
38
+ console.log(`${result.posts.length} posts processed`);
38
39
  ```
39
40
 
40
- ### `blog.posts.get(slug)`
41
+ Enable wiki-link resolution (Obsidian-style `[[links]]`):
42
+
43
+ ```ts
44
+ const result = await processFolder({
45
+ dir: { input: "./vault", output: "./dist" },
46
+ pipeline: { wikiLinks: true },
47
+ });
48
+ ```
49
+
50
+ ### Using the Processor class directly
51
+
52
+ ```ts
53
+ import { Processor } from "folderblog";
41
54
 
42
- ```typescript
43
- const post = await blog.posts.get('hello-world');
55
+ const processor = new Processor({
56
+ config: {
57
+ dir: { input: "./content", output: "./dist" },
58
+ pipeline: { gfm: true, allowRawHtml: true },
59
+ },
60
+ });
44
61
 
45
- post.content // "<p>Full HTML here...</p>"
46
- post.raw // "# Hello World\n\nFull markdown..."
47
- post.images // ["/posts/images/photo.jpg"]
62
+ await processor.initialize();
63
+ const result = await processor.process();
64
+ await processor.dispose();
48
65
  ```
49
66
 
50
- ### `blog.site.get()`
67
+ ### Markdown utilities
51
68
 
52
- ```typescript
53
- const site = await blog.site.get();
69
+ ```ts
70
+ import { processMarkdown, parseFrontmatter, toSlug } from "folderblog";
54
71
 
55
- site.site // "My Blog"
56
- site.variant // "light" | "dark" | "serif" | "mono"
57
- site.postsCount // 12
58
- site.url // "https://yourname.folder.blog"
72
+ const { html, frontmatter } = await processMarkdown("# Hello\n\nWorld");
73
+ const slug = toSlug("My Blog Post"); // 'my-blog-post'
59
74
  ```
60
75
 
61
- ## Error Handling
76
+ ## Server Middleware
62
77
 
63
- ```typescript
64
- import { folderBlog, NotFoundError } from 'folderblog';
78
+ Proxy a folder.blog into your app. Works with any Web Standard Request/Response framework:
65
79
 
66
- try {
67
- await blog.posts.get('missing');
68
- } catch (error) {
69
- if (error instanceof NotFoundError) {
70
- // 404
71
- }
72
- }
80
+ ```ts
81
+ import { createHandler } from "folderblog/server";
82
+
83
+ const handler = createHandler({
84
+ domain: "yourname.folder.blog",
85
+ basePath: "/blog",
86
+ });
87
+
88
+ // Hono
89
+ app.get("/blog/*", (c) => handler(c.req.raw));
90
+
91
+ // Node.js / any Web Standard server
92
+ const response = await handler(request);
73
93
  ```
74
94
 
75
- ## Types
95
+ ## CLI
76
96
 
77
- ```typescript
78
- import type { Post, PostSummary, Site } from 'folderblog';
97
+ ```bash
98
+ npx folderblog build # Auto-detect config
99
+ npx folderblog build -i ./content # Specify input dir
100
+ npx folderblog build -o ./dist # Specify output dir
101
+ npx folderblog build -c my.config.js # Custom config file
102
+ ```
103
+
104
+ Config file (auto-detected as `folderblog.config.{js,mjs,ts}`):
105
+
106
+ ```js
107
+ export default {
108
+ dir: { input: "./content", output: "./dist" },
109
+ pipeline: { gfm: true, wikiLinks: true },
110
+ };
111
+ ```
112
+
113
+ ## Sub-path Imports
114
+
115
+ For tree-shaking, import from specific sub-paths:
116
+
117
+ ```ts
118
+ import { Processor } from "folderblog/processor";
119
+ import { PluginManager } from "folderblog/processor/plugins";
120
+ import type { ProcessConfig, ProcessedPost } from "folderblog/processor/types";
121
+ import { createHandler } from "folderblog/server";
122
+ import { build } from "folderblog/cli";
79
123
  ```
80
124
 
81
- ## Links
125
+ Everything is also available from the main `'folderblog'` entry point.
82
126
 
83
- - **Website:** [folder.blog](https://folder.blog)
84
- - **Docs:** [folder.blog/docs](https://folder.blog/docs)
127
+ ## Error Handling
128
+
129
+ ```ts
130
+ import { folderBlog, NotFoundError, ApiError, NetworkError } from "folderblog";
131
+
132
+ const blog = folderBlog("yourname.folder.blog");
133
+
134
+ try {
135
+ const post = await blog.posts.get("missing");
136
+ } catch (err) {
137
+ if (err instanceof NotFoundError) {
138
+ console.log("Post not found");
139
+ } else if (err instanceof ApiError) {
140
+ console.log(`API error: ${err.status}`);
141
+ } else if (err instanceof NetworkError) {
142
+ console.log("Network failure", err.cause);
143
+ }
144
+ }
145
+ ```
85
146
 
86
147
  ## License
87
148
 
@@ -0,0 +1,143 @@
1
+ 'use strict';
2
+
3
+ // ../processor/src/types/config.ts
4
+ var DEFAULT_IMAGE_SIZES = [
5
+ { width: 320, height: null, suffix: "xs" },
6
+ { width: 640, height: null, suffix: "sm" },
7
+ { width: 1024, height: null, suffix: "md" },
8
+ { width: 1920, height: null, suffix: "lg" },
9
+ { width: 3840, height: null, suffix: "xl" }
10
+ ];
11
+ var withDefaults = (config) => ({
12
+ dir: config.dir,
13
+ plugins: config.plugins ?? {},
14
+ media: {
15
+ optimize: true,
16
+ sizes: DEFAULT_IMAGE_SIZES,
17
+ format: "webp",
18
+ quality: 80,
19
+ useHash: true,
20
+ useSharding: false,
21
+ pathPrefix: "/_media",
22
+ ...config.media
23
+ },
24
+ content: {
25
+ notePathPrefix: "/content",
26
+ processAllFiles: false,
27
+ ignoreFiles: ["CONTRIBUTING.md", "README.md", "readme.md", "LICENSE.md"],
28
+ exportPosts: false,
29
+ trackRelationships: false,
30
+ includeSlugTracking: false,
31
+ slugConflictStrategy: "number",
32
+ slugScope: "global",
33
+ ...config.content
34
+ },
35
+ mermaid: {
36
+ enabled: true,
37
+ strategy: "inline-svg",
38
+ dark: false,
39
+ ...config.mermaid
40
+ },
41
+ pipeline: {
42
+ gfm: true,
43
+ allowRawHtml: true,
44
+ syntaxHighlighting: true,
45
+ parseFormulas: false,
46
+ removeDeadLinks: false,
47
+ ...config.pipeline
48
+ },
49
+ debug: {
50
+ level: 0,
51
+ timing: false,
52
+ ...config.debug
53
+ },
54
+ cache: config.cache
55
+ });
56
+ var getOutputDir = (config) => config.dir.output ?? `${config.dir.input}/dist`;
57
+ var getMediaOutputDir = (config) => {
58
+ const outputDir = getOutputDir(config);
59
+ const mediaSubdir = config.dir.mediaOutput ?? "_media";
60
+ return `${outputDir}/${mediaSubdir}`;
61
+ };
62
+
63
+ // ../processor/src/types/output.ts
64
+ var OUTPUT_FILES = {
65
+ POSTS: "posts.json",
66
+ POSTS_SLUG_MAP: "posts-slug-map.json",
67
+ POSTS_PATH_MAP: "posts-path-map.json",
68
+ MEDIAS: "media.json",
69
+ MEDIA_PATH_MAP: "media-path-map.json",
70
+ GRAPH: "graph.json",
71
+ TEXT_EMBEDDINGS: "posts-embedding-hash-map.json",
72
+ IMAGE_EMBEDDINGS: "media-embedding-hash-map.json",
73
+ SIMILARITY: "similarity.json",
74
+ DATABASE: "repo.db",
75
+ ISSUES: "processor-issues.json"
76
+ };
77
+
78
+ // ../processor/src/types/issues.ts
79
+ var isBrokenLinkIssue = (issue) => issue.category === "broken-link";
80
+ var isMissingMediaIssue = (issue) => issue.category === "missing-media";
81
+ var isMediaProcessingIssue = (issue) => issue.category === "media-processing";
82
+ var isSlugConflictIssue = (issue) => issue.category === "slug-conflict";
83
+ var isMermaidErrorIssue = (issue) => issue.category === "mermaid-error";
84
+ var isEmbeddingErrorIssue = (issue) => issue.category === "embedding-error";
85
+ var isPluginErrorIssue = (issue) => issue.category === "plugin-error";
86
+
87
+ // ../processor/src/types/cache.ts
88
+ var createEmptyCacheStats = () => ({
89
+ mediaCacheHits: 0,
90
+ mediaCacheMisses: 0,
91
+ textEmbeddingCacheHits: 0,
92
+ textEmbeddingCacheMisses: 0,
93
+ imageEmbeddingCacheHits: 0,
94
+ imageEmbeddingCacheMisses: 0
95
+ });
96
+ function buildMediaCacheFromManifest(medias) {
97
+ const cache = /* @__PURE__ */ new Map();
98
+ for (const media of medias) {
99
+ const hash = media.metadata?.hash;
100
+ if (!hash || !media.metadata?.width || !media.metadata?.height) continue;
101
+ cache.set(hash, {
102
+ width: media.metadata.width,
103
+ height: media.metadata.height,
104
+ format: media.metadata.format ?? "webp",
105
+ size: media.metadata.size ?? 0,
106
+ originalSize: media.metadata.originalSize,
107
+ outputPath: media.outputPath,
108
+ sizes: (media.sizes ?? []).map((s) => ({
109
+ suffix: s.suffix,
110
+ outputPath: s.outputPath,
111
+ width: s.width,
112
+ height: s.height,
113
+ size: s.size
114
+ }))
115
+ });
116
+ }
117
+ return cache;
118
+ }
119
+ function buildEmbeddingCacheFromManifest(embeddingMap) {
120
+ const cache = /* @__PURE__ */ new Map();
121
+ for (const [hash, embedding] of Object.entries(embeddingMap)) {
122
+ if (Array.isArray(embedding) && embedding.length > 0) {
123
+ cache.set(hash, embedding);
124
+ }
125
+ }
126
+ return cache;
127
+ }
128
+
129
+ exports.DEFAULT_IMAGE_SIZES = DEFAULT_IMAGE_SIZES;
130
+ exports.OUTPUT_FILES = OUTPUT_FILES;
131
+ exports.buildEmbeddingCacheFromManifest = buildEmbeddingCacheFromManifest;
132
+ exports.buildMediaCacheFromManifest = buildMediaCacheFromManifest;
133
+ exports.createEmptyCacheStats = createEmptyCacheStats;
134
+ exports.getMediaOutputDir = getMediaOutputDir;
135
+ exports.getOutputDir = getOutputDir;
136
+ exports.isBrokenLinkIssue = isBrokenLinkIssue;
137
+ exports.isEmbeddingErrorIssue = isEmbeddingErrorIssue;
138
+ exports.isMediaProcessingIssue = isMediaProcessingIssue;
139
+ exports.isMermaidErrorIssue = isMermaidErrorIssue;
140
+ exports.isMissingMediaIssue = isMissingMediaIssue;
141
+ exports.isPluginErrorIssue = isPluginErrorIssue;
142
+ exports.isSlugConflictIssue = isSlugConflictIssue;
143
+ exports.withDefaults = withDefaults;
@@ -0,0 +1,8 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ export { __require };
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ var chunkQA4KPPTA_cjs = require('./chunk-QA4KPPTA.cjs');
4
+ var path = require('path');
5
+ var url = require('url');
6
+ var fs = require('fs');
7
+ var child_process = require('child_process');
8
+
9
+ async function loadConfig(configPath) {
10
+ const abs = path.resolve(configPath);
11
+ try {
12
+ const mod = await import(url.pathToFileURL(abs).href);
13
+ return mod.default ?? mod;
14
+ } catch {
15
+ }
16
+ if (abs.endsWith(".ts")) {
17
+ try {
18
+ const script = `import{pathToFileURL}from"url";const m=await import(pathToFileURL(${JSON.stringify(abs)}).href);process.stdout.write(JSON.stringify(m.default??m))`;
19
+ const json = child_process.execFileSync(
20
+ process.execPath,
21
+ ["--experimental-strip-types", "--input-type=module", "-e", script],
22
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
23
+ );
24
+ return JSON.parse(json);
25
+ } catch {
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+ function resolveConfigPath(explicit) {
31
+ if (explicit) return explicit;
32
+ for (const ext of [".js", ".mjs", ".ts"]) {
33
+ const candidate = `./folderblog.config${ext}`;
34
+ if (fs.existsSync(path.resolve(candidate))) return candidate;
35
+ }
36
+ return "./folderblog.config.js";
37
+ }
38
+ async function build(options = {}) {
39
+ const configPath = resolveConfigPath(options.config);
40
+ let config = await loadConfig(configPath);
41
+ if (!config) {
42
+ config = {
43
+ dir: {
44
+ input: options.input ?? "./src",
45
+ output: options.output ?? "./dist"
46
+ }
47
+ };
48
+ } else {
49
+ if (options.input || options.output) {
50
+ config = {
51
+ ...config,
52
+ dir: {
53
+ ...config.dir,
54
+ ...options.input ? { input: options.input } : {},
55
+ ...options.output ? { output: options.output } : {}
56
+ }
57
+ };
58
+ }
59
+ }
60
+ console.log(`Processing: ${path.resolve(config.dir.input)}`);
61
+ const result = await chunkQA4KPPTA_cjs.processFolder(config);
62
+ const errorCount = result.issues.summary?.errorCount ?? 0;
63
+ const warningCount = result.issues.summary?.warningCount ?? 0;
64
+ console.log(`
65
+ Done.`);
66
+ console.log(` Posts: ${result.posts.length}`);
67
+ console.log(` Media: ${result.media.length}`);
68
+ console.log(` Output: ${path.resolve(result.outputDir)}`);
69
+ if (errorCount > 0 || warningCount > 0) {
70
+ console.log(` Errors: ${errorCount}`);
71
+ console.log(` Warnings: ${warningCount}`);
72
+ }
73
+ if (errorCount > 0) {
74
+ process.exit(1);
75
+ }
76
+ }
77
+
78
+ exports.build = build;