nca-ai-cms-astro-plugin 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/OPENSPEC.md ADDED
@@ -0,0 +1,306 @@
1
+ # OpenSpec: nca-ai-cms-astro-plugin
2
+
3
+ ## Overview
4
+
5
+ Astro integration plugin that adds an AI-powered content management system. Generates articles and images with Google Gemini, schedules posts, and provides a built-in editor UI via route injection.
6
+
7
+ **Version:** 1.0.0
8
+ **Type:** Astro Integration (ESM)
9
+ **Runtime:** Node.js (SSR only)
10
+
11
+ ## Architecture
12
+
13
+ ```
14
+ src/
15
+ ├── index.ts # Integration entry point (hooks)
16
+ ├── config.d.ts # Virtual module type declarations
17
+ ├── db/
18
+ │ ├── tables.ts # AstroDB table definitions
19
+ │ └── config.ts # AstroDB config for extendDb
20
+ ├── domain/
21
+ │ ├── entities/ # Article, ScheduledPost, Source
22
+ │ └── value-objects/ # Slug, SEOMetadata, ArticleFinder
23
+ ├── services/ # Business logic layer
24
+ │ ├── ArticleService.ts # Filesystem article CRUD
25
+ │ ├── ContentGenerator.ts # Gemini text generation
26
+ │ ├── ImageGenerator.ts # Gemini Imagen generation
27
+ │ ├── PromptService.ts # AstroDB prompts/settings access
28
+ │ ├── SchedulerService.ts # Scheduled post CRUD
29
+ │ ├── SchedulerDBAdapter.ts # AstroDB adapter for scheduler
30
+ │ ├── FileWriter.ts # Article markdown writer
31
+ │ ├── ImageConverter.ts # Base64 → WebP via Sharp
32
+ │ ├── ContentFetcher.ts # URL → Markdown via TurnDown
33
+ │ └── AutoPublisher.ts # Background publish interval
34
+ ├── utils/
35
+ │ ├── markdown.ts # renderMarkdown() (marked + sanitize)
36
+ │ ├── sanitize.ts # HTML/JSON-LD sanitization
37
+ │ └── envUtils.ts # getEnvVariable()
38
+ ├── api/ # Injected API routes
39
+ │ ├── _utils.ts # jsonResponse(), jsonError()
40
+ │ ├── generate-content.ts
41
+ │ ├── generate-image.ts
42
+ │ ├── save.ts
43
+ │ ├── save-image.ts
44
+ │ ├── prompts.ts
45
+ │ ├── scheduler.ts
46
+ │ ├── scheduler/
47
+ │ │ ├── generate.ts
48
+ │ │ ├── publish.ts
49
+ │ │ └── [id].ts
50
+ │ └── articles/
51
+ │ ├── [id].ts
52
+ │ └── [id]/
53
+ │ ├── apply.ts
54
+ │ ├── regenerate-text.ts
55
+ │ └── regenerate-image.ts
56
+ ├── pages/
57
+ │ └── editor.astro # Editor page (React client:load)
58
+ └── components/
59
+ ├── Editor.tsx # Main React editor component
60
+ └── editor/
61
+ ├── GenerateTab.tsx
62
+ ├── PlannerTab.tsx
63
+ ├── SettingsTab.tsx
64
+ ├── styles.ts
65
+ ├── types.ts
66
+ └── useTabNavigation.ts
67
+ ```
68
+
69
+ ## Design Patterns
70
+
71
+ | Pattern | Where | Purpose |
72
+ |---------|-------|---------|
73
+ | DDD Entities | `domain/entities/` | Business rules on Article, ScheduledPost, Source |
74
+ | Value Objects | `domain/value-objects/` | Slug generation, SEO truncation, article lookup |
75
+ | Service Layer | `services/` | Orchestration, external API calls, filesystem I/O |
76
+ | Repository | `SchedulerDBAdapter` | Abstract DB operations behind interface |
77
+ | Dependency Injection | `ContentGenerator`, `SchedulerService` | Accept adapters via constructor |
78
+ | Virtual Module | `virtual:nca-ai-cms/config` | Share plugin config with routes without circular deps |
79
+
80
+ ## Integration Hooks
81
+
82
+ ### `astro:db:setup`
83
+
84
+ Registers three AstroDB tables via `extendDb({ configEntrypoint })`:
85
+
86
+ | Table | Purpose | Primary Key |
87
+ |-------|---------|-------------|
88
+ | `SiteSettings` | Key-value config store | `key` (text) |
89
+ | `Prompts` | Editable AI prompts | `id` (text) |
90
+ | `ScheduledPosts` | Content scheduler queue | `id` (text) |
91
+
92
+ ### `astro:config:setup`
93
+
94
+ - Creates Vite virtual module `virtual:nca-ai-cms/config`
95
+ - Injects 13 API routes + 1 page via `injectRoute()`
96
+
97
+ ### `astro:server:start`
98
+
99
+ - Starts `AutoPublisher` (60-min interval) if `autoPublish === true`
100
+
101
+ ## Plugin Options
102
+
103
+ ```typescript
104
+ interface NcaAiCmsPluginOptions {
105
+ contentPath?: string; // Default: 'nca-ai-cms-content'
106
+ autoPublish?: boolean; // Default: true in production
107
+ }
108
+ ```
109
+
110
+ ## Package Exports
111
+
112
+ ```json
113
+ {
114
+ ".": "./src/index.ts",
115
+ "./services": "./src/services/index.ts",
116
+ "./domain": "./src/domain/index.ts",
117
+ "./domain/entities": "./src/domain/entities/index.ts",
118
+ "./domain/value-objects": "./src/domain/value-objects/index.ts",
119
+ "./utils": "./src/utils/index.ts",
120
+ "./api/*": "./src/api/*",
121
+ "./pages/*": "./src/pages/*",
122
+ "./db/*": "./src/db/*"
123
+ }
124
+ ```
125
+
126
+ Host projects can import domain objects and services directly:
127
+ ```typescript
128
+ import { Article, ScheduledPost } from 'nca-ai-cms-astro-plugin/domain/entities';
129
+ import { ArticleService } from 'nca-ai-cms-astro-plugin/services';
130
+ ```
131
+
132
+ ## Environment Variables
133
+
134
+ | Variable | Required | Purpose |
135
+ |----------|----------|---------|
136
+ | `GOOGLE_GEMINI_API_KEY` | Yes | Gemini API for text + image generation |
137
+ | `GOOGLE_GEMINI_MODELS` | No | Override default model names |
138
+ | `ASTRO_DB_REMOTE_URL` | Production | Turso database URL |
139
+ | `ASTRO_DB_APP_TOKEN` | Production | Turso auth token |
140
+ | `ASTRO_DATABASE_FILE` | Local build | SQLite file path for local builds |
141
+
142
+ ## Database Schema
143
+
144
+ ### SiteSettings
145
+
146
+ | Column | Type | Notes |
147
+ |--------|------|-------|
148
+ | `key` | text (PK) | Setting identifier |
149
+ | `value` | text | Setting value |
150
+ | `updatedAt` | date | Auto-set |
151
+
152
+ ### Prompts
153
+
154
+ | Column | Type | Notes |
155
+ |--------|------|-------|
156
+ | `id` | text (PK) | Prompt identifier |
157
+ | `name` | text | Display name |
158
+ | `category` | text | `content` / `image` / `analysis` |
159
+ | `promptText` | text | Full prompt text |
160
+ | `updatedAt` | date | Auto-set |
161
+
162
+ ### ScheduledPosts
163
+
164
+ | Column | Type | Notes |
165
+ |--------|------|-------|
166
+ | `id` | text (PK) | Format: `sp_{timestamp}_{random}` |
167
+ | `input` | text | URL or keywords |
168
+ | `inputType` | text | `url` / `keywords` |
169
+ | `scheduledDate` | date | When to publish |
170
+ | `status` | text | `pending` → `generated` → `published` |
171
+ | `generatedTitle` | text (opt) | AI-generated title |
172
+ | `generatedDescription` | text (opt) | AI-generated description |
173
+ | `generatedContent` | text (opt) | AI-generated markdown |
174
+ | `generatedTags` | text (opt) | JSON string array |
175
+ | `generatedImageData` | text (opt) | Base64 PNG |
176
+ | `generatedImageAlt` | text (opt) | Image alt text |
177
+ | `publishedPath` | text (opt) | Filesystem path after publish |
178
+ | `createdAt` | date | Auto-set |
179
+
180
+ ## Article Filesystem Convention
181
+
182
+ ```
183
+ {contentPath}/{YYYY}/{MM}/{slug}/
184
+ ├── index.md # Frontmatter + markdown content
185
+ └── hero.webp # Co-located hero image
186
+ ```
187
+
188
+ **Frontmatter format:**
189
+ ```yaml
190
+ ---
191
+ title: "Article Title"
192
+ description: "SEO description"
193
+ date: 2026-02-21
194
+ tags: ["Semantik", "HTML", "Barrierefrei"]
195
+ image: "./hero.webp"
196
+ imageAlt: "Alt text for hero image"
197
+ ---
198
+ ```
199
+
200
+ ## API Routes
201
+
202
+ ### Content Generation
203
+
204
+ | Method | Route | Purpose |
205
+ |--------|-------|---------|
206
+ | POST | `/api/generate-content` | Generate article from URL or keywords |
207
+ | POST | `/api/generate-image` | Generate hero image for title |
208
+ | POST | `/api/save` | Write article to filesystem |
209
+ | POST | `/api/save-image` | Save image as hero.webp |
210
+
211
+ ### Scheduler
212
+
213
+ | Method | Route | Purpose |
214
+ |--------|-------|---------|
215
+ | GET | `/api/scheduler` | List all scheduled posts |
216
+ | POST | `/api/scheduler` | Create scheduled post |
217
+ | DELETE | `/api/scheduler/[id]` | Delete scheduled post |
218
+ | POST | `/api/scheduler/generate` | Generate content for post |
219
+ | POST | `/api/scheduler/publish` | Publish single or all due posts |
220
+
221
+ ### Articles
222
+
223
+ | Method | Route | Purpose |
224
+ |--------|-------|---------|
225
+ | GET | `/api/articles/[id]` | Read article by slug |
226
+ | DELETE | `/api/articles/[id]` | Delete article folder |
227
+ | POST | `/api/articles/[id]/regenerate-text` | Preview new text |
228
+ | POST | `/api/articles/[id]/regenerate-image` | Preview new image |
229
+ | POST | `/api/articles/[id]/apply` | Save text/image changes |
230
+
231
+ ### Settings
232
+
233
+ | Method | Route | Purpose |
234
+ |--------|-------|---------|
235
+ | GET | `/api/prompts` | Get all prompts + settings |
236
+ | POST | `/api/prompts` | Update prompt or setting |
237
+
238
+ ## Scheduled Post Lifecycle
239
+
240
+ ```
241
+ pending ──[generate]──→ generated ──[publish]──→ published
242
+ │ │
243
+ └──[generate]───────────┘ (can regenerate)
244
+ ```
245
+
246
+ - **pending**: Created, waiting for content generation
247
+ - **generated**: Content + image ready, waiting for publish date
248
+ - **published**: Written to filesystem, immutable
249
+
250
+ `AutoPublisher` checks every 60 minutes for posts where `scheduledDate <= today && status === 'generated'`.
251
+
252
+ ## Development
253
+
254
+ ### Commands
255
+
256
+ ```bash
257
+ npm test # Run unit tests (vitest)
258
+ npm run test:watch # Watch mode
259
+ npm run typecheck # TypeScript check
260
+ ```
261
+
262
+ ### Peer Dependencies
263
+
264
+ The host project must provide these packages:
265
+
266
+ ```
267
+ astro ^5.0.0
268
+ @astrojs/db ^0.18.0
269
+ @astrojs/react ^4.0.0
270
+ @google/genai ^1.0.0
271
+ @google/generative-ai ^0.24.0
272
+ gray-matter ^4.0.0
273
+ marked ^17.0.0
274
+ react ^19.0.0
275
+ react-dom ^19.0.0
276
+ sanitize-html ^2.0.0
277
+ sharp ^0.34.0
278
+ turndown ^7.0.0
279
+ zod ^3.0.0
280
+ ```
281
+
282
+ ### Local Development with Host Project
283
+
284
+ ```bash
285
+ # In host project:
286
+ npm run plugin:link # Install from local filesystem
287
+ npm run dev # Start dev server with local plugin
288
+ npm run plugin:unlink # Switch back to registry version
289
+ ```
290
+
291
+ ### Publishing
292
+
293
+ ```bash
294
+ # Bump version in package.json
295
+ npm publish # Runs tests via prepublishOnly
296
+ ```
297
+
298
+ ### Key Rules
299
+
300
+ 1. **No `import.meta.env`** — Use `process.env` via `getEnvVariable()` for server-side secrets
301
+ 2. **No bare `marked()`** — Always use `renderMarkdown()` which sanitizes output
302
+ 3. **German slug handling** — Slug value object converts umlauts (ä→ae, ö→oe, ü→ue, ß→ss)
303
+ 4. **Virtual module for config** — API routes import `virtual:nca-ai-cms/config` for contentPath
304
+ 5. **AstroDB via configEntrypoint** — Tables registered via URL-based `configEntrypoint` to avoid top-level `astro:db` import
305
+ 6. **Images always WebP** — ImageConverter uses Sharp (quality 85, effort 6)
306
+ 7. **Content in German** — Default prompts generate German-language accessibility articles
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nca-ai-cms-astro-plugin",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts",
@@ -9,7 +9,10 @@
9
9
  "./domain/entities": "./src/domain/entities/index.ts",
