noblog 0.2.2 → 0.3.0-beta.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.
Files changed (43) hide show
  1. package/README.md +277 -121
  2. package/dist/client-CkOIgSvW.js +549 -0
  3. package/dist/client-CkOIgSvW.js.map +1 -0
  4. package/dist/config-check.impl-BQvIrgNe.js +14 -0
  5. package/dist/config-check.impl-BQvIrgNe.js.map +1 -0
  6. package/dist/config-csbquMhF.js +21 -0
  7. package/dist/config-csbquMhF.js.map +1 -0
  8. package/dist/config-file-BF0G2iJq.js +27 -0
  9. package/dist/config-file-BF0G2iJq.js.map +1 -0
  10. package/dist/defaults-D9BbNYsd.js +8 -0
  11. package/dist/defaults-D9BbNYsd.js.map +1 -0
  12. package/dist/errors-DgJ15MsB.js +72 -0
  13. package/dist/errors-DgJ15MsB.js.map +1 -0
  14. package/dist/frontmatter-fields-BkXq38CO.js +71 -0
  15. package/dist/frontmatter-fields-BkXq38CO.js.map +1 -0
  16. package/dist/generate.d.ts +1 -0
  17. package/dist/generate.impl-BKu-Ix4a.js +33 -0
  18. package/dist/generate.impl-BKu-Ix4a.js.map +1 -0
  19. package/dist/generate.js +197 -932
  20. package/dist/generate.js.map +1 -1
  21. package/dist/index.d.ts +209 -18
  22. package/dist/index.js +5 -0
  23. package/package.json +32 -17
  24. package/schema/config.json +173 -0
  25. package/skills/noblog-config/SKILL.md +40 -0
  26. package/skills/noblog-config/assets/nobloger.yaml +30 -0
  27. package/skills/noblog-config/assets/thought-lite.yaml +47 -0
  28. package/skills/noblog-config/references/config-schema.md +108 -0
  29. package/skills/noblog-config/references/notion-property-types.md +42 -0
  30. package/skills/noblog-config/references/theme-examples.md +39 -0
  31. package/dist/cmds/generate.d.ts +0 -3
  32. package/dist/cmds/generate.d.ts.map +0 -1
  33. package/dist/generate.cjs +0 -962
  34. package/dist/generate.cjs.map +0 -1
  35. package/dist/index.d.ts.map +0 -1
  36. package/dist/lib/noblog/format.d.ts +0 -24
  37. package/dist/lib/noblog/format.d.ts.map +0 -1
  38. package/dist/lib/noblog/noblog.d.ts +0 -31
  39. package/dist/lib/noblog/noblog.d.ts.map +0 -1
  40. package/dist/lib/noblog/utils.d.ts +0 -36
  41. package/dist/lib/noblog/utils.d.ts.map +0 -1
  42. package/dist/lib/notion/client.d.ts +0 -8
  43. package/dist/lib/notion/client.d.ts.map +0 -1
package/README.md CHANGED
@@ -1,208 +1,364 @@
1
1
  # 📝 Noblog
2
2
 
