@bernierllc/contentful-cma-client 1.0.2
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/.eslintrc.cjs +34 -0
- package/README.md +441 -0
- package/dist/ContentfulCMAClient.d.ts +119 -0
- package/dist/ContentfulCMAClient.js +538 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +12 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.js +9 -0
- package/jest.config.cjs +31 -0
- package/package.json +44 -0
- package/src/ContentfulCMAClient.ts +694 -0
- package/src/__tests__/ContentfulCMAClient.test.ts +841 -0
- package/src/index.ts +16 -0
- package/src/types.ts +51 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import contentfulManagement, {
|
|
10
|
+
PlainClientAPI
|
|
11
|
+
} from 'contentful-management';
|
|
12
|
+
import {
|
|
13
|
+
ContentfulEntry,
|
|
14
|
+
ContentfulAsset,
|
|
15
|
+
ContentfulContentType,
|
|
16
|
+
ContentfulCMAConfig
|
|
17
|
+
} from '@bernierllc/contentful-types';
|
|
18
|
+
import { Logger, LogLevel, ConsoleTransport } from '@bernierllc/logger';
|
|
19
|
+
import {
|
|
20
|
+
CMAQueryOptions,
|
|
21
|
+
CMAEntryCreateOptions,
|
|
22
|
+
CMAEntryUpdateOptions,
|
|
23
|
+
CMAAssetCreateOptions,
|
|
24
|
+
CMABulkOperationResult
|
|
25
|
+
} from './types';
|
|
26
|
+
|
|
27
|
+
export class ContentfulCMAClient {
|
|
28
|
+
private client: PlainClientAPI;
|
|
29
|
+
private logger: Logger;
|
|
30
|
+
private config: Required<ContentfulCMAConfig>;
|
|
31
|
+
|
|
32
|
+
constructor(config: ContentfulCMAConfig) {
|
|
33
|
+
// Set defaults
|
|
34
|
+
this.config = {
|
|
35
|
+
environmentId: 'master',
|
|
36
|
+
host: 'api.contentful.com',
|
|
37
|
+
retryOnError: true,
|
|
38
|
+
timeout: 30000,
|
|
39
|
+
...config
|
|
40
|
+
} as Required<ContentfulCMAConfig>;
|
|
41
|
+
|
|
42
|
+
// Initialize logger
|
|
43
|
+
this.logger = new Logger({
|
|
44
|
+
level: LogLevel.INFO,
|
|
45
|
+
transports: [new ConsoleTransport()],
|
|
46
|
+
context: {
|
|
47
|
+
service: 'contentful-cma-client',
|
|
48
|
+
version: '1.0.0',
|
|
49
|
+
spaceId: config.spaceId,
|
|
50
|
+
environmentId: this.config.environmentId
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Create contentful-management client with plain API
|
|
55
|
+
this.client = contentfulManagement.createClient(
|
|
56
|
+
{
|
|
57
|
+
accessToken: config.accessToken,
|
|
58
|
+
host: this.config.host
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
type: 'plain',
|
|
62
|
+
defaults: {
|
|
63
|
+
spaceId: config.spaceId,
|
|
64
|
+
environmentId: this.config.environmentId
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
this.logger.info('ContentfulCMAClient initialized', {
|
|
70
|
+
spaceId: config.spaceId,
|
|
71
|
+
environmentId: this.config.environmentId,
|
|
72
|
+
host: this.config.host
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get entry by ID
|
|
78
|
+
*/
|
|
79
|
+
async getEntry<T = Record<string, unknown>>(
|
|
80
|
+
entryId: string
|
|
81
|
+
): Promise<ContentfulEntry<T>> {
|
|
82
|
+
try {
|
|
83
|
+
this.logger.debug('Getting entry', { entryId });
|
|
84
|
+
|
|
85
|
+
const entry = await this.client.entry.get({ entryId });
|
|
86
|
+
|
|
87
|
+
this.logger.debug('Entry retrieved', { entryId });
|
|
88
|
+
return entry as unknown as ContentfulEntry<T>;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
91
|
+
this.logger.error('Failed to get entry', errorObj, { entryId });
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get multiple entries with optional query parameters
|
|
98
|
+
*/
|
|
99
|
+
async getEntries<T = Record<string, unknown>>(
|
|
100
|
+
query?: CMAQueryOptions
|
|
101
|
+
): Promise<ContentfulEntry<T>[]> {
|
|
102
|
+
try {
|
|
103
|
+
this.logger.debug('Getting entries', { query: query || {} });
|
|
104
|
+
|
|
105
|
+
const response = await this.client.entry.getMany({
|
|
106
|
+
query: query as Record<string, unknown>
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
this.logger.debug('Entries retrieved', {
|
|
110
|
+
count: response.items.length,
|
|
111
|
+
total: response.total
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return response.items as unknown as ContentfulEntry<T>[];
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
117
|
+
this.logger.error('Failed to get entries', errorObj, { query: query || {} });
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get all entries with automatic pagination
|
|
124
|
+
*/
|
|
125
|
+
async getAllEntries<T = Record<string, unknown>>(
|
|
126
|
+
query?: Omit<CMAQueryOptions, 'skip' | 'limit'>
|
|
127
|
+
): Promise<ContentfulEntry<T>[]> {
|
|
128
|
+
const all: ContentfulEntry<T>[] = [];
|
|
129
|
+
let skip = 0;
|
|
130
|
+
const limit = 100;
|
|
131
|
+
|
|
132
|
+
this.logger.debug('Getting all entries with pagination', { query: query || {} });
|
|
133
|
+
|
|
134
|
+
let hasMore = true;
|
|
135
|
+
while (hasMore) {
|
|
136
|
+
const response = await this.client.entry.getMany({
|
|
137
|
+
query: { ...query, skip, limit } as Record<string, unknown>
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
all.push(...(response.items as unknown as ContentfulEntry<T>[]));
|
|
141
|
+
|
|
142
|
+
this.logger.debug('Retrieved entry batch', {
|
|
143
|
+
batch: response.items.length,
|
|
144
|
+
total: all.length,
|
|
145
|
+
remaining: response.total - (skip + response.items.length)
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (skip + response.items.length >= response.total) {
|
|
149
|
+
hasMore = false;
|
|
150
|
+
} else {
|
|
151
|
+
skip += response.items.length;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
this.logger.info('All entries retrieved', { totalCount: all.length });
|
|
156
|
+
return all;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Create entry
|
|
161
|
+
*/
|
|
162
|
+
async createEntry<T = Record<string, unknown>>(
|
|
163
|
+
contentTypeId: string,
|
|
164
|
+
fields: T,
|
|
165
|
+
options?: CMAEntryCreateOptions
|
|
166
|
+
): Promise<ContentfulEntry<T>> {
|
|
167
|
+
try {
|
|
168
|
+
this.logger.debug('Creating entry', { contentTypeId, options: options || {} });
|
|
169
|
+
|
|
170
|
+
const entry = await this.client.entry.create(
|
|
171
|
+
{ contentTypeId, ...(options?.entryId ? { entryId: options.entryId } : {}) },
|
|
172
|
+
{ fields: fields as Record<string, unknown> }
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
this.logger.info('Entry created', {
|
|
176
|
+
entryId: entry.sys.id,
|
|
177
|
+
contentTypeId
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return entry as unknown as ContentfulEntry<T>;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
183
|
+
this.logger.error('Failed to create entry', errorObj, { contentTypeId });
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Update entry
|
|
190
|
+
*/
|
|
191
|
+
async updateEntry<T = Record<string, unknown>>(
|
|
192
|
+
entryId: string,
|
|
193
|
+
fields: T,
|
|
194
|
+
version: number,
|
|
195
|
+
_options?: CMAEntryUpdateOptions
|
|
196
|
+
): Promise<ContentfulEntry<T>> {
|
|
197
|
+
try {
|
|
198
|
+
this.logger.debug('Updating entry', { entryId, version });
|
|
199
|
+
|
|
200
|
+
// Type assertion needed due to contentful-management strict typing
|
|
201
|
+
const entry = await this.client.entry.update(
|
|
202
|
+
{ entryId },
|
|
203
|
+
{
|
|
204
|
+
sys: { version } as any,
|
|
205
|
+
fields: fields as Record<string, unknown>
|
|
206
|
+
} as any
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
this.logger.info('Entry updated', { entryId, newVersion: entry.sys.version });
|
|
210
|
+
return entry as unknown as ContentfulEntry<T>;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
213
|
+
this.logger.error('Failed to update entry', errorObj, { entryId, version });
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Publish entry
|
|
220
|
+
*/
|
|
221
|
+
async publishEntry(
|
|
222
|
+
entryId: string,
|
|
223
|
+
version: number
|
|
224
|
+
): Promise<ContentfulEntry> {
|
|
225
|
+
try {
|
|
226
|
+
this.logger.debug('Publishing entry', { entryId, version });
|
|
227
|
+
|
|
228
|
+
// Type assertion needed due to contentful-management strict typing
|
|
229
|
+
const entry = await this.client.entry.publish(
|
|
230
|
+
{ entryId },
|
|
231
|
+
{ sys: { version } } as any
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
this.logger.info('Entry published', {
|
|
235
|
+
entryId,
|
|
236
|
+
publishedVersion: entry.sys.publishedVersion
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return entry as unknown as ContentfulEntry;
|
|
240
|
+
} catch (error) {
|
|
241
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
242
|
+
this.logger.error('Failed to publish entry', errorObj, { entryId, version });
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Unpublish entry
|
|
249
|
+
*/
|
|
250
|
+
async unpublishEntry(entryId: string): Promise<ContentfulEntry> {
|
|
251
|
+
try {
|
|
252
|
+
this.logger.debug('Unpublishing entry', { entryId });
|
|
253
|
+
|
|
254
|
+
const entry = await this.client.entry.unpublish({ entryId });
|
|
255
|
+
|
|
256
|
+
this.logger.info('Entry unpublished', { entryId });
|
|
257
|
+
return entry as unknown as ContentfulEntry;
|
|
258
|
+
} catch (error) {
|
|
259
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
260
|
+
this.logger.error('Failed to unpublish entry', errorObj, { entryId });
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Archive entry
|
|
267
|
+
*/
|
|
268
|
+
async archiveEntry(
|
|
269
|
+
entryId: string,
|
|
270
|
+
version: number
|
|
271
|
+
): Promise<ContentfulEntry> {
|
|
272
|
+
try {
|
|
273
|
+
this.logger.debug('Archiving entry', { entryId, version });
|
|
274
|
+
|
|
275
|
+
// Type assertion needed due to contentful-management strict typing
|
|
276
|
+
const entry = await this.client.entry.archive({ entryId, version } as any);
|
|
277
|
+
|
|
278
|
+
this.logger.info('Entry archived', { entryId });
|
|
279
|
+
return entry as unknown as ContentfulEntry;
|
|
280
|
+
} catch (error) {
|
|
281
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
282
|
+
this.logger.error('Failed to archive entry', errorObj, { entryId, version });
|
|
283
|
+
throw error;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Unarchive entry
|
|
289
|
+
*/
|
|
290
|
+
async unarchiveEntry(entryId: string): Promise<ContentfulEntry> {
|
|
291
|
+
try {
|
|
292
|
+
this.logger.debug('Unarchiving entry', { entryId });
|
|
293
|
+
|
|
294
|
+
const entry = await this.client.entry.unarchive({ entryId });
|
|
295
|
+
|
|
296
|
+
this.logger.info('Entry unarchived', { entryId });
|
|
297
|
+
return entry as unknown as ContentfulEntry;
|
|
298
|
+
} catch (error) {
|
|
299
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
300
|
+
this.logger.error('Failed to unarchive entry', errorObj, { entryId });
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Delete entry
|
|
307
|
+
*/
|
|
308
|
+
async deleteEntry(entryId: string): Promise<void> {
|
|
309
|
+
try {
|
|
310
|
+
this.logger.debug('Deleting entry', { entryId });
|
|
311
|
+
|
|
312
|
+
await this.client.entry.delete({ entryId });
|
|
313
|
+
|
|
314
|
+
this.logger.info('Entry deleted', { entryId });
|
|
315
|
+
} catch (error) {
|
|
316
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
317
|
+
this.logger.error('Failed to delete entry', errorObj, { entryId });
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get asset by ID
|
|
324
|
+
*/
|
|
325
|
+
async getAsset(assetId: string): Promise<ContentfulAsset> {
|
|
326
|
+
try {
|
|
327
|
+
this.logger.debug('Getting asset', { assetId });
|
|
328
|
+
|
|
329
|
+
const asset = await this.client.asset.get({ assetId });
|
|
330
|
+
|
|
331
|
+
this.logger.debug('Asset retrieved', { assetId });
|
|
332
|
+
return asset as unknown as ContentfulAsset;
|
|
333
|
+
} catch (error) {
|
|
334
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
335
|
+
this.logger.error('Failed to get asset', errorObj, { assetId });
|
|
336
|
+
throw error;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Get multiple assets with optional query parameters
|
|
342
|
+
*/
|
|
343
|
+
async getAssets(query?: CMAQueryOptions): Promise<ContentfulAsset[]> {
|
|
344
|
+
try {
|
|
345
|
+
this.logger.debug('Getting assets', { query: query || {} });
|
|
346
|
+
|
|
347
|
+
const response = await this.client.asset.getMany({
|
|
348
|
+
query: query as Record<string, unknown>
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
this.logger.debug('Assets retrieved', {
|
|
352
|
+
count: response.items.length,
|
|
353
|
+
total: response.total
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
return response.items as unknown as ContentfulAsset[];
|
|
357
|
+
} catch (error) {
|
|
358
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
359
|
+
this.logger.error('Failed to get assets', errorObj, { query: query || {} });
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Get all assets with automatic pagination
|
|
366
|
+
*/
|
|
367
|
+
async getAllAssets(
|
|
368
|
+
query?: Omit<CMAQueryOptions, 'skip' | 'limit'>
|
|
369
|
+
): Promise<ContentfulAsset[]> {
|
|
370
|
+
const all: ContentfulAsset[] = [];
|
|
371
|
+
let skip = 0;
|
|
372
|
+
const limit = 100;
|
|
373
|
+
|
|
374
|
+
this.logger.debug('Getting all assets with pagination', { query: query || {} });
|
|
375
|
+
|
|
376
|
+
let hasMore = true;
|
|
377
|
+
while (hasMore) {
|
|
378
|
+
const response = await this.client.asset.getMany({
|
|
379
|
+
query: { ...query, skip, limit } as Record<string, unknown>
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
all.push(...(response.items as unknown as ContentfulAsset[]));
|
|
383
|
+
|
|
384
|
+
this.logger.debug('Retrieved asset batch', {
|
|
385
|
+
batch: response.items.length,
|
|
386
|
+
total: all.length,
|
|
387
|
+
remaining: response.total - (skip + response.items.length)
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
if (skip + response.items.length >= response.total) {
|
|
391
|
+
hasMore = false;
|
|
392
|
+
} else {
|
|
393
|
+
skip += response.items.length;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
this.logger.info('All assets retrieved', { totalCount: all.length });
|
|
398
|
+
return all;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Create asset
|
|
403
|
+
*/
|
|
404
|
+
async createAsset(
|
|
405
|
+
fields: Record<string, unknown>,
|
|
406
|
+
options?: CMAAssetCreateOptions
|
|
407
|
+
): Promise<ContentfulAsset> {
|
|
408
|
+
try {
|
|
409
|
+
this.logger.debug('Creating asset', { options: options || {} });
|
|
410
|
+
|
|
411
|
+
// Type assertion needed due to contentful-management strict typing
|
|
412
|
+
const asset = await this.client.asset.create(
|
|
413
|
+
{ ...(options?.assetId ? { assetId: options.assetId } : {}) },
|
|
414
|
+
{ fields } as any
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
this.logger.info('Asset created', { assetId: asset.sys.id });
|
|
418
|
+
return asset as unknown as ContentfulAsset;
|
|
419
|
+
} catch (error) {
|
|
420
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
421
|
+
this.logger.error('Failed to create asset', errorObj);
|
|
422
|
+
throw error;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Process asset (for uploaded files)
|
|
428
|
+
*/
|
|
429
|
+
async processAsset(
|
|
430
|
+
assetId: string,
|
|
431
|
+
version: number,
|
|
432
|
+
locale: string = 'en-US'
|
|
433
|
+
): Promise<ContentfulAsset> {
|
|
434
|
+
try {
|
|
435
|
+
this.logger.debug('Processing asset', { assetId, version, locale });
|
|
436
|
+
|
|
437
|
+
// Type assertion needed due to contentful-management strict typing
|
|
438
|
+
const asset = await this.client.asset.processForLocale(
|
|
439
|
+
{ assetId, version } as any,
|
|
440
|
+
{ sys: { version } } as any,
|
|
441
|
+
locale
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
this.logger.info('Asset processing started', { assetId });
|
|
445
|
+
return asset as unknown as ContentfulAsset;
|
|
446
|
+
} catch (error) {
|
|
447
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
448
|
+
this.logger.error('Failed to process asset', errorObj, {
|
|
449
|
+
assetId,
|
|
450
|
+
version,
|
|
451
|
+
locale
|
|
452
|
+
});
|
|
453
|
+
throw error;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Publish asset
|
|
459
|
+
*/
|
|
460
|
+
async publishAsset(
|
|
461
|
+
assetId: string,
|
|
462
|
+
version: number
|
|
463
|
+
): Promise<ContentfulAsset> {
|
|
464
|
+
try {
|
|
465
|
+
this.logger.debug('Publishing asset', { assetId, version });
|
|
466
|
+
|
|
467
|
+
// Type assertion needed due to contentful-management strict typing
|
|
468
|
+
const asset = await this.client.asset.publish(
|
|
469
|
+
{ assetId },
|
|
470
|
+
{ sys: { version } } as any
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
this.logger.info('Asset published', {
|
|
474
|
+
assetId,
|
|
475
|
+
publishedVersion: asset.sys.publishedVersion
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
return asset as unknown as ContentfulAsset;
|
|
479
|
+
} catch (error) {
|
|
480
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
481
|
+
this.logger.error('Failed to publish asset', errorObj, { assetId, version });
|
|
482
|
+
throw error;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Unpublish asset
|
|
488
|
+
*/
|
|
489
|
+
async unpublishAsset(assetId: string): Promise<ContentfulAsset> {
|
|
490
|
+
try {
|
|
491
|
+
this.logger.debug('Unpublishing asset', { assetId });
|
|
492
|
+
|
|
493
|
+
const asset = await this.client.asset.unpublish({ assetId });
|
|
494
|
+
|
|
495
|
+
this.logger.info('Asset unpublished', { assetId });
|
|
496
|
+
return asset as unknown as ContentfulAsset;
|
|
497
|
+
} catch (error) {
|
|
498
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
499
|
+
this.logger.error('Failed to unpublish asset', errorObj, { assetId });
|
|
500
|
+
throw error;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Delete asset
|
|
506
|
+
*/
|
|
507
|
+
async deleteAsset(assetId: string): Promise<void> {
|
|
508
|
+
try {
|
|
509
|
+
this.logger.debug('Deleting asset', { assetId });
|
|
510
|
+
|
|
511
|
+
await this.client.asset.delete({ assetId });
|
|
512
|
+
|
|
513
|
+
this.logger.info('Asset deleted', { assetId });
|
|
514
|
+
} catch (error) {
|
|
515
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
516
|
+
this.logger.error('Failed to delete asset', errorObj, { assetId });
|
|
517
|
+
throw error;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Get content type by ID
|
|
523
|
+
*/
|
|
524
|
+
async getContentType(contentTypeId: string): Promise<ContentfulContentType> {
|
|
525
|
+
try {
|
|
526
|
+
this.logger.debug('Getting content type', { contentTypeId });
|
|
527
|
+
|
|
528
|
+
const contentType = await this.client.contentType.get({ contentTypeId });
|
|
529
|
+
|
|
530
|
+
this.logger.debug('Content type retrieved', { contentTypeId });
|
|
531
|
+
return contentType as unknown as ContentfulContentType;
|
|
532
|
+
} catch (error) {
|
|
533
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
534
|
+
this.logger.error('Failed to get content type', errorObj, { contentTypeId });
|
|
535
|
+
throw error;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Get all content types
|
|
541
|
+
*/
|
|
542
|
+
async getContentTypes(
|
|
543
|
+
query?: CMAQueryOptions
|
|
544
|
+
): Promise<ContentfulContentType[]> {
|
|
545
|
+
try {
|
|
546
|
+
this.logger.debug('Getting content types', { query: query || {} });
|
|
547
|
+
|
|
548
|
+
const response = await this.client.contentType.getMany({
|
|
549
|
+
query: query as Record<string, unknown>
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
this.logger.info('Content types retrieved', { count: response.items.length });
|
|
553
|
+
return response.items as unknown as ContentfulContentType[];
|
|
554
|
+
} catch (error) {
|
|
555
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
556
|
+
this.logger.error('Failed to get content types', errorObj, { query: query || {} });
|
|
557
|
+
throw error;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Bulk create entries
|
|
563
|
+
*/
|
|
564
|
+
async bulkCreateEntries<T = Record<string, unknown>>(
|
|
565
|
+
entries: Array<{
|
|
566
|
+
contentTypeId: string;
|
|
567
|
+
fields: T;
|
|
568
|
+
entryId?: string;
|
|
569
|
+
}>
|
|
570
|
+
): Promise<CMABulkOperationResult<ContentfulEntry<T>>> {
|
|
571
|
+
const results: CMABulkOperationResult<ContentfulEntry<T>> = {
|
|
572
|
+
successful: [],
|
|
573
|
+
failed: []
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
this.logger.info('Starting bulk entry creation', { count: entries.length });
|
|
577
|
+
|
|
578
|
+
for (const entry of entries) {
|
|
579
|
+
try {
|
|
580
|
+
const created = await this.createEntry<T>(
|
|
581
|
+
entry.contentTypeId,
|
|
582
|
+
entry.fields,
|
|
583
|
+
{ entryId: entry.entryId }
|
|
584
|
+
);
|
|
585
|
+
results.successful.push(created);
|
|
586
|
+
} catch (error) {
|
|
587
|
+
results.failed.push({
|
|
588
|
+
item: { contentTypeId: entry.contentTypeId, fields: entry.fields },
|
|
589
|
+
error: this.getErrorMessage(error)
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
this.logger.info('Bulk entry creation completed', {
|
|
595
|
+
successful: results.successful.length,
|
|
596
|
+
failed: results.failed.length
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
return results;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Bulk delete entries
|
|
604
|
+
*/
|
|
605
|
+
async bulkDeleteEntries(
|
|
606
|
+
entryIds: string[]
|
|
607
|
+
): Promise<CMABulkOperationResult<string>> {
|
|
608
|
+
const results: CMABulkOperationResult<string> = {
|
|
609
|
+
successful: [],
|
|
610
|
+
failed: []
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
this.logger.info('Starting bulk entry deletion', { count: entryIds.length });
|
|
614
|
+
|
|
615
|
+
for (const entryId of entryIds) {
|
|
616
|
+
try {
|
|
617
|
+
await this.deleteEntry(entryId);
|
|
618
|
+
results.successful.push(entryId);
|
|
619
|
+
} catch (error) {
|
|
620
|
+
results.failed.push({
|
|
621
|
+
item: entryId,
|
|
622
|
+
error: this.getErrorMessage(error)
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
this.logger.info('Bulk entry deletion completed', {
|
|
628
|
+
successful: results.successful.length,
|
|
629
|
+
failed: results.failed.length
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
return results;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Bulk publish entries
|
|
637
|
+
*/
|
|
638
|
+
async bulkPublishEntries(
|
|
639
|
+
entries: Array<{ entryId: string; version: number }>
|
|
640
|
+
): Promise<CMABulkOperationResult<ContentfulEntry>> {
|
|
641
|
+
const results: CMABulkOperationResult<ContentfulEntry> = {
|
|
642
|
+
successful: [],
|
|
643
|
+
failed: []
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
this.logger.info('Starting bulk entry publishing', { count: entries.length });
|
|
647
|
+
|
|
648
|
+
for (const entry of entries) {
|
|
649
|
+
try {
|
|
650
|
+
const published = await this.publishEntry(entry.entryId, entry.version);
|
|
651
|
+
results.successful.push(published);
|
|
652
|
+
} catch (error) {
|
|
653
|
+
results.failed.push({
|
|
654
|
+
item: entry,
|
|
655
|
+
error: this.getErrorMessage(error)
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
this.logger.info('Bulk entry publishing completed', {
|
|
661
|
+
successful: results.successful.length,
|
|
662
|
+
failed: results.failed.length
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
return results;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Helper method to extract error message
|
|
670
|
+
*/
|
|
671
|
+
private getErrorMessage(error: unknown): string {
|
|
672
|
+
if (error instanceof Error) {
|
|
673
|
+
return error.message;
|
|
674
|
+
}
|
|
675
|
+
if (typeof error === 'string') {
|
|
676
|
+
return error;
|
|
677
|
+
}
|
|
678
|
+
return 'Unknown error';
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Get current space ID
|
|
683
|
+
*/
|
|
684
|
+
getSpaceId(): string {
|
|
685
|
+
return this.config.spaceId;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Get current environment ID
|
|
690
|
+
*/
|
|
691
|
+
getEnvironmentId(): string {
|
|
692
|
+
return this.config.environmentId;
|
|
693
|
+
}
|
|
694
|
+
}
|