@storyblok/management-api-client 1.0.0-alpha.2 → 1.0.0-alpha.4
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/client.cjs +6 -0
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +133 -7
- package/dist/client.d.mts +133 -7
- package/dist/client.mjs +6 -0
- package/dist/client.mjs.map +1 -1
- package/dist/generated/mapi/_internal.gen.d.cts +1 -171
- package/dist/generated/mapi/_internal.gen.d.mts +1 -171
- package/dist/generated/mapi/types-aliased.gen.d.cts +171 -1
- package/dist/generated/mapi/types-aliased.gen.d.mts +171 -1
- package/dist/generated/types/block.d.cts +27 -8
- package/dist/generated/types/block.d.mts +27 -8
- package/dist/generated/types/field.d.cts +45 -17
- package/dist/generated/types/field.d.mts +45 -17
- package/dist/generated/types/mapi-story.d.cts +6 -6
- package/dist/generated/types/mapi-story.d.mts +6 -6
- package/dist/index.d.cts +6 -4
- package/dist/index.d.mts +6 -4
- package/dist/resources/assets.cjs +1 -0
- package/dist/resources/assets.cjs.map +1 -1
- package/dist/resources/assets.mjs +1 -1
- package/dist/resources/assets.mjs.map +1 -1
- package/dist/resources/components.cjs.map +1 -1
- package/dist/resources/components.d.cts +17 -17
- package/dist/resources/components.d.mts +17 -17
- package/dist/resources/components.mjs.map +1 -1
- package/dist/resources/shared-asset-folders.cjs +81 -0
- package/dist/resources/shared-asset-folders.cjs.map +1 -0
- package/dist/resources/shared-asset-folders.d.cts +41 -0
- package/dist/resources/shared-asset-folders.d.mts +41 -0
- package/dist/resources/shared-asset-folders.mjs +81 -0
- package/dist/resources/shared-asset-folders.mjs.map +1 -0
- package/dist/resources/shared-assets.cjs +197 -0
- package/dist/resources/shared-assets.cjs.map +1 -0
- package/dist/resources/shared-assets.d.cts +29 -0
- package/dist/resources/shared-assets.d.mts +28 -0
- package/dist/resources/shared-assets.mjs +197 -0
- package/dist/resources/shared-assets.mjs.map +1 -0
- package/dist/resources/shared-internal-tags.cjs +73 -0
- package/dist/resources/shared-internal-tags.cjs.map +1 -0
- package/dist/resources/shared-internal-tags.d.cts +32 -0
- package/dist/resources/shared-internal-tags.d.mts +32 -0
- package/dist/resources/shared-internal-tags.mjs +73 -0
- package/dist/resources/shared-internal-tags.mjs.map +1 -0
- package/dist/resources/stories.cjs.map +1 -1
- package/dist/resources/stories.d.cts +26 -26
- package/dist/resources/stories.d.mts +26 -26
- package/dist/resources/stories.mjs.map +1 -1
- package/package.json +3 -3
- package/playground/integration-tests/test/specs/mapi-round-trip.spec.e2e.ts +30 -40
- package/playground/integration-tests/test/types/components.test-d.ts +7 -46
- package/playground/integration-tests/test/types/resources.test-d.ts +0 -152
- package/playground/integration-tests/test/types/stories.test-d.ts +47 -43
- package/test/GUIDE.md +2 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storyblok/management-api-client",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-alpha.
|
|
4
|
+
"version": "1.0.0-alpha.4",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Storyblok Management API Client",
|
|
7
7
|
"author": "",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"async-sema": "^3.1.1",
|
|
34
34
|
"ky": "^1.14.3",
|
|
35
|
-
"@storyblok/region-helper": "1.
|
|
35
|
+
"@storyblok/region-helper": "1.5.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^24.1.0",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"tsdown": "^0.20.3",
|
|
43
43
|
"tsx": "^4.20.3",
|
|
44
44
|
"vitest": "^4.1.3",
|
|
45
|
-
"@storyblok/eslint-config": "0.
|
|
45
|
+
"@storyblok/eslint-config": "0.6.0",
|
|
46
46
|
"@storyblok/openapi-codegen": "0.0.1"
|
|
47
47
|
},
|
|
48
48
|
"nx": {
|
|
@@ -18,16 +18,10 @@
|
|
|
18
18
|
|
|
19
19
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
20
20
|
import { createManagementApiClient } from '@storyblok/management-api-client';
|
|
21
|
+
import type { StoryCreate, StoryUpdate } from '@storyblok/management-api-client';
|
|
21
22
|
import {
|
|
22
|
-
createStoryHelpers,
|
|
23
23
|
defineBlock,
|
|
24
|
-
defineBlockCreate,
|
|
25
|
-
defineBlockFolderCreate,
|
|
26
|
-
defineDatasourceCreate,
|
|
27
|
-
defineDatasourceEntryCreate,
|
|
28
24
|
defineField,
|
|
29
|
-
defineInternalTagCreate,
|
|
30
|
-
definePresetCreate,
|
|
31
25
|
} from '@storyblok/schema';
|
|
32
26
|
|
|
33
27
|
const token = process.env.STORYBLOK_TOKEN!;
|
|
@@ -43,7 +37,7 @@ const STORY_SLUG = `${STORY_SLUG_PREFIX}test-page`;
|
|
|
43
37
|
|
|
44
38
|
const teaserComponent = defineBlock({
|
|
45
39
|
name: `${PREFIX}teaser`,
|
|
46
|
-
|
|
40
|
+
fields: [
|
|
47
41
|
defineField('title', { type: 'text', required: true }),
|
|
48
42
|
defineField('image', { type: 'asset' }),
|
|
49
43
|
],
|
|
@@ -51,21 +45,21 @@ const teaserComponent = defineBlock({
|
|
|
51
45
|
// Level-2 container: holds teasers in its `items` bloks field (level 3)
|
|
52
46
|
const sectionComponent = defineBlock({
|
|
53
47
|
name: `${PREFIX}section`,
|
|
54
|
-
|
|
48
|
+
fields: [
|
|
55
49
|
defineField('title', { type: 'text' }),
|
|
56
|
-
defineField('items', { type: 'bloks',
|
|
50
|
+
defineField('items', { type: 'bloks', allow: [teaserComponent.name], required: true }),
|
|
57
51
|
],
|
|
58
52
|
});
|
|
59
53
|
const pageComponent = defineBlock({
|
|
60
54
|
name: `${PREFIX}page`,
|
|
61
55
|
is_root: true,
|
|
62
|
-
|
|
56
|
+
fields: [
|
|
63
57
|
defineField('headline', { type: 'text', required: true }),
|
|
64
58
|
defineField('rating', { type: 'number' }),
|
|
65
59
|
defineField('is_featured', { type: 'boolean' }),
|
|
66
60
|
defineField('description', { type: 'richtext' }),
|
|
67
|
-
defineField('body', { type: 'bloks',
|
|
68
|
-
defineField('category', { type: 'option', source: 'internal',
|
|
61
|
+
defineField('body', { type: 'bloks', allow: [teaserComponent.name, sectionComponent.name], required: true }),
|
|
62
|
+
defineField('category', { type: 'option', source: 'internal', datasource: DATASOURCE_SLUG }),
|
|
69
63
|
defineField('any_blocks', { type: 'bloks', required: true }),
|
|
70
64
|
],
|
|
71
65
|
});
|
|
@@ -74,7 +68,7 @@ interface StoryblokTypes {
|
|
|
74
68
|
components: typeof pageComponent | typeof teaserComponent | typeof sectionComponent;
|
|
75
69
|
}
|
|
76
70
|
|
|
77
|
-
|
|
71
|
+
type Blocks = StoryblokTypes['components'];
|
|
78
72
|
|
|
79
73
|
const client = createManagementApiClient({
|
|
80
74
|
personalAccessToken: token,
|
|
@@ -155,53 +149,50 @@ describe('schema + mapi-client MAPI round-trip', () => {
|
|
|
155
149
|
|
|
156
150
|
// 1. Datasource + entries first — the page component schema references the datasource slug,
|
|
157
151
|
// and the MAPI validates that the datasource exists at component creation time.
|
|
158
|
-
|
|
152
|
+
// Create payloads are plain MAPI wire objects validated by the typed client.
|
|
153
|
+
const dsRes = await client.datasources.create({ body: { datasource: {
|
|
159
154
|
name: DATASOURCE_NAME,
|
|
160
155
|
slug: DATASOURCE_SLUG,
|
|
161
|
-
});
|
|
162
|
-
const dsRes = await client.datasources.create({ body: { datasource: datasourcePayload } });
|
|
156
|
+
} } });
|
|
163
157
|
datasourceId = dsRes.data!.datasource!.id!;
|
|
164
158
|
|
|
165
159
|
for (const entry of [
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
160
|
+
{ name: 'Technology', value: 'tech', datasource_id: datasourceId },
|
|
161
|
+
{ name: 'Design', value: 'design', datasource_id: datasourceId },
|
|
162
|
+
{ name: 'Business', value: 'business', datasource_id: datasourceId },
|
|
169
163
|
]) {
|
|
170
164
|
await client.datasourceEntries.create({ body: { datasource_entry: entry } });
|
|
171
165
|
}
|
|
172
166
|
|
|
173
167
|
// 2. Component folder
|
|
174
|
-
const
|
|
175
|
-
const folderRes = await client.componentFolders.create({ body: { component_group: folderPayload } });
|
|
168
|
+
const folderRes = await client.componentFolders.create({ body: { component_group: { name: `${PREFIX}folder` } } });
|
|
176
169
|
componentFolderId = folderRes.data!.component_group!.id!;
|
|
177
170
|
const folderUuid = folderRes.data!.component_group!.uuid;
|
|
178
171
|
|
|
179
172
|
// 3. Teaser component (innermost — whitelisted by section)
|
|
180
|
-
const
|
|
173
|
+
const teaserRes = await client.components.create({ body: { component: {
|
|
181
174
|
name: teaserComponent.name,
|
|
182
175
|
schema: {
|
|
183
176
|
title: { type: 'text', required: true, pos: 0 },
|
|
184
177
|
image: { type: 'asset', pos: 1 },
|
|
185
178
|
},
|
|
186
179
|
component_group_uuid: folderUuid,
|
|
187
|
-
});
|
|
188
|
-
const teaserRes = await client.components.create({ body: { component: teaserPayload } });
|
|
180
|
+
} } });
|
|
189
181
|
teaserComponentId = teaserRes.data!.component!.id!;
|
|
190
182
|
|
|
191
183
|
// 4. Section component (level 2 — whitelists teaser, whitelisted by page)
|
|
192
|
-
const
|
|
184
|
+
const sectionRes = await client.components.create({ body: { component: {
|
|
193
185
|
name: sectionComponent.name,
|
|
194
186
|
schema: {
|
|
195
187
|
title: { type: 'text', pos: 0 },
|
|
196
188
|
items: { type: 'bloks', component_whitelist: [teaserComponent.name], pos: 1 },
|
|
197
189
|
},
|
|
198
190
|
component_group_uuid: folderUuid,
|
|
199
|
-
});
|
|
200
|
-
const sectionRes = await client.components.create({ body: { component: sectionPayload } });
|
|
191
|
+
} } });
|
|
201
192
|
sectionComponentId = sectionRes.data!.component!.id!;
|
|
202
193
|
|
|
203
194
|
// 5. Page component (level 1 — whitelists both teaser and section in body)
|
|
204
|
-
const
|
|
195
|
+
const pageRes = await client.components.create({ body: { component: {
|
|
205
196
|
name: pageComponent.name,
|
|
206
197
|
schema: {
|
|
207
198
|
headline: { type: 'text', required: true, pos: 0 },
|
|
@@ -214,30 +205,28 @@ describe('schema + mapi-client MAPI round-trip', () => {
|
|
|
214
205
|
},
|
|
215
206
|
component_group_uuid: folderUuid,
|
|
216
207
|
is_root: true,
|
|
217
|
-
});
|
|
218
|
-
const pageRes = await client.components.create({ body: { component: pagePayload } });
|
|
208
|
+
} } });
|
|
219
209
|
pageComponentId = pageRes.data!.component!.id!;
|
|
220
210
|
|
|
221
211
|
// 5. Internal tag
|
|
222
|
-
const
|
|
223
|
-
const tagRes = await client.internalTags.create({ body: { internal_tag: tagPayload } });
|
|
212
|
+
const tagRes = await client.internalTags.create({ body: { internal_tag: { name: `${PREFIX}tag`, object_type: 'component' } } });
|
|
224
213
|
internalTagId = tagRes.data!.internal_tag!.id!;
|
|
225
214
|
|
|
226
215
|
// 6. Preset for page component
|
|
227
|
-
const
|
|
216
|
+
const presetRes = await client.presets.create({ body: { preset: {
|
|
228
217
|
name: `${PREFIX}default_page`,
|
|
229
218
|
component_id: pageComponentId,
|
|
230
219
|
preset: { headline: 'Default Headline', rating: 0, is_featured: false },
|
|
231
220
|
description: `Default preset for ${pageComponent.name}`,
|
|
232
|
-
});
|
|
233
|
-
const presetRes = await client.presets.create({ body: { preset: presetPayload } });
|
|
221
|
+
} } });
|
|
234
222
|
presetId = presetRes.data!.preset!.id!;
|
|
235
223
|
|
|
236
224
|
// 8. Story: body[0]=teaser (level 2), body[1]=section{items:[teaser]} (levels 2+3)
|
|
237
|
-
const storyPayload =
|
|
225
|
+
const storyPayload: StoryCreate<Blocks> = {
|
|
238
226
|
name: STORY_NAME,
|
|
239
227
|
slug: STORY_SLUG,
|
|
240
228
|
content: {
|
|
229
|
+
component: pageComponent.name,
|
|
241
230
|
headline: 'Hello from e2e',
|
|
242
231
|
rating: 42,
|
|
243
232
|
is_featured: true,
|
|
@@ -269,7 +258,7 @@ describe('schema + mapi-client MAPI round-trip', () => {
|
|
|
269
258
|
},
|
|
270
259
|
],
|
|
271
260
|
},
|
|
272
|
-
}
|
|
261
|
+
};
|
|
273
262
|
const storyRes = await client.stories.create({ body: { story: storyPayload } });
|
|
274
263
|
storyId = storyRes.data!.story!.id!;
|
|
275
264
|
});
|
|
@@ -485,10 +474,11 @@ describe('schema + mapi-client MAPI round-trip', () => {
|
|
|
485
474
|
});
|
|
486
475
|
|
|
487
476
|
it('should round-trip story update correctly', async () => {
|
|
488
|
-
const updatedPayload =
|
|
477
|
+
const updatedPayload: StoryUpdate<Blocks> = {
|
|
489
478
|
name: `${STORY_NAME} (Updated)`,
|
|
490
479
|
slug: STORY_SLUG,
|
|
491
480
|
content: {
|
|
481
|
+
component: pageComponent.name,
|
|
492
482
|
headline: 'Updated headline',
|
|
493
483
|
rating: 100,
|
|
494
484
|
is_featured: false,
|
|
@@ -500,7 +490,7 @@ describe('schema + mapi-client MAPI round-trip', () => {
|
|
|
500
490
|
category: 'design',
|
|
501
491
|
any_blocks: [],
|
|
502
492
|
},
|
|
503
|
-
}
|
|
493
|
+
};
|
|
504
494
|
|
|
505
495
|
await client.stories.update(storyId, { body: { story: updatedPayload } });
|
|
506
496
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { defineBlock,
|
|
1
|
+
import { defineBlock, defineField } from '@storyblok/schema';
|
|
2
2
|
import { type Component as ComponentMapi, createManagementApiClient } from '@storyblok/management-api-client';
|
|
3
3
|
import { describe, expectTypeOf, it } from 'vitest';
|
|
4
4
|
|
|
5
5
|
// Nestable block — not a root story type
|
|
6
6
|
const teaserComponent = defineBlock({
|
|
7
7
|
name: 'teaser',
|
|
8
|
-
|
|
8
|
+
fields: [
|
|
9
9
|
defineField('text', { type: 'text' }),
|
|
10
10
|
defineField('image', { type: 'asset' }),
|
|
11
11
|
],
|
|
@@ -16,49 +16,23 @@ const _pageComponent = defineBlock({
|
|
|
16
16
|
name: 'page',
|
|
17
17
|
is_root: true,
|
|
18
18
|
is_nestable: false,
|
|
19
|
-
|
|
19
|
+
fields: [
|
|
20
20
|
defineField('headline', { type: 'text', required: true }),
|
|
21
21
|
defineField('body', { type: 'richtext' }),
|
|
22
|
-
defineField('teasers', { type: 'bloks',
|
|
22
|
+
defineField('teasers', { type: 'bloks', allow: [teaserComponent.name] }),
|
|
23
23
|
],
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
const CLIENT_CONFIG = { personalAccessToken: 'test-token', spaceId: 12345 };
|
|
27
27
|
|
|
28
|
-
describe('components.create body type compatibility', () => {
|
|
29
|
-
it('should produce a defineBlockCreate result assignable to components.create body', () => {
|
|
30
|
-
const createPayload = defineBlockCreate({
|
|
31
|
-
name: 'article',
|
|
32
|
-
schema: {
|
|
33
|
-
title: { type: 'text', pos: 1 },
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
type CreateBody = Parameters<ReturnType<typeof createManagementApiClient>['components']['create']>[0]['body'];
|
|
38
|
-
type ComponentCreateInput = NonNullable<CreateBody['component']>;
|
|
39
|
-
|
|
40
|
-
expectTypeOf(createPayload).toExtend<ComponentCreateInput>();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should produce a defineBlockUpdate result assignable to components.update body', () => {
|
|
44
|
-
const updatePayload = defineBlockUpdate({
|
|
45
|
-
display_name: 'Article',
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
type UpdateBody = Parameters<ReturnType<typeof createManagementApiClient>['components']['update']>[1]['body'];
|
|
49
|
-
type ComponentUpdateInput = NonNullable<UpdateBody['component']>;
|
|
50
|
-
|
|
51
|
-
expectTypeOf(updatePayload).toExtend<ComponentUpdateInput>();
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
28
|
describe('components.get response shape', () => {
|
|
56
|
-
it('should return
|
|
29
|
+
it('should return the wire Component (a `schema` record) from components.get', async () => {
|
|
57
30
|
const client = createManagementApiClient(CLIENT_CONFIG);
|
|
58
31
|
const result = await client.components.get(123);
|
|
59
32
|
|
|
60
33
|
if (result.data?.component) {
|
|
61
|
-
// components.get() returns the
|
|
34
|
+
// components.get() returns the wire-shaped Component definition (a `schema`
|
|
35
|
+
// record), not the DSL `fields` block.
|
|
62
36
|
expectTypeOf(result.data.component.id).toEqualTypeOf<ComponentMapi['id']>();
|
|
63
37
|
expectTypeOf(result.data.component.name).toEqualTypeOf<ComponentMapi['name']>();
|
|
64
38
|
expectTypeOf(result.data.component).toHaveProperty('schema');
|
|
@@ -66,19 +40,6 @@ describe('components.get response shape', () => {
|
|
|
66
40
|
});
|
|
67
41
|
});
|
|
68
42
|
|
|
69
|
-
describe('components.create body type rejection', () => {
|
|
70
|
-
it('should reject a component create payload with wrong schema field type', () => {
|
|
71
|
-
const createPayload = defineBlockCreate({
|
|
72
|
-
name: 'article',
|
|
73
|
-
schema: {
|
|
74
|
-
// @ts-expect-error: schema value must be a field definition, not a string
|
|
75
|
-
title: 'invalid',
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
void createPayload;
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
43
|
describe('defineBlock result used in .withTypes() interface', () => {
|
|
83
44
|
interface StoryblokTypes {
|
|
84
45
|
components: typeof _pageComponent | typeof teaserComponent;
|
|
@@ -1,44 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defineAssetFolderCreate,
|
|
3
|
-
defineAssetFolderUpdate,
|
|
4
|
-
defineBlockFolderCreate,
|
|
5
|
-
defineBlockFolderUpdate,
|
|
6
|
-
defineDatasourceCreate,
|
|
7
|
-
defineDatasourceEntryCreate,
|
|
8
|
-
defineDatasourceEntryUpdate,
|
|
9
|
-
defineDatasourceUpdate,
|
|
10
|
-
defineInternalTagCreate,
|
|
11
|
-
defineInternalTagUpdate,
|
|
12
|
-
definePresetCreate,
|
|
13
|
-
definePresetUpdate,
|
|
14
|
-
defineSpaceCreate,
|
|
15
|
-
defineSpaceUpdate,
|
|
16
|
-
defineUserUpdate,
|
|
17
|
-
} from '@storyblok/schema';
|
|
18
1
|
import { createManagementApiClient } from '@storyblok/management-api-client';
|
|
19
2
|
import { describe, expectTypeOf, it } from 'vitest';
|
|
20
3
|
|
|
21
4
|
const CLIENT_CONFIG = { personalAccessToken: 'test-token', spaceId: 12345 };
|
|
22
5
|
|
|
23
6
|
describe('datasources type tests', () => {
|
|
24
|
-
it('should produce a defineDatasourceCreate result assignable to datasources.create body', () => {
|
|
25
|
-
const payload = defineDatasourceCreate({ name: 'Categories', slug: 'categories' });
|
|
26
|
-
|
|
27
|
-
type CreateBody = Parameters<ReturnType<typeof createManagementApiClient>['datasources']['create']>[0]['body'];
|
|
28
|
-
type InnerType = NonNullable<CreateBody['datasource']>;
|
|
29
|
-
|
|
30
|
-
expectTypeOf(payload).toExtend<InnerType>();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('should produce a defineDatasourceUpdate result assignable to datasources.update body', () => {
|
|
34
|
-
const payload = defineDatasourceUpdate({ name: 'Updated Categories' });
|
|
35
|
-
|
|
36
|
-
type UpdateBody = Parameters<ReturnType<typeof createManagementApiClient>['datasources']['update']>[1]['body'];
|
|
37
|
-
type InnerType = NonNullable<UpdateBody['datasource']>;
|
|
38
|
-
|
|
39
|
-
expectTypeOf(payload).toExtend<InnerType>();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
7
|
it('should return datasource in response from create', async () => {
|
|
43
8
|
const client = createManagementApiClient(CLIENT_CONFIG);
|
|
44
9
|
const result = await client.datasources.create({
|
|
@@ -67,24 +32,6 @@ describe('datasources type tests', () => {
|
|
|
67
32
|
});
|
|
68
33
|
|
|
69
34
|
describe('datasource entries type tests', () => {
|
|
70
|
-
it('should produce a defineDatasourceEntryCreate result assignable to datasourceEntries.create body', () => {
|
|
71
|
-
const payload = defineDatasourceEntryCreate({ name: 'red', value: '#ff0000', datasource_id: 42 });
|
|
72
|
-
|
|
73
|
-
type CreateBody = Parameters<ReturnType<typeof createManagementApiClient>['datasourceEntries']['create']>[0]['body'];
|
|
74
|
-
type InnerType = NonNullable<CreateBody['datasource_entry']>;
|
|
75
|
-
|
|
76
|
-
expectTypeOf(payload).toExtend<InnerType>();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should produce a defineDatasourceEntryUpdate result assignable to datasourceEntries.update body', () => {
|
|
80
|
-
const payload = defineDatasourceEntryUpdate({ value: '#00ff00' });
|
|
81
|
-
|
|
82
|
-
type UpdateBody = Parameters<ReturnType<typeof createManagementApiClient>['datasourceEntries']['update']>[1]['body'];
|
|
83
|
-
type InnerType = NonNullable<UpdateBody['datasource_entry']>;
|
|
84
|
-
|
|
85
|
-
expectTypeOf(payload).toExtend<InnerType>();
|
|
86
|
-
});
|
|
87
|
-
|
|
88
35
|
it('should return datasource_entry in response from create', async () => {
|
|
89
36
|
const client = createManagementApiClient(CLIENT_CONFIG);
|
|
90
37
|
const result = await client.datasourceEntries.create({
|
|
@@ -113,24 +60,6 @@ describe('datasource entries type tests', () => {
|
|
|
113
60
|
});
|
|
114
61
|
|
|
115
62
|
describe('asset folders type tests', () => {
|
|
116
|
-
it('should produce a defineAssetFolderCreate result assignable to assetFolders.create body', () => {
|
|
117
|
-
const payload = defineAssetFolderCreate({ name: 'Images' });
|
|
118
|
-
|
|
119
|
-
type CreateBody = Parameters<ReturnType<typeof createManagementApiClient>['assetFolders']['create']>[0]['body'];
|
|
120
|
-
type InnerType = NonNullable<CreateBody['asset_folder']>;
|
|
121
|
-
|
|
122
|
-
expectTypeOf(payload).toExtend<InnerType>();
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('should produce a defineAssetFolderUpdate result assignable to assetFolders.update body', () => {
|
|
126
|
-
const payload = defineAssetFolderUpdate({ name: 'Updated Images' });
|
|
127
|
-
|
|
128
|
-
type UpdateBody = Parameters<ReturnType<typeof createManagementApiClient>['assetFolders']['update']>[1]['body'];
|
|
129
|
-
type InnerType = NonNullable<UpdateBody['asset_folder']>;
|
|
130
|
-
|
|
131
|
-
expectTypeOf(payload).toExtend<InnerType>();
|
|
132
|
-
});
|
|
133
|
-
|
|
134
63
|
it('should return asset_folder in response from create', async () => {
|
|
135
64
|
const client = createManagementApiClient(CLIENT_CONFIG);
|
|
136
65
|
const result = await client.assetFolders.create({
|
|
@@ -159,24 +88,6 @@ describe('asset folders type tests', () => {
|
|
|
159
88
|
});
|
|
160
89
|
|
|
161
90
|
describe('component folders type tests', () => {
|
|
162
|
-
it('should produce a defineBlockFolderCreate result assignable to componentFolders.create body', () => {
|
|
163
|
-
const payload = defineBlockFolderCreate({ name: 'Layout' });
|
|
164
|
-
|
|
165
|
-
type CreateBody = Parameters<ReturnType<typeof createManagementApiClient>['componentFolders']['create']>[0]['body'];
|
|
166
|
-
type InnerType = NonNullable<CreateBody['component_group']>;
|
|
167
|
-
|
|
168
|
-
expectTypeOf(payload).toExtend<InnerType>();
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('should produce a defineBlockFolderUpdate result assignable to componentFolders.update body', () => {
|
|
172
|
-
const payload = defineBlockFolderUpdate({ name: 'Updated Layout' });
|
|
173
|
-
|
|
174
|
-
type UpdateBody = Parameters<ReturnType<typeof createManagementApiClient>['componentFolders']['update']>[1]['body'];
|
|
175
|
-
type InnerType = NonNullable<UpdateBody['component_group']>;
|
|
176
|
-
|
|
177
|
-
expectTypeOf(payload).toExtend<InnerType>();
|
|
178
|
-
});
|
|
179
|
-
|
|
180
91
|
it('should return component_group in response from create', async () => {
|
|
181
92
|
const client = createManagementApiClient(CLIENT_CONFIG);
|
|
182
93
|
const result = await client.componentFolders.create({
|
|
@@ -205,24 +116,6 @@ describe('component folders type tests', () => {
|
|
|
205
116
|
});
|
|
206
117
|
|
|
207
118
|
describe('internal tags type tests', () => {
|
|
208
|
-
it('should produce a defineInternalTagCreate result assignable to internalTags.create body', () => {
|
|
209
|
-
const payload = defineInternalTagCreate({ name: 'hero', object_type: 'asset' });
|
|
210
|
-
|
|
211
|
-
type CreateBody = Parameters<ReturnType<typeof createManagementApiClient>['internalTags']['create']>[0]['body'];
|
|
212
|
-
type InnerType = NonNullable<CreateBody['internal_tag']>;
|
|
213
|
-
|
|
214
|
-
expectTypeOf(payload).toExtend<InnerType>();
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it('should produce a defineInternalTagUpdate result assignable to internalTags.update body', () => {
|
|
218
|
-
const payload = defineInternalTagUpdate({ name: 'hero-image' });
|
|
219
|
-
|
|
220
|
-
type UpdateBody = Parameters<ReturnType<typeof createManagementApiClient>['internalTags']['update']>[1]['body'];
|
|
221
|
-
type InnerType = NonNullable<UpdateBody['internal_tag']>;
|
|
222
|
-
|
|
223
|
-
expectTypeOf(payload).toExtend<InnerType>();
|
|
224
|
-
});
|
|
225
|
-
|
|
226
119
|
it('should return internal_tag in response from create', async () => {
|
|
227
120
|
const client = createManagementApiClient(CLIENT_CONFIG);
|
|
228
121
|
const result = await client.internalTags.create({
|
|
@@ -243,24 +136,6 @@ describe('internal tags type tests', () => {
|
|
|
243
136
|
});
|
|
244
137
|
|
|
245
138
|
describe('presets type tests', () => {
|
|
246
|
-
it('should produce a definePresetCreate result assignable to presets.create body', () => {
|
|
247
|
-
const payload = definePresetCreate({ name: 'Hero Dark', component_id: 42 });
|
|
248
|
-
|
|
249
|
-
type CreateBody = Parameters<ReturnType<typeof createManagementApiClient>['presets']['create']>[0]['body'];
|
|
250
|
-
type InnerType = NonNullable<CreateBody['preset']>;
|
|
251
|
-
|
|
252
|
-
expectTypeOf(payload).toExtend<InnerType>();
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it('should produce a definePresetUpdate result assignable to presets.update body', () => {
|
|
256
|
-
const payload = definePresetUpdate({ name: 'Hero Light' });
|
|
257
|
-
|
|
258
|
-
type UpdateBody = Parameters<ReturnType<typeof createManagementApiClient>['presets']['update']>[1]['body'];
|
|
259
|
-
type InnerType = NonNullable<UpdateBody['preset']>;
|
|
260
|
-
|
|
261
|
-
expectTypeOf(payload).toExtend<InnerType>();
|
|
262
|
-
});
|
|
263
|
-
|
|
264
139
|
it('should return preset in response from create', async () => {
|
|
265
140
|
const client = createManagementApiClient(CLIENT_CONFIG);
|
|
266
141
|
const result = await client.presets.create({
|
|
@@ -289,24 +164,6 @@ describe('presets type tests', () => {
|
|
|
289
164
|
});
|
|
290
165
|
|
|
291
166
|
describe('spaces type tests', () => {
|
|
292
|
-
it('should produce a defineSpaceCreate result assignable to spaces.create body', () => {
|
|
293
|
-
const payload = defineSpaceCreate({ name: 'My New Space' });
|
|
294
|
-
|
|
295
|
-
type CreateBody = Parameters<ReturnType<typeof createManagementApiClient>['spaces']['create']>[0]['body'];
|
|
296
|
-
type InnerType = CreateBody['space'];
|
|
297
|
-
|
|
298
|
-
expectTypeOf(payload).toExtend<InnerType>();
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
it('should produce a defineSpaceUpdate result assignable to spaces.update body', () => {
|
|
302
|
-
const payload = defineSpaceUpdate({ name: 'Updated Space Name' });
|
|
303
|
-
|
|
304
|
-
type UpdateBody = Parameters<ReturnType<typeof createManagementApiClient>['spaces']['update']>[0]['body'];
|
|
305
|
-
type InnerType = UpdateBody['space'];
|
|
306
|
-
|
|
307
|
-
expectTypeOf(payload).toExtend<InnerType>();
|
|
308
|
-
});
|
|
309
|
-
|
|
310
167
|
it('should return space in response from create', async () => {
|
|
311
168
|
const client = createManagementApiClient(CLIENT_CONFIG);
|
|
312
169
|
const result = await client.spaces.create({
|
|
@@ -335,15 +192,6 @@ describe('spaces type tests', () => {
|
|
|
335
192
|
});
|
|
336
193
|
|
|
337
194
|
describe('users type tests', () => {
|
|
338
|
-
it('should produce a defineUserUpdate result assignable to users.updateMe body', () => {
|
|
339
|
-
const payload = defineUserUpdate({ firstname: 'Jane', lastname: 'Doe' });
|
|
340
|
-
|
|
341
|
-
type UpdateBody = Parameters<ReturnType<typeof createManagementApiClient>['users']['updateMe']>[0]['body'];
|
|
342
|
-
type InnerType = UpdateBody['user'];
|
|
343
|
-
|
|
344
|
-
expectTypeOf(payload).toExtend<InnerType>();
|
|
345
|
-
});
|
|
346
|
-
|
|
347
195
|
it('should return user in response from updateMe', async () => {
|
|
348
196
|
const client = createManagementApiClient(CLIENT_CONFIG);
|
|
349
197
|
const result = await client.users.updateMe({
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { defineBlock, defineField,
|
|
2
|
-
import {
|
|
1
|
+
import { defineBlock, defineField, defineSchema, type Schema as InferSchema } from '@storyblok/schema';
|
|
2
|
+
import { storyblokColorField } from '@storyblok/schema/field-plugins';
|
|
3
|
+
import { createManagementApiClient, type PluginFieldValue, type Story as StoryMapi } from '@storyblok/management-api-client';
|
|
3
4
|
import { describe, expectTypeOf, it } from 'vitest';
|
|
4
5
|
|
|
5
6
|
// Nestable block — not a root story type
|
|
6
7
|
const teaserComponent = defineBlock({
|
|
7
8
|
name: 'teaser',
|
|
8
|
-
|
|
9
|
+
fields: [
|
|
9
10
|
defineField('text', { type: 'text' }),
|
|
10
11
|
defineField('image', { type: 'asset' }),
|
|
11
12
|
],
|
|
@@ -18,7 +19,7 @@ const teaserComponent = defineBlock({
|
|
|
18
19
|
const heroComponent = defineBlock({
|
|
19
20
|
name: 'hero',
|
|
20
21
|
is_root: true,
|
|
21
|
-
|
|
22
|
+
fields: [
|
|
22
23
|
defineField('title', { type: 'text' }),
|
|
23
24
|
defineField('count', { type: 'number' }),
|
|
24
25
|
// bloks field without a whitelist — resolves to nestable components only
|
|
@@ -34,12 +35,12 @@ const _pageComponent = defineBlock({
|
|
|
34
35
|
name: 'page',
|
|
35
36
|
is_root: true,
|
|
36
37
|
is_nestable: false,
|
|
37
|
-
|
|
38
|
+
fields: [
|
|
38
39
|
defineField('headline', { type: 'text' }),
|
|
39
40
|
defineField('body', { type: 'richtext' }),
|
|
40
|
-
defineField('teasers', { type: 'bloks',
|
|
41
|
-
defineField('hero', { type: 'bloks',
|
|
42
|
-
defineField('blocks', { type: 'bloks',
|
|
41
|
+
defineField('teasers', { type: 'bloks', allow: [teaserComponent.name] }),
|
|
42
|
+
defineField('hero', { type: 'bloks', allow: [heroComponent.name] }),
|
|
43
|
+
defineField('blocks', { type: 'bloks', allow: [heroComponent.name, teaserComponent.name] }),
|
|
43
44
|
],
|
|
44
45
|
id: 0,
|
|
45
46
|
created_at: '',
|
|
@@ -237,36 +238,7 @@ describe('createManagementApiClient with .withTypes()', () => {
|
|
|
237
238
|
});
|
|
238
239
|
});
|
|
239
240
|
|
|
240
|
-
describe('
|
|
241
|
-
it('should produce a defineStoryCreate result accepted by untyped stories.create', async () => {
|
|
242
|
-
const createPayload = defineStoryCreate(_pageComponent, {
|
|
243
|
-
name: 'My Page',
|
|
244
|
-
content: { headline: 'Hello' },
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
const client = createManagementApiClient(CLIENT_CONFIG);
|
|
248
|
-
await client.stories.create({ body: { story: createPayload } });
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
it('should produce a defineStoryUpdate result accepted by untyped stories.update', async () => {
|
|
252
|
-
const updatePayload = defineStoryUpdate(_pageComponent, {
|
|
253
|
-
content: { headline: 'Updated' },
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
const client = createManagementApiClient(CLIENT_CONFIG);
|
|
257
|
-
await client.stories.update(1, { body: { story: updatePayload } });
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
it('should produce a defineStoryCreate result for hero component accepted by untyped stories.create', async () => {
|
|
261
|
-
const createPayload = defineStoryCreate(heroComponent, {
|
|
262
|
-
name: 'My Hero',
|
|
263
|
-
content: { title: 'Welcome', count: 42, sections: [] },
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
const client = createManagementApiClient(CLIENT_CONFIG);
|
|
267
|
-
await client.stories.create({ body: { story: createPayload } });
|
|
268
|
-
});
|
|
269
|
-
|
|
241
|
+
describe('withTypes() write-body narrowing', () => {
|
|
270
242
|
it('should reject nestable-only component name in create body content', () => {
|
|
271
243
|
const client = createManagementApiClient(CLIENT_CONFIG).withTypes<StoryblokTypes>();
|
|
272
244
|
client.stories.create({
|
|
@@ -295,12 +267,44 @@ describe('defineStoryCreate / defineStoryUpdate combined with mapi client', () =
|
|
|
295
267
|
expectTypeOf(story.content.component).toEqualTypeOf<'page' | 'hero'>();
|
|
296
268
|
|
|
297
269
|
if (story.content.component === 'page') {
|
|
298
|
-
|
|
299
|
-
name: story.name,
|
|
300
|
-
content: { headline: story.content.headline ?? 'Default' },
|
|
301
|
-
});
|
|
302
|
-
expectTypeOf(updatePayload).toMatchTypeOf<{ name?: string | null }>();
|
|
270
|
+
expectTypeOf(story.content.headline).toEqualTypeOf<string | null | undefined>();
|
|
303
271
|
}
|
|
304
272
|
}
|
|
305
273
|
});
|
|
306
274
|
});
|
|
275
|
+
|
|
276
|
+
describe('createManagementApiClient with .withTypes() — field plugins', () => {
|
|
277
|
+
const _themedComponent = defineBlock({
|
|
278
|
+
name: 'themed',
|
|
279
|
+
is_root: true,
|
|
280
|
+
fields: [
|
|
281
|
+
defineField('bg', { type: 'custom', field_type: 'storyblok-colorpicker' }),
|
|
282
|
+
defineField('legacy', { type: 'custom', field_type: 'unregistered-plugin' }),
|
|
283
|
+
],
|
|
284
|
+
});
|
|
285
|
+
const _schema = defineSchema({
|
|
286
|
+
blocks: { themedComponent: _themedComponent },
|
|
287
|
+
fieldPlugins: { storyblokColorField },
|
|
288
|
+
});
|
|
289
|
+
type Schema = InferSchema<typeof _schema>;
|
|
290
|
+
|
|
291
|
+
it('resolves a registered custom field to the validator output merged with the plugin envelope', async () => {
|
|
292
|
+
const client = createManagementApiClient(CLIENT_CONFIG).withTypes<Schema>();
|
|
293
|
+
const result = await client.stories.get(123);
|
|
294
|
+
if (result.data?.story && result.data.story.content.component === 'themed') {
|
|
295
|
+
expectTypeOf<NonNullable<typeof result.data.story.content.bg>>().toEqualTypeOf<{
|
|
296
|
+
color: string;
|
|
297
|
+
plugin: string;
|
|
298
|
+
_uid?: string;
|
|
299
|
+
}>();
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('leaves an unregistered custom field as PluginFieldValue', async () => {
|
|
304
|
+
const client = createManagementApiClient(CLIENT_CONFIG).withTypes<Schema>();
|
|
305
|
+
const result = await client.stories.get(123);
|
|
306
|
+
if (result.data?.story && result.data.story.content.component === 'themed') {
|
|
307
|
+
expectTypeOf<NonNullable<typeof result.data.story.content.legacy>>().toEqualTypeOf<PluginFieldValue>();
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
});
|
package/test/GUIDE.md
CHANGED
|
@@ -48,7 +48,7 @@ node packages/cli/dist/index.mjs components push \
|
|
|
48
48
|
|
|
49
49
|
**Always push components before stories.** The CLI validates that each story's `content.component` (and any nested block components) exist in the space. Pushing stories first fails if the referenced components are not yet present.
|
|
50
50
|
|
|
51
|
-
You can do the same with stories
|
|
51
|
+
You can do the same with stories. **Story filename convention:** the CLI requires files named `{slug}_{uuid}.json` where the part after the last `_` exactly matches `story.uuid` in the JSON. Use hyphens (not underscores) in UUIDs. A mismatch causes Pass 2 of the push (reference mapping + content update) to silently skip all stories.
|
|
52
52
|
|
|
53
53
|
## Known quirks
|
|
54
54
|
|
|
@@ -56,4 +56,4 @@ You can do the same with stories and `defineStory`. **Story filename convention:
|
|
|
56
56
|
- **MAPI `stories.get` takes a positional ID, not an options object:** `client.stories.get(id)` — there is no `getBySlug`. The `list` response does not include `content`; call `get(id)` for full content.
|
|
57
57
|
- **MAPI client constructor uses `personalAccessToken`:** `createManagementApiClient({ personalAccessToken: '...', spaceId: ... })`.
|
|
58
58
|
- **Delete methods are `delete`:** Use `client.RESOURCE.delete(id)`.
|
|
59
|
-
- **Each story JSON must have a unique `id`.**
|
|
59
|
+
- **Each story JSON must have a unique `id`.** If multiple story files share the same `id`, the CLI's manifest maps all of them to the same `old_id` entry, causing "slug already taken" errors.
|