3
- > Transform your Notion content into beautiful static blogs with Astro
3
+ > Generate Astro-compatible Markdown from your Notion database.
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/noblog.svg)](https://badge.fury.io/js/noblog)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
- Noblog generates static content from Notion databases and provides a pluggable component/theme marketplace for bloggers. Inspired by [notablog](https://github.com/dragonman225/notablog) but more flexible thanks to the power of Astro.
8
+ Noblog fetches published Notion pages, converts their blocks to Markdown, and writes a complete blog structure with Astro frontmatter. Each page lives once in a canonical store. Database posts also receive public symlinks, while child pages are referenced directly in the store. Custom output, store, and layout paths are supported.
9
9
 
10
- ## 🆚 Why Noblog vs notion-to-md?
10
+ The renderer includes logic derived from [notion-to-md](https://github.com/souvikinator/notion-to-md), with additional handling for nested formatting and blog generation.
11
11
 
12
- While Noblog uses [notion-to-md](https://github.com/souvikinator/notion-to-md) under the hood, it provides significant improvements for blog generation:
12
+ ## Requirements
13
13
 
14
- ### 🔧 **Enhanced Markdown Generation**
14
+ - Node.js 24 or newer
15
+ - A Notion integration with read access to your database
16
+ - A Notion database with `publish` and `date` properties
15
17
 
16
- **Problem with notion-to-md:**
17
- When dealing with complex formatting like nested styles (e.g., `{BLOCK1}` and `{BLOCK2}` both bold, but only `BLOCK1` also italic), notion-to-md generates malformed markdown:
18
+ ## Installation
18
19
 
19
- ```markdown
20
- _**BLOCK1**_**BLOCK2** # Broken: unmerged tags
20
+ ```bash
21
+ npm install -g noblog
21
22
  ```
22
23
 
23
- **Noblog's Solution:**
24
-
25
- - **Merged Tag Handling**: Properly merges nested formatting tags
26
- - **Clean Markdown Output**: Generates valid, renderer-friendly markdown
27
- - **Astro-Optimized**: Specifically designed for static site generation
28
-
29
- ### 🏗️ **Complete Blog Structure**
24
+ For use as a library in another package:
30
25
 
31
- **notion-to-md:** Just converts pages to markdown
26
+ ```bash
27
+ npm install noblog
28
+ ```
32
29
 
33
- **Noblog:**
30
+ ## Configuration
34
31
 
35
- - ✅ Automatic file structure generation
36
- - ✅ Parent/child page organization
37
- - ✅ Astro-compatible frontmatter
38
- - ✅ Customizable save paths
39
- - ✅ Layout path customization
40
- - ✅ Theme marketplace ready
32
+ Set the following environment variables in your shell or in a `.env` file in the directory where you run `noblog`:
41
33
 
42
- ### ⚡ **One-Command Solution**
34
+ ```bash
35
+ NOTION_API_SECRET="your-notion-api-secret"
36
+ NOBLOG_DATABASE_ID="your-notion-database-id"
37
+ ```
43
38
 
44
- Instead of manually handling notion-to-md output and building your own file structure:
39
+ The CLI loads `.env` automatically. Credentials are validated only when generating content, so `--help`, `--version`, and `config check` do not require them. Keep credentials in environment variables or `.env`; YAML configuration has no credential fields.
45
40
 
46
- ```bash
47
- # With Noblog - everything handled automatically
48
- noblog --layout ../../layouts/BlogLayout.astro
41
+ ## Usage
49
42
 
50
- # With notion-to-md - you'd need to build everything yourself
43
+ ```text
44
+ noblog [generate] [options] [SAVE_PATH] [SUB_PATH]
51
45
  ```
52
46
 
53
- > **Note:** Noblog includes improvements to notion-to-md's core parsing logic, fixing edge cases like unmerged formatting tags that can break markdown renderers.
47
+ Running `noblog` without arguments immediately generates content with the default settings. It does not print help.
54
48
 
55
- ## 🚀 Quick Start
49
+ ### Commands
56
50
 
57
- ### Installation
51
+ | Command | Description |
52
+ | --- | --- |
53
+ | `noblog` / `noblog generate` | Generate blog content |
54
+ | `noblog config check --config PATH` | Validate YAML without credentials or network access |
55
+ | `noblog help` | Show general help |
56
+ | `noblog version` | Show the installed version |
58
57
 
59
- ```bash
60
- npm install -g noblog
61
- ```
58
+ ### Options
62
59
 
63
- ### Basic Usage
60
+ | Option | Description |
61
+ | --- | --- |
62
+ | `-v, --verbose` | Print diagnostics and error stack traces |
63
+ | `--config PATH` | Load YAML configuration for generation |
64
+ | `--layout PATH` | Set the layout path relative to `SAVE_PATH` |
65
+ | `--store PATH` | Set the canonical Markdown store (default: `.noblog/pages/`) |
66
+ | `-h, --help` | Show general or command-specific help |
67
+ | `--version` | Show the installed version |
68
+ | `--` | Treat all remaining inputs as positional paths |
64
69
 
65
- ```bash
66
- noblog
67
- ```
70
+ ### Positional arguments
68
71
 
69
- That's it! Noblog will fetch your Notion content and generate static markdown files.
72
+ | Argument | Default | Description |
73
+ | --- | --- | --- |
74
+ | `SAVE_PATH` | `./src/pages/posts/` | Public view directory for database posts |
75
+ | `SUB_PATH` | — | Deprecated compatibility argument; ignored with a warning |
70
76
 
71
- ### 🛠️ Command-Line Options
72
-
73
- Noblog supports various command-line options to customize your blog generation:
77
+ ### Examples
74
78
 
75
79
  ```bash
76
- # Basic usage with default settings
80
+ # Generate with all defaults
77
81
  noblog
78
82
 
79
- # Custom layout path
83
+ # Equivalent explicit command
84
+ noblog generate
85
+
86
+ # Use a custom layout
80
87
  noblog --layout ../../layouts/BlogLayout.astro
81
88
 
82
- # Custom save and sub paths with layout
83
- noblog ./content/blog/ ./content/blog/subpages/ --layout ../../../layouts/CustomLayout.astro
89
+ # Write database-post symlinks to ./content/blog/
90
+ noblog ./content/blog/ --layout ../../layouts/CustomLayout.astro
84
91
 
85
- # Verbose output for debugging
86
- noblog -v
87
- # or
88
- noblog --verbose
89
- ```
92
+ # Store the real Markdown files in a custom canonical store
93
+ noblog --store content/.noblog-pages
90
94
 
91
- **Available Options:**
95
+ # Generate using YAML configuration
96
+ noblog --config ./noblog.yaml
92
97
 
93
- - `--layout PATH` - Custom layout path relative to the posts directory (default: `../../layouts/MarkdownPostLayout.astro`)
94
- - `-v, --verbose` - Print more messages for debugging
95
- - `-h, --help` - Show help message
96
- - `--version` - Show version information
98
+ # Validate configuration without credentials or Notion access
99
+ noblog config check --config ./noblog.yaml
97
100
 
98
- **Positional Arguments:**
101
+ # Print diagnostics
102
+ noblog generate -v
99
103
 
100
- - `SAVE_PATH` - Directory to save main posts (default: `./src/pages/posts/`)
101
- - `SUB_PATH` - Subdirectory for nested content (default: `nob_children/`)
104
+ # Show help for the generate command and its arguments
105
+ noblog generate --help
106
+ ```
107
+
108
+ ## YAML configuration
102
109
 
103
- ## 📋 Setup Guide
110
+ Pass `--config PATH` to load a versioned YAML file. Configuration is explicit; Noblog does not search for a YAML file automatically. Generation resolves each output setting with this precedence:
104
111
 
105
- ### 1. Create Your Notion Database
112
+ ```text
113
+ built-in defaults < YAML configuration < explicit CLI flags and SAVE_PATH
114
+ ```
106
115
 
107
- Duplicate this [template database](https://www.notion.so/humbornjo/fa0faae85c504934a4a86cfa70302850?v=2abd1079ae134fbd8df2604765baa1df) and start writing your blog posts in Notion.
116
+ For example, this config adapts output and frontmatter to a theme with a native date, custom Notion properties, and typed boolean/number fields:
117
+
118
+ ```yaml
119
+ version: 1
120
+ notion:
121
+ properties:
122
+ title: Name
123
+ publishedAt: Published
124
+ series: Series
125
+ sensitive: Sensitive
126
+ top: Top
127
+ output:
128
+ savePath: src/pages/posts/
129
+ storePath: .noblog/pages/
130
+ layoutPath: ../../layouts/MarkdownPostLayout.astro
131
+ recursive: true
132
+ frontmatter:
133
+ fields:
134
+ title:
135
+ from: title
136
+ required: true
137
+ timestamp:
138
+ from: publishedAt
139
+ as: date
140
+ required: true
141
+ series:
142
+ from: series
143
+ omitEmpty: true
144
+ sensitive:
145
+ from: sensitive
146
+ default: false
147
+ top:
148
+ from: top
149
+ default: 0
150
+ toc:
151
+ value: true
152
+ ```
108
153
 
109
- ### 2. Configure Environment Variables
154
+ The keys under `notion.properties` are logical source names; their values must exactly match Notion property names. Custom sources such as `series` must be declared there before frontmatter can reference them. Built-in sources need no declaration.
110
155
 
111
- Set up your Notion API credentials:
156
+ Validate a file without Notion credentials, a Notion client, or network access:
112
157
 
113
158
  ```bash
114
- export NOBLOG_DATABASE_ID="your-notion-database-id"
115
- export NOTION_API_SECRET="your-notion-api-secret"
159
+ noblog config check --config ./noblog.yaml
116
160
  ```
117
161
 
118
- ### 3. Choose a Template
162
+ The machine-readable JSON Schema is generated from the runtime Zod schema and shipped as `schema/config.json` (package export: `noblog/config.schema.json`). For a local installation, enable editor validation with:
119
163
 
120
- Pick an Astro template that supports Noblog. Here's a starter template: **[nobloger](https://github.com/humbornjo/nobloger)**
164
+ ```yaml
165
+ # yaml-language-server: $schema=./node_modules/noblog/schema/config.json
166
+ ```
121
167
 
122
- ### 4. Deploy Your Site
168
+ The schema checks structure and native types. `config check` additionally enforces cross-field rules such as declared custom sources. Invalid YAML, unsupported versions, unknown keys, and invalid field definitions exit with status 2.
123
169
 
124
- Run `noblog` to generate your content, then deploy your Astro site to your preferred hosting platform.
170
+ A standard Agent Skill is included at [`skills/noblog-config/SKILL.md`](skills/noblog-config/SKILL.md), with detailed references and Nobloger/ThoughtLite examples. Copy that directory into your agent's skills location or point the agent at it. The skill inspects the theme's actual collection schema, frontmatter consumers, and routes before generating YAML; it does not need or request Notion credentials.
125
171
 
126
- ## 📁 File Structure
172
+ ## Output structure
127
173
 
128
- By default, Noblog organizes your content like this:
174
+ With the default paths, Noblog names files by Notion page ID. All real Markdown files are flat in `.noblog/pages/`; database posts also have relative symlinks in `SAVE_PATH`:
129
175
 
130
- ```
176
+ ```text
131
177
  project-root/
178
+ ├── .noblog/
179
+ │ └── pages/
180
+ │ ├── <database-page-id>.md # real file
181
+ │ └── <child-page-id>.md # real file
132
182
  └── src/
133
183
  └── pages/
134
184
  └── posts/
135
- ├── blog-post-1.md # Direct database entries
136
- ├── blog-post-2.md
137
- └── nob_children/
138
- ├── sub-post-1.md # Referenced/child pages
139
- ├── sub-post-2.md
140
- └── sub-post-3.md
185
+ └── <database-page-id>.md # relative symlink
141
186
  ```
142
187
 
143
- ### Custom Save Paths
188
+ Only database posts receive public views. Child pages live exclusively in the canonical store, and child links use `./<child-page-id>.md` so they resolve as siblings after Vite/Astro resolves symlinks to real paths. Child pages therefore stay out of `src/pages/posts/*.md` listings. Use `--store PATH` to change the canonical store independently of the public path.
144
189
 
145
- You can customize the output directories:
190
+ **Routing migration:** Noblog no longer creates `/nob_children/` routes. Configure the theme to load and route child pages from `.noblog/pages/` (or the custom store). Existing nested view files are not deleted; remove or redirect them separately after updating the theme. Existing `SUB_PATH` arguments are ignored with a warning.
146
191
 
147
- ```bash
148
- noblog <SAVE_PATH> <SUB_PATH>
192
+ Generation is repeatable. Existing regular database-post view files with matching page IDs are replaced by links, and all canonical files are updated. Unrelated files are left unchanged; directories and other non-file outputs are not replaced.
149
193
 
150
- # Example:
151
- noblog ./content/blog/ ./content/blog/subpages/
152
- ```
194
+ The store and views must remain together when building or deploying. If generated content is committed, include both; otherwise regenerate before building. Creating the views requires filesystem symlink support. On Windows, this may require Developer Mode or elevated privileges.
153
195
 
154
- ## 🎯 Markdown Output
196
+ ## Frontmatter
155
197
 
156
- Generated markdown files include Astro-compatible frontmatter:
198
+ Without custom Notion or frontmatter mappings, generated files retain the legacy Astro-compatible frontmatter:
157
199
 
158
- ```markdown
200
+ ```yaml
159
201
  ---
160
- layout: ../../layouts/MarkdownPostLayout.astro
202
+ layout: ../../src/layouts/MarkdownPostLayout.astro
161
203
  title: "Your Blog Post Title"
162
- tags: ["tag1", "tag2", "tag3"]
163
- pubDate: 2024-01-24
164
- archived: false
165
204
  description: "Your post description here"
205
+ tags: ["tag1", "tag2", "tag3"]
206
+ pubDate: "2024-01-24"
207
+ archived: "false"
166
208
  ---
209
+ ```
167
210
 
168
- # Your content starts here...
211
+ When `frontmatter.fields` is present, it defines the complete output field set; legacy fields are not implicitly added. When only `notion.properties` is present, Noblog uses the declarative equivalent of the legacy fields (`layout`, `title`, `description`, `tags`, `pubDate`, and `archived`) with the configured property names.
212
+
213
+ Every custom field has exactly one of:
214
+
215
+ - `from`: read a built-in source or a key declared under `notion.properties`.
216
+ - `value`: emit a static native string, finite number, boolean, null, array, or nested object.
217
+
218
+ Built-in sources are `title`, `description`, `tags`, `publishedAt`, `archived`, `layoutPath`, `pageId`, and `isDatabasePage`. Without explicit property mappings, the conventional Notion names are `description`, `tags`, `date`, and `archived`; title comes from the last Notion title property.
219
+
220
+ Field behavior can be refined with:
221
+
222
+ | Directive | Behavior |
223
+ | --- | --- |
224
+ | `default` | Use this value when the source is empty |
225
+ | `required` | Fail generation if the value remains empty after defaults; errors include the field and page ID |
226
+ | `omitEmpty` | Omit an empty value after default/required processing |
227
+ | `as: string` | Convert a string, number, or boolean to a string; date-like strings remain quoted |
228
+ | `as: date` | Convert a valid date string or finite number to an unquoted YAML timestamp |
229
+
230
+ Empty means `undefined`, `null`, `""`, or `[]`; `false` and `0` are preserved. Processing order is source/static value, default, required check, omission, then conversion.
231
+
232
+ Notion values are normalized according to their actual property types. Text, select/status, dates, URLs, times, users, and unique IDs become strings; checkboxes and numeric properties remain native; multi-selects, people, relations, and files become string arrays. Formula results follow their own value type. Unsupported or missing properties become `undefined`, so defaults and omission rules apply. See the [property-type reference](skills/noblog-config/references/notion-property-types.md) for the complete table.
233
+
234
+ This makes native theme schemas possible without stringly-typed frontmatter: `as: string` can produce `pubDate: "2024-01-24"`, while `as: date` produces `timestamp: 2024-01-24`, and booleans/numbers remain unquoted unless conversion is requested.
235
+
236
+ Vite and Astro resolve Markdown symlinks to their real paths, so layout imports must be relative to the canonical store. The CLI interprets `--layout` relative to `SAVE_PATH`, then writes the equivalent store-relative path for every page.
237
+
238
+ ## Programmatic API
239
+
240
+ The package exposes a reusable renderer and filesystem writer. The optional Notion adapter is configured explicitly; importing `noblog` has no environment or network side effects.
241
+
242
+ ```ts
243
+ import {
244
+ createNotionSource,
245
+ renderPages,
246
+ resolveStoreLayoutPath,
247
+ writePages,
248
+ } from "noblog";
249
+
250
+ const notionApiSecret = process.env["NOTION_API_SECRET"];
251
+ const databaseId = process.env["NOBLOG_DATABASE_ID"];
252
+ if (!notionApiSecret || !databaseId) {
253
+ throw new Error("Notion credentials are required");
254
+ }
255
+
256
+ const notion = createNotionSource({ notionApiSecret, databaseId });
257
+
258
+ const savePath = "./content/blog/";
259
+ const storePath = ".noblog/pages/";
260
+ const layoutPath = resolveStoreLayoutPath({
261
+ savePath,
262
+ storePath,
263
+ layoutPath: "../../layouts/MarkdownPostLayout.astro",
264
+ });
265
+
266
+ const posts = await notion.listPages();
267
+ const pages = await renderPages(posts, notion, { layoutPath });
268
+
269
+ await writePages(pages, { savePath, storePath });
169
270
  ```
170
271
 
171
- ## 🔧 Requirements
272
+ `renderPages` recursively collects child pages by default and returns complete Markdown documents with frontmatter. Set `recursive: false` to render only the supplied database pages. `writePages` writes every canonical file but returns only the public database-post view paths. When rendering for a canonical store, use `resolveStoreLayoutPath` as above so layout imports remain valid after symlink resolution.
273
+
274
+ Use `renderPage` for a single page, or supply any implementation of the `NotionReader` interface instead of the built-in adapter. Lower-level helpers such as `renderRichText` and `renderFrontmatter` are exported as well. To customize a block, pass a `transformers` map in `RenderOptions`; return a string to override the default renderer or `undefined` to fall back to it. Each key narrows its handler's block payload to the corresponding Notion block type, so a `paragraph` transformer can access `block.paragraph` directly. The programmatic API does not load `.env`; load environment variables yourself or pass explicit configuration.
275
+
276
+ The same declarative mapping is available programmatically, or you can provide a `FrontmatterAdapter` function for full control:
277
+
278
+ ```ts
279
+ import { createFrontmatterAdapter, renderPages } from "noblog";
280
+
281
+ const frontmatter = createFrontmatterAdapter({
282
+ title: { from: "title", required: true },
283
+ timestamp: { from: "publishedAt", as: "date" },
284
+ draft: { from: "archived", default: false },
285
+ });
286
+
287
+ const pages = await renderPages(posts, notion, {
288
+ layoutPath,
289
+ propertyMap: { publishedAt: "Published" },
290
+ frontmatter,
291
+ });
292
+ ```
172
293
 
173
- **Any Astro template can be used with Noblog!** The only requirement is that your template includes a layout component.
294
+ An adapter receives `{ page, metadata, layoutPath, isDatabasePage }` and returns a plain object. It can use the raw Notion page or normalized metadata, and root `undefined` values are omitted. Values follow the exported `FrontmatterValue` contract: strings, numbers, booleans, `null`, `Date`, and readonly arrays/objects containing those values. Arrays, null, dates, and class instances are rejected as adapter roots.
174
295
 
175
- This means you can use:
296
+ ## Exit codes
176
297
 
177
- - ✅ Any existing Astro blog template
178
- - ✅ Your custom Astro theme
179
- - ✅ Popular Astro themes from the community
180
- - ✅ The starter template: **[nobloger](https://github.com/humbornjo/nobloger)**
298
+ | Code | Meaning |
299
+ | --- | --- |
300
+ | `0` | Help, version, successful validation, or successful generation |
301
+ | `1` | Generation or unexpected runtime failure |
302
+ | `2` | Invalid arguments, YAML, or configuration |
181
303
 
182
- ### Layout Customization
304
+ ## Development
183
305
 
184
- By default, Noblog uses `../../layouts/MarkdownPostLayout.astro` as the layout path. You can customize this using the `--layout` option:
306
+ Noblog uses [Stricli](https://bloomberg.github.io/stricli/) for typed command routing, [tsdown](https://tsdown.dev/) for CLI/library builds, and [Vitest](https://vitest.dev/) for tests.
185
307
 
186
308
  ```bash
187
- # Use a custom layout
188
- noblog --layout ../../layouts/BlogLayout.astro
309
+ pnpm install
189
310
 
190
- # Use with custom save paths
191
- noblog ./content/blog/ ./content/blog/subpages/ --layout ../../../layouts/CustomLayout.astro
192
- ```
311
+ # Run the TypeScript CLI directly
312
+ pnpm dev --help
313
+
314
+ # Run unit tests and type checking
315
+ pnpm test
316
+ pnpm typecheck
193
317
 
194
- **Important:** The layout path is relative to the posts directory where your markdown files are saved. For example:
195
- - If your save path is `./src/pages/posts/`, the default layout path `../../layouts/MarkdownPostLayout.astro` points to `./src/layouts/MarkdownPostLayout.astro`
196
- - If you change the save path to `./content/blog/`, you might need `--layout ../../layouts/CustomLayout.astro` to point to `./content/layouts/CustomLayout.astro`
318
+ # Regenerate the published JSON Schema (also runs during build)
319
+ pnpm schema:generate
197
320
 
198
- Adjust the layout path based on where your Astro layout files are located relative to your posts directory.
321
+ # Build and smoke-test the actual dist artifacts
322
+ pnpm test:built
199
323
 
200
- ## 🙏 Acknowledgments
324
+ # Run the complete verification suite
325
+ pnpm check
326
+ ```
327
+
328
+ ### Project structure
329
+
330
+ ```text
331
+ src/
332
+ ├── cli.ts # Process entry
333
+ ├── main.ts # Dotenv loading and dependency composition
334
+ ├── app.ts # Routes, help/version, and exit handling
335
+ ├── config-file.ts # YAML loading and validation
336
+ ├── commands/
337
+ │ ├── config*.ts # Offline configuration validation
338
+ │ ├── generate.ts # Declarative arguments and documentation
339
+ │ └── generate.impl.ts # Lazy-loaded command action
340
+ ├── workflows/
341
+ │ └── generate.ts # CLI-oriented generation orchestration
342
+ └── lib/
343
+ ├── noblog/ # Reusable rendering and file-writing package
344
+ │ ├── config.ts # Runtime YAML schema and types
345
+ │ ├── metadata.ts # Notion property normalization
346
+ │ ├── frontmatter-fields.ts # Declarative frontmatter adapter
347
+ │ ├── pages.ts # Page collection, caching, and recursion
348
+ │ ├── blocks.ts # Notion block-tree loading and rendering
349
+ │ ├── rich-text.ts # Rich-text annotations and links
350
+ │ ├── markdown.ts # Markdown formatting primitives
351
+ │ ├── frontmatter.ts # Legacy/custom frontmatter serialization
352
+ │ └── writer.ts # Canonical store and database-post symlinks
353
+ └── notion/client.ts # Explicit Notion SDK adapter
354
+ schema/config.json # Generated JSON Schema
355
+ skills/noblog-config/ # Agent Skill, references, and examples
356
+ ```
201
357
 
202
- Built upon the excellent work of these projects:
358
+ ## Acknowledgments
203
359
 
204
- - [Notion API](https://developers.notion.com/reference/intro) - Official Notion API documentation
205
- - [Notion SDK](https://developers.notion.com/docs/getting-started) - Notion's official SDK
206
- - [notion-to-md](https://github.com/souvikinator/notion-to-md) - Notion to Markdown converter
207
- - [notablog](https://github.com/dragonman225/notablog) - Original inspiration
208
- - [astro-notion-blog](https://github.com/otoyo/astro-notion-blog) - Astro + Notion integration
360
+ - [Notion API](https://developers.notion.com/reference/intro) and [Notion SDK](https://github.com/makenotion/notion-sdk-js)
361
+ - [notion-to-md](https://github.com/souvikinator/notion-to-md)
362
+ - [notablog](https://github.com/dragonman225/notablog)
363
+ - [astro-notion-blog](https://github.com/otoyo/astro-notion-blog)
364
+ - [nobloger](https://github.com/humbornjo/nobloger), a starter Astro template