@kvasar/openclaw-storyblok-plugin 0.1.11 → 0.1.13
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/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +192 -0
- package/dist/index.js.map +1 -0
- package/dist/src/client.d.ts +61 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/client.js +155 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/config.d.ts +9 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +32 -0
- package/dist/src/config.js.map +1 -0
- package/openclaw.plugin.json +11 -1
- package/package.json +6 -4
- package/index.ts +0 -247
- package/src/__tests__/README.md +0 -115
- package/src/__tests__/openclaw.plugin.json +0 -30
- package/src/__tests__/package-lock.json +0 -1555
- package/src/__tests__/package.json +0 -22
- package/src/__tests__/storyblok.test.ts +0 -55
- package/src/__tests__/tsconfig.json +0 -19
- package/src/client.ts +0 -176
- package/src/config.ts +0 -35
- package/src/openclaw-plugin-entry.d.ts +0 -3
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "openclaw-storyblok-plugin",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "OpenClaw plugin for Storyblok AI page generation (thin client)",
|
|
6
|
-
"main": "dist/index.js",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"scripts": {
|
|
9
|
-
"typecheck": "tsc --noEmit",
|
|
10
|
-
"build": "tsc",
|
|
11
|
-
"test": "vitest run"
|
|
12
|
-
},
|
|
13
|
-
"dependencies": {
|
|
14
|
-
"@sinclair/typebox": "^0.34.30",
|
|
15
|
-
"node-fetch": "^3.3.2"
|
|
16
|
-
},
|
|
17
|
-
"devDependencies": {
|
|
18
|
-
"@types/node": "^22.10.6",
|
|
19
|
-
"typescript": "^5.7.3",
|
|
20
|
-
"vitest": "^2.1.8"
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import fetch from 'node-fetch';
|
|
3
|
-
|
|
4
|
-
vi.mock('node-fetch');
|
|
5
|
-
|
|
6
|
-
describe('storyblok_generate_page', () => {
|
|
7
|
-
const mockConfig = {
|
|
8
|
-
serviceUrl: 'http://localhost:8000',
|
|
9
|
-
apiKey: 'fake-key',
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
// We need to import the plugin after mocking fetch
|
|
13
|
-
// but we can't directly import the tool. Instead, we'll test the fetch call.
|
|
14
|
-
// For simplicity, we'll just test the fetch mock.
|
|
15
|
-
|
|
16
|
-
it('should call service with correct URL and headers', async () => {
|
|
17
|
-
vi.mocked(fetch).mockResolvedValue({
|
|
18
|
-
ok: true,
|
|
19
|
-
json: () => Promise.resolve({ status: 'created' }),
|
|
20
|
-
text: () => Promise.resolve(''),
|
|
21
|
-
} as any);
|
|
22
|
-
|
|
23
|
-
// Simulate the plugin's fetch call
|
|
24
|
-
const url = `${mockConfig.serviceUrl}/generate-page`;
|
|
25
|
-
const headers = {
|
|
26
|
-
'Content-Type': 'application/json',
|
|
27
|
-
Authorization: `Bearer ${mockConfig.apiKey}`,
|
|
28
|
-
};
|
|
29
|
-
const body = JSON.stringify({ prompt: 'test prompt' });
|
|
30
|
-
|
|
31
|
-
await fetch(url, {
|
|
32
|
-
method: 'POST',
|
|
33
|
-
headers,
|
|
34
|
-
body,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
expect(fetch).toHaveBeenCalledWith(url, {
|
|
38
|
-
method: 'POST',
|
|
39
|
-
headers,
|
|
40
|
-
body,
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should handle service error', async () => {
|
|
45
|
-
vi.mocked(fetch).mockResolvedValue({
|
|
46
|
-
ok: false,
|
|
47
|
-
status: 500,
|
|
48
|
-
text: () => Promise.resolve('Internal server error'),
|
|
49
|
-
} as any);
|
|
50
|
-
|
|
51
|
-
const url = `${mockConfig.serviceUrl}/generate-page`;
|
|
52
|
-
const response = await fetch(url, { method: 'POST' });
|
|
53
|
-
expect(response.ok).toBe(false);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"lib": ["ES2022"],
|
|
7
|
-
"strict": true,
|
|
8
|
-
"esModuleInterop": true,
|
|
9
|
-
"skipLibCheck": true,
|
|
10
|
-
"forceConsistentCasingInFileNames": true,
|
|
11
|
-
"resolveJsonModule": true,
|
|
12
|
-
"isolatedModules": true,
|
|
13
|
-
"declaration": true,
|
|
14
|
-
"declarationMap": false,
|
|
15
|
-
"outDir": "dist"
|
|
16
|
-
},
|
|
17
|
-
"include": ["src/**/*"],
|
|
18
|
-
"exclude": ["node_modules", "dist"]
|
|
19
|
-
}
|
package/src/client.ts
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Storyblok client for OpenClaw plugin.
|
|
3
|
-
*
|
|
4
|
-
* Wraps Storyblok Management API (v1) and optionally Delivery API (v2) for reads.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export interface StoryblokConfig {
|
|
8
|
-
baseUrl: string;
|
|
9
|
-
spaceId: string;
|
|
10
|
-
managementToken: string;
|
|
11
|
-
previewToken?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class StoryblokClient {
|
|
15
|
-
private cfg: StoryblokConfig;
|
|
16
|
-
|
|
17
|
-
constructor(cfg: StoryblokConfig) {
|
|
18
|
-
this.cfg = cfg;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
private async request<T>(url: string, options: RequestInit = {}): Promise<T> {
|
|
22
|
-
const res = await fetch(url, {
|
|
23
|
-
...options,
|
|
24
|
-
headers: {
|
|
25
|
-
"Content-Type": "application/json",
|
|
26
|
-
...options.headers,
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
if (!res.ok) {
|
|
31
|
-
const text = await res.text();
|
|
32
|
-
throw new Error(`HTTP ${res.status}: ${text}`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// For 204 No Content, return null
|
|
36
|
-
if (res.status === 204) return null as any;
|
|
37
|
-
return res.json();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
private managementUrl(path: string, params: Record<string, string> = {}): string {
|
|
41
|
-
const base = this.cfg.baseUrl.replace(/\/$/, "");
|
|
42
|
-
const url = `${base}/v1/spaces/${this.cfg.spaceId}${path}`;
|
|
43
|
-
params.token = this.cfg.managementToken;
|
|
44
|
-
const qs = new URLSearchParams(params).toString();
|
|
45
|
-
return `${url}?${qs}`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private deliveryUrl(path: string, params: Record<string, string> = {}): string {
|
|
49
|
-
const base = this.cfg.baseUrl.replace(/\/$/, "");
|
|
50
|
-
const url = `${base}/v2/cdn${path}`;
|
|
51
|
-
if (this.cfg.previewToken) {
|
|
52
|
-
params.token = this.cfg.previewToken;
|
|
53
|
-
}
|
|
54
|
-
const qs = new URLSearchParams(params).toString();
|
|
55
|
-
return qs ? `${url}?${qs}` : url;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async getSpace(): Promise<any> {
|
|
59
|
-
// Management API: GET /v1/spaces/:space_id
|
|
60
|
-
const url = this.managementUrl("");
|
|
61
|
-
return this.request(url);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async getStory(identifier: string, opts: { version?: string; language?: string; svg_render?: boolean } = {}): Promise<any> {
|
|
65
|
-
// Decide API based on version: if version === 'published' and previewToken exists, use Delivery API; else Management API.
|
|
66
|
-
const version = opts.version === 'published' ? 'published' : 'draft';
|
|
67
|
-
const params: Record<string, string> = {};
|
|
68
|
-
|
|
69
|
-
// Management API endpoint: /v1/stories/:id
|
|
70
|
-
// Delivery API endpoint: /v2/cdn/stories/:slug or /v2/cdn/stories/:id
|
|
71
|
-
// We'll use Management API for flexibility and to support both versions.
|
|
72
|
-
// But note: Management API returns full content; Delivery API returns rendered content.
|
|
73
|
-
// The description mentions "svg_render" which is a Feature of Management API? Actually Storyblok management has an option to render as SVG.
|
|
74
|
-
if (opts.svg_render) params.svg = 'true';
|
|
75
|
-
if (opts.language) params.language = opts.language;
|
|
76
|
-
if (version) params.version = version;
|
|
77
|
-
|
|
78
|
-
// Use Management API (it allows version selection)
|
|
79
|
-
const url = this.managementUrl(`/stories/${encodeURIComponent(identifier)}`, params);
|
|
80
|
-
return this.request(url);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async listStories(params: {
|
|
84
|
-
folder_id?: number;
|
|
85
|
-
parent_id?: number;
|
|
86
|
-
status?: string;
|
|
87
|
-
tag?: string;
|
|
88
|
-
per_page?: number;
|
|
89
|
-
page?: number;
|
|
90
|
-
sort_by?: string;
|
|
91
|
-
direction?: string;
|
|
92
|
-
} = {}): Promise<any> {
|
|
93
|
-
const q: Record<string, string> = {};
|
|
94
|
-
if (params.folder_id !== undefined) q.folder_id = String(params.folder_id);
|
|
95
|
-
if (params.parent_id !== undefined) q.parent_id = String(params.parent_id);
|
|
96
|
-
if (params.status) q.status = params.status;
|
|
97
|
-
if (params.tag) q.tag = params.tag;
|
|
98
|
-
if (params.per_page) q.per_page = String(params.per_page);
|
|
99
|
-
if (params.page) q.page = String(params.page);
|
|
100
|
-
if (params.sort_by) q.sort_by = params.sort_by;
|
|
101
|
-
if (params.direction) q.direction = params.direction;
|
|
102
|
-
|
|
103
|
-
const url = this.managementUrl("/stories", q);
|
|
104
|
-
return this.request(url);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async createStory(data: {
|
|
108
|
-
title: string;
|
|
109
|
-
slug?: string;
|
|
110
|
-
folder_id?: number;
|
|
111
|
-
parent_id?: number;
|
|
112
|
-
content?: Record<string, any>;
|
|
113
|
-
tags?: string[];
|
|
114
|
-
is_folder?: boolean;
|
|
115
|
-
language?: string;
|
|
116
|
-
}): Promise<any> {
|
|
117
|
-
const body: Record<string, any> = { title: data.title };
|
|
118
|
-
if (data.slug) body.slug = data.slug;
|
|
119
|
-
if (data.folder_id !== undefined) body.folder_id = data.folder_id;
|
|
120
|
-
if (data.parent_id !== undefined) body.parent_id = data.parent_id;
|
|
121
|
-
if (data.content) body.content = data.content;
|
|
122
|
-
if (data.tags) body.tags = data.tags;
|
|
123
|
-
if (data.is_folder !== undefined) body.is_folder = data.is_folder;
|
|
124
|
-
if (data.language) body.language = data.language;
|
|
125
|
-
|
|
126
|
-
const url = this.managementUrl("/stories");
|
|
127
|
-
return this.request(url, { method: "POST", body: JSON.stringify(body) });
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async updateStory(storyId: string, data: {
|
|
131
|
-
title?: string;
|
|
132
|
-
slug?: string;
|
|
133
|
-
content?: Record<string, any>;
|
|
134
|
-
parent_id?: number;
|
|
135
|
-
tags?: string[];
|
|
136
|
-
language?: string;
|
|
137
|
-
version?: string;
|
|
138
|
-
}): Promise<any> {
|
|
139
|
-
const body: Record<string, any> = {};
|
|
140
|
-
if (data.title) body.title = data.title;
|
|
141
|
-
if (data.slug) body.slug = data.slug;
|
|
142
|
-
if (data.content) body.content = data.content;
|
|
143
|
-
if (data.parent_id !== undefined) body.parent_id = data.parent_id;
|
|
144
|
-
if (data.tags) body.tags = data.tags;
|
|
145
|
-
if (data.language) body.language = data.language;
|
|
146
|
-
if (data.version) body.version = data.version;
|
|
147
|
-
|
|
148
|
-
const url = this.managementUrl(`/stories/${encodeURIComponent(storyId)}`);
|
|
149
|
-
return this.request(url, { method: "PUT", body: JSON.stringify(body) });
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async publishStory(storyId: string, opts: { version?: string; language?: string; publish_notes?: string } = {}): Promise<any> {
|
|
153
|
-
const body: Record<string, any> = {};
|
|
154
|
-
if (opts.version) body.version = opts.version;
|
|
155
|
-
if (opts.language) body.language = opts.language;
|
|
156
|
-
if (opts.publish_notes) body.publish_notes = opts.publish_notes;
|
|
157
|
-
|
|
158
|
-
const url = this.managementUrl(`/stories/${encodeURIComponent(storyId )}/publish`);
|
|
159
|
-
return this.request(url, { method: "POST", body: JSON.stringify(body) });
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
async unpublishStory(storyId: string, language?: string): Promise<any> {
|
|
163
|
-
const body: Record<string, any> = {};
|
|
164
|
-
if (language) body.language = language;
|
|
165
|
-
const url = this.managementUrl(`/stories/${encodeURIComponent(storyId)}/unpublish`);
|
|
166
|
-
return this.request(url, { method: "POST", body: JSON.stringify(body) });
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async getComponents(version?: string, language?: string): Promise<any> {
|
|
170
|
-
const params: Record<string, string> = {};
|
|
171
|
-
if (version) params.version = version;
|
|
172
|
-
if (language) params.language = language;
|
|
173
|
-
const url = this.managementUrl("/components", params);
|
|
174
|
-
return this.request(url);
|
|
175
|
-
}
|
|
176
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Config validation and redaction helpers.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export function validateConfig(cfg: Record<string, any>): void {
|
|
6
|
-
const errors: string[] = [];
|
|
7
|
-
|
|
8
|
-
if (!cfg.baseUrl || typeof cfg.baseUrl !== "string") {
|
|
9
|
-
errors.push("baseUrl is required and must be a string");
|
|
10
|
-
}
|
|
11
|
-
if (!cfg.spaceId && cfg.spaceId !== 0) {
|
|
12
|
-
errors.push("spaceId is required");
|
|
13
|
-
}
|
|
14
|
-
if (!cfg.managementToken || typeof cfg.managementToken !== "string") {
|
|
15
|
-
errors.push("managementToken is required and must be a string");
|
|
16
|
-
}
|
|
17
|
-
if (cfg.previewToken && typeof cfg.previewToken !== "string") {
|
|
18
|
-
errors.push("previewToken, if provided, must be a string");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (errors.length > 0) {
|
|
22
|
-
throw new Error(`Invalid configuration for Storyblok plugin: ${errors.join("; ")}`);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function redactTokens(message: string, cfg: { managementToken?: string; previewToken?: string }): string {
|
|
27
|
-
let redacted = message;
|
|
28
|
-
if (cfg.managementToken) {
|
|
29
|
-
redacted = redacted.replace(new RegExp(cfg.managementToken, "g"), "[REDACTED]");
|
|
30
|
-
}
|
|
31
|
-
if (cfg.previewToken) {
|
|
32
|
-
redacted = redacted.replace(new RegExp(cfg.previewToken, "g"), "[REDACTED]");
|
|
33
|
-
}
|
|
34
|
-
return redacted;
|
|
35
|
-
}
|