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 +306 -0
- package/package.json +5 -2
- package/src/db/config.ts +6 -0
- package/src/index.ts +2 -3
- package/.claude/settings.local.json +0 -9
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.
|
|
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",
|
package/src/db/config.ts
ADDED
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 }
|
|
17
|
+
'astro:db:setup'({ extendDb }) {
|
|
19
18
|
extendDb({
|
|
20
|
-
|
|
19
|
+
configEntrypoint: new URL('./db/config.ts', import.meta.url),
|
|
21
20
|
});
|
|
22
21
|
},
|
|
23
22
|
|