10
10
  "./domain/value-objects": "./src/domain/value-objects/index.ts",
11
11
  "./utils": "./src/utils/index.ts",
12
- "./api/*": "./src/api/*"
12
+ "./api/*": "./src/api/*",
13
+ "./pages/*": "./src/pages/*",
14
+ "./db/*": "./src/db/*",
15
+ "./middleware.ts": "./src/middleware.ts"
13
16
  },
14
17
  "scripts": {
15
18
  "test": "vitest run",
@@ -0,0 +1,6 @@
1
+ import { defineDb } from 'astro:db';
2
+ import { SiteSettings, Prompts, ScheduledPosts } from './tables.js';
3
+
4
+ export default defineDb({
5
+ tables: { SiteSettings, Prompts, ScheduledPosts },
6
+ });
package/src/index.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import type { AstroIntegration } from 'astro';
2
- import { SiteSettings, Prompts, ScheduledPosts } from './db/tables.js';
3
2
 
4
3
  export interface NcaAiCmsPluginOptions {
5
4
  contentPath?: string;
@@ -15,9 +14,9 @@ export default function ncaAiCms(
15
14
  return {
16
15
  name: 'nca-ai-cms-astro-plugin',
17
16
  hooks: {
18
- 'astro:db:setup'({ extendDb }: { extendDb: (config: { tables: Record<string, unknown> }) => void }) {
17
+ 'astro:db:setup'({ extendDb }) {
19
18
  extendDb({
20
- tables: { SiteSettings, Prompts, ScheduledPosts },
19
+ configEntrypoint: new URL('./db/config.ts', import.meta.url),
21
20
  });
22
21
  },
23
22
 
@@ -1,9 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(tail:*)",
5
- "Bash(npx tsc:*)",
6
- "Bash(head:*)"
7
- ]
8
- }
9
- }