content-serialization 0.0.0
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/README.md +1 -0
- package/dist/_chunks/App-B8wd-5ZH.mjs +186 -0
- package/dist/_chunks/App-CF2xHAoO.js +186 -0
- package/dist/_chunks/en-B4KWt_jN.js +4 -0
- package/dist/_chunks/en-Byx4XI2L.mjs +4 -0
- package/dist/_chunks/index-B04ym5Xr.js +65 -0
- package/dist/_chunks/index-DgFuP8NI.mjs +66 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/index.mjs +4 -0
- package/dist/server/index.js +379 -0
- package/dist/server/index.mjs +375 -0
- package/package.json +71 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
import crypto from "crypto";
|
|
5
|
+
const bootstrap = ({ strapi: strapi2 }) => {
|
|
6
|
+
};
|
|
7
|
+
const destroy = ({ strapi: strapi2 }) => {
|
|
8
|
+
};
|
|
9
|
+
const register = ({ strapi: strapi2 }) => {
|
|
10
|
+
};
|
|
11
|
+
const config = {
|
|
12
|
+
default: {},
|
|
13
|
+
validator() {
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const contentTypes = {};
|
|
17
|
+
const controller = ({ strapi: strapi2 }) => ({
|
|
18
|
+
index(ctx) {
|
|
19
|
+
ctx.body = strapi2.plugin("content-serialization").service("service").getWelcomeMessage();
|
|
20
|
+
},
|
|
21
|
+
async pull(ctx) {
|
|
22
|
+
try {
|
|
23
|
+
const { stats, logs } = await strapi2.plugin("content-serialization").service("serialization").pull();
|
|
24
|
+
ctx.body = { status: "success", stats, logs };
|
|
25
|
+
} catch (e) {
|
|
26
|
+
ctx.badRequest(e.message);
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
async push(ctx) {
|
|
30
|
+
try {
|
|
31
|
+
const { stats, logs } = await strapi2.plugin("content-serialization").service("serialization").push();
|
|
32
|
+
ctx.body = { status: "success", stats, logs };
|
|
33
|
+
} catch (e) {
|
|
34
|
+
ctx.badRequest(e.message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
const controllers = {
|
|
39
|
+
controller
|
|
40
|
+
};
|
|
41
|
+
const middlewares = {};
|
|
42
|
+
const policies = {};
|
|
43
|
+
const contentAPIRoutes = () => ({
|
|
44
|
+
type: "content-api",
|
|
45
|
+
routes: [
|
|
46
|
+
{
|
|
47
|
+
method: "GET",
|
|
48
|
+
path: "/",
|
|
49
|
+
// name of the controller file & the method.
|
|
50
|
+
handler: "controller.index",
|
|
51
|
+
config: {
|
|
52
|
+
policies: []
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
method: "POST",
|
|
57
|
+
path: "/perform-pull",
|
|
58
|
+
handler: "controller.pull",
|
|
59
|
+
config: {
|
|
60
|
+
policies: []
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
method: "POST",
|
|
65
|
+
path: "/perform-push",
|
|
66
|
+
handler: "controller.push",
|
|
67
|
+
config: {
|
|
68
|
+
policies: []
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
});
|
|
73
|
+
const adminAPIRoutes = () => ({
|
|
74
|
+
type: "admin",
|
|
75
|
+
routes: [
|
|
76
|
+
{
|
|
77
|
+
method: "POST",
|
|
78
|
+
path: "/perform-pull",
|
|
79
|
+
handler: "controller.pull",
|
|
80
|
+
config: {
|
|
81
|
+
policies: []
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
method: "POST",
|
|
86
|
+
path: "/perform-push",
|
|
87
|
+
handler: "controller.push",
|
|
88
|
+
config: {
|
|
89
|
+
policies: []
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
});
|
|
94
|
+
const routes = {
|
|
95
|
+
"content-api": contentAPIRoutes,
|
|
96
|
+
admin: adminAPIRoutes
|
|
97
|
+
};
|
|
98
|
+
const service = ({ strapi: strapi2 }) => ({
|
|
99
|
+
getWelcomeMessage() {
|
|
100
|
+
return "Welcome to Strapi 🚀";
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
const SERIALIZATION_DIR = path.join(process.cwd(), "content_serialization");
|
|
104
|
+
const CHECKSUM_FILE = path.join(SERIALIZATION_DIR, ".checksums.json");
|
|
105
|
+
function getHash(content) {
|
|
106
|
+
return crypto.createHash("md5").update(content).digest("hex");
|
|
107
|
+
}
|
|
108
|
+
function loadChecksums() {
|
|
109
|
+
try {
|
|
110
|
+
if (fs.existsSync(CHECKSUM_FILE)) {
|
|
111
|
+
return JSON.parse(fs.readFileSync(CHECKSUM_FILE, "utf8"));
|
|
112
|
+
}
|
|
113
|
+
} catch (e) {
|
|
114
|
+
}
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
117
|
+
function saveChecksums(checksums) {
|
|
118
|
+
try {
|
|
119
|
+
fs.writeFileSync(CHECKSUM_FILE, JSON.stringify(checksums, null, 2));
|
|
120
|
+
} catch (e) {
|
|
121
|
+
strapi.log.warn(`Failed to save checksums: ${e.message}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function ensureDir(dirPath) {
|
|
125
|
+
if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true });
|
|
126
|
+
}
|
|
127
|
+
function sanitizeFilename(name) {
|
|
128
|
+
return name.replace(/[^a-z0-9]/gi, "_").toLowerCase();
|
|
129
|
+
}
|
|
130
|
+
function sanitizeUser(user) {
|
|
131
|
+
if (!user || typeof user !== "object") return user;
|
|
132
|
+
return {
|
|
133
|
+
id: user.id,
|
|
134
|
+
documentId: user.documentId,
|
|
135
|
+
firstname: user.firstname,
|
|
136
|
+
lastname: user.lastname,
|
|
137
|
+
username: user.username
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function recursiveSanitize(data, attributes, componentSchemas) {
|
|
141
|
+
if (!data || typeof data !== "object") return data;
|
|
142
|
+
if (Array.isArray(data)) {
|
|
143
|
+
return data.map((item) => recursiveSanitize(item, attributes, componentSchemas));
|
|
144
|
+
}
|
|
145
|
+
const sanitized = {};
|
|
146
|
+
const keys = Object.keys(data);
|
|
147
|
+
let currentAttributes = attributes;
|
|
148
|
+
if (data.__component) {
|
|
149
|
+
const compSchema = componentSchemas[data.__component];
|
|
150
|
+
if (compSchema) {
|
|
151
|
+
currentAttributes = compSchema.attributes;
|
|
152
|
+
sanitized.__component = data.__component;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
keys.forEach((key) => {
|
|
156
|
+
if (key === "__component") return;
|
|
157
|
+
if (["id", "documentId", "createdAt", "updatedAt", "publishedAt", "createdBy", "updatedBy", "locale", "localizations"].includes(key)) return;
|
|
158
|
+
const value = data[key];
|
|
159
|
+
const attr = currentAttributes ? currentAttributes[key] : null;
|
|
160
|
+
if (!attr) {
|
|
161
|
+
sanitized[key] = value;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (attr.type === "relation" || attr.type === "media") {
|
|
165
|
+
if (Array.isArray(value)) {
|
|
166
|
+
sanitized[key] = value.map((v) => v?.documentId || v?.id).filter((v) => v);
|
|
167
|
+
} else if (value && typeof value === "object") {
|
|
168
|
+
sanitized[key] = value.documentId || value.id;
|
|
169
|
+
} else {
|
|
170
|
+
sanitized[key] = value;
|
|
171
|
+
}
|
|
172
|
+
} else if (attr.type === "component") {
|
|
173
|
+
const compSchema = componentSchemas[attr.component];
|
|
174
|
+
const compAttributes = compSchema ? compSchema.attributes : {};
|
|
175
|
+
sanitized[key] = recursiveSanitize(value, compAttributes, componentSchemas);
|
|
176
|
+
} else if (attr.type === "dynamiczone") {
|
|
177
|
+
sanitized[key] = recursiveSanitize(value, null, componentSchemas);
|
|
178
|
+
} else {
|
|
179
|
+
sanitized[key] = value;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
return sanitized;
|
|
183
|
+
}
|
|
184
|
+
function enforceSchemaStructure(data, attributes) {
|
|
185
|
+
if (data.createdBy) data.createdBy = sanitizeUser(data.createdBy);
|
|
186
|
+
if (data.updatedBy) data.updatedBy = sanitizeUser(data.updatedBy);
|
|
187
|
+
const ordered = {};
|
|
188
|
+
const commonKeys = ["id", "documentId", "title", "name", "slug"];
|
|
189
|
+
commonKeys.forEach((key) => {
|
|
190
|
+
if (data[key] !== void 0) ordered[key] = data[key];
|
|
191
|
+
});
|
|
192
|
+
if (attributes) {
|
|
193
|
+
Object.keys(attributes).forEach((key) => {
|
|
194
|
+
if (data[key] !== void 0) ordered[key] = data[key];
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
Object.keys(data).forEach((key) => {
|
|
198
|
+
if (ordered[key] === void 0) ordered[key] = data[key];
|
|
199
|
+
});
|
|
200
|
+
return ordered;
|
|
201
|
+
}
|
|
202
|
+
function getComponentSchemas() {
|
|
203
|
+
const schemas = {};
|
|
204
|
+
Object.keys(strapi.components).forEach((uid) => {
|
|
205
|
+
schemas[uid] = strapi.components[uid];
|
|
206
|
+
});
|
|
207
|
+
return schemas;
|
|
208
|
+
}
|
|
209
|
+
const serialization = ({ strapi: strapi2 }) => ({
|
|
210
|
+
async pull() {
|
|
211
|
+
ensureDir(SERIALIZATION_DIR);
|
|
212
|
+
const stats = { fetched: 0, skipped: 0, deleted: 0, errors: 0 };
|
|
213
|
+
const checksums = loadChecksums();
|
|
214
|
+
const logs = [];
|
|
215
|
+
const contentTypes2 = Object.keys(strapi2.contentTypes).filter((uid) => uid.startsWith("api::")).map((uid) => strapi2.contentTypes[uid]);
|
|
216
|
+
for (const contentType of contentTypes2) {
|
|
217
|
+
const typeName = contentType.collectionName || contentType.info.pluralName;
|
|
218
|
+
const singularName = contentType.info.singularName;
|
|
219
|
+
const uid = contentType.uid;
|
|
220
|
+
const typeDir = path.join(SERIALIZATION_DIR, typeName);
|
|
221
|
+
ensureDir(typeDir);
|
|
222
|
+
const processedFiles = /* @__PURE__ */ new Set();
|
|
223
|
+
logs.push(`
|
|
224
|
+
--- Processing ${typeName} ---`);
|
|
225
|
+
try {
|
|
226
|
+
const processItem = (item, filename) => {
|
|
227
|
+
const filePath = path.join(typeDir, filename);
|
|
228
|
+
const structuredData = enforceSchemaStructure(item, contentType.attributes);
|
|
229
|
+
const yamlContent = yaml.dump(structuredData);
|
|
230
|
+
const relPath = path.join(typeName, filename).replace(/\\/g, "/");
|
|
231
|
+
const hash = getHash(yamlContent);
|
|
232
|
+
checksums[relPath] = hash;
|
|
233
|
+
let hasChanges = true;
|
|
234
|
+
if (fs.existsSync(filePath)) {
|
|
235
|
+
if (fs.readFileSync(filePath, "utf8") === yamlContent) hasChanges = false;
|
|
236
|
+
}
|
|
237
|
+
if (hasChanges) {
|
|
238
|
+
fs.writeFileSync(filePath, yamlContent);
|
|
239
|
+
stats.fetched++;
|
|
240
|
+
logs.push(`[FETCHED] ${filename}`);
|
|
241
|
+
} else {
|
|
242
|
+
stats.skipped++;
|
|
243
|
+
logs.push(`[SKIPPED] ${filename}`);
|
|
244
|
+
}
|
|
245
|
+
processedFiles.add(filename);
|
|
246
|
+
};
|
|
247
|
+
const items = await strapi2.documents(uid).findMany({
|
|
248
|
+
populate: "*",
|
|
249
|
+
publicationState: "preview",
|
|
250
|
+
limit: 1e4
|
|
251
|
+
});
|
|
252
|
+
const itemArray = Array.isArray(items) ? items : [items];
|
|
253
|
+
if (contentType.kind === "singleType") {
|
|
254
|
+
const item = await strapi2.documents(uid).findFirst({ populate: "*" });
|
|
255
|
+
if (item) {
|
|
256
|
+
processItem(item, `${singularName}.yml`);
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
itemArray.forEach((item) => {
|
|
260
|
+
const nameKey = item.slug || item.title || item.name || item.documentId || item.id;
|
|
261
|
+
processItem(item, `${sanitizeFilename(String(nameKey))}.yml`);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
fs.readdirSync(typeDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml")).forEach((file) => {
|
|
265
|
+
if (!processedFiles.has(file)) {
|
|
266
|
+
fs.unlinkSync(path.join(typeDir, file));
|
|
267
|
+
stats.deleted++;
|
|
268
|
+
logs.push(`[DELETED] ${file}`);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
} catch (e) {
|
|
272
|
+
stats.errors++;
|
|
273
|
+
logs.push(`[ERROR] ${uid}: ${e.message}`);
|
|
274
|
+
strapi2.log.error(`[Serialization] Error processing ${uid}: ${e.message}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
saveChecksums(checksums);
|
|
278
|
+
logs.push(`
|
|
279
|
+
-----------------------------------`);
|
|
280
|
+
logs.push(`PULL SUMMARY`);
|
|
281
|
+
logs.push(`-----------------------------------`);
|
|
282
|
+
logs.push(`Fetched: ${stats.fetched}`);
|
|
283
|
+
logs.push(`Deleted: ${stats.deleted}`);
|
|
284
|
+
logs.push(`Skipped: ${stats.skipped}`);
|
|
285
|
+
logs.push(`Errors: ${stats.errors}`);
|
|
286
|
+
logs.push(`-----------------------------------`);
|
|
287
|
+
return { stats, logs };
|
|
288
|
+
},
|
|
289
|
+
async push() {
|
|
290
|
+
const stats = { created: 0, updated: 0, skipped: 0, errors: 0 };
|
|
291
|
+
const checksums = loadChecksums();
|
|
292
|
+
const componentSchemas = getComponentSchemas();
|
|
293
|
+
const logs = [];
|
|
294
|
+
const contentTypes2 = Object.keys(strapi2.contentTypes).filter((uid) => uid.startsWith("api::")).map((uid) => strapi2.contentTypes[uid]);
|
|
295
|
+
for (const contentType of contentTypes2) {
|
|
296
|
+
const typeName = contentType.collectionName || contentType.info.pluralName;
|
|
297
|
+
const uid = contentType.uid;
|
|
298
|
+
const typeDir = path.join(SERIALIZATION_DIR, typeName);
|
|
299
|
+
if (!fs.existsSync(typeDir)) continue;
|
|
300
|
+
const files = fs.readdirSync(typeDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
301
|
+
if (files.length > 0) logs.push(`
|
|
302
|
+
--- Processing ${typeName} ---`);
|
|
303
|
+
for (const file of files) {
|
|
304
|
+
const filePath = path.join(typeDir, file);
|
|
305
|
+
const rawContent = fs.readFileSync(filePath, "utf8");
|
|
306
|
+
const data = yaml.load(rawContent);
|
|
307
|
+
const relPath = path.join(typeName, file).replace(/\\/g, "/");
|
|
308
|
+
const currentHash = getHash(rawContent);
|
|
309
|
+
if (checksums[relPath] === currentHash) {
|
|
310
|
+
stats.skipped++;
|
|
311
|
+
logs.push(`[SKIPPED] ${file}`);
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
try {
|
|
315
|
+
let attributes = { ...data };
|
|
316
|
+
if (data.attributes) Object.assign(attributes, data.attributes);
|
|
317
|
+
const sanitizedData = recursiveSanitize(attributes, contentType.attributes, componentSchemas);
|
|
318
|
+
let targetId = data.documentId;
|
|
319
|
+
if (!targetId && contentType.attributes.slug && data.slug) {
|
|
320
|
+
const existing = await strapi2.documents(uid).findFirst({ filters: { slug: data.slug } });
|
|
321
|
+
if (existing) targetId = existing.documentId;
|
|
322
|
+
}
|
|
323
|
+
if (contentType.kind === "singleType") {
|
|
324
|
+
const existing = await strapi2.documents(uid).findFirst();
|
|
325
|
+
if (existing) targetId = existing.documentId;
|
|
326
|
+
}
|
|
327
|
+
if (targetId) {
|
|
328
|
+
await strapi2.documents(uid).update({ documentId: targetId, data: sanitizedData });
|
|
329
|
+
stats.updated++;
|
|
330
|
+
logs.push(`[UPDATED] ${file}`);
|
|
331
|
+
} else {
|
|
332
|
+
await strapi2.documents(uid).create({ data: sanitizedData });
|
|
333
|
+
stats.created++;
|
|
334
|
+
logs.push(`[CREATED] ${file}`);
|
|
335
|
+
}
|
|
336
|
+
checksums[relPath] = currentHash;
|
|
337
|
+
} catch (e) {
|
|
338
|
+
stats.errors++;
|
|
339
|
+
logs.push(`[ERROR] ${file}: ${e.message}`);
|
|
340
|
+
strapi2.log.error(`[Serialization] Push Error ${file}: ${e.message}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
saveChecksums(checksums);
|
|
345
|
+
logs.push(`
|
|
346
|
+
-----------------------------------`);
|
|
347
|
+
logs.push(`PUSH SUMMARY`);
|
|
348
|
+
logs.push(`-----------------------------------`);
|
|
349
|
+
logs.push(`Created: ${stats.created}`);
|
|
350
|
+
logs.push(`Updated: ${stats.updated}`);
|
|
351
|
+
logs.push(`Skipped: ${stats.skipped}`);
|
|
352
|
+
logs.push(`Errors: ${stats.errors}`);
|
|
353
|
+
logs.push(`-----------------------------------`);
|
|
354
|
+
return { stats, logs };
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
const services = {
|
|
358
|
+
service,
|
|
359
|
+
serialization
|
|
360
|
+
};
|
|
361
|
+
const index = {
|
|
362
|
+
register,
|
|
363
|
+
bootstrap,
|
|
364
|
+
destroy,
|
|
365
|
+
config,
|
|
366
|
+
controllers,
|
|
367
|
+
routes,
|
|
368
|
+
services,
|
|
369
|
+
contentTypes,
|
|
370
|
+
policies,
|
|
371
|
+
middlewares
|
|
372
|
+
};
|
|
373
|
+
export {
|
|
374
|
+
index as default
|
|
375
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.0.0",
|
|
3
|
+
"keywords": [],
|
|
4
|
+
"type": "commonjs",
|
|
5
|
+
"exports": {
|
|
6
|
+
"./package.json": "./package.json",
|
|
7
|
+
"./strapi-admin": {
|
|
8
|
+
"types": "./dist/admin/src/index.d.ts",
|
|
9
|
+
"source": "./admin/src/index.ts",
|
|
10
|
+
"import": "./dist/admin/index.mjs",
|
|
11
|
+
"require": "./dist/admin/index.js",
|
|
12
|
+
"default": "./dist/admin/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./strapi-server": {
|
|
15
|
+
"types": "./dist/server/src/index.d.ts",
|
|
16
|
+
"source": "./server/src/index.ts",
|
|
17
|
+
"import": "./dist/server/index.mjs",
|
|
18
|
+
"require": "./dist/server/index.js",
|
|
19
|
+
"default": "./dist/server/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "strapi-plugin build",
|
|
27
|
+
"watch": "strapi-plugin watch",
|
|
28
|
+
"watch:link": "strapi-plugin watch:link",
|
|
29
|
+
"verify": "strapi-plugin verify",
|
|
30
|
+
"test:ts:front": "run -T tsc -p admin/tsconfig.json",
|
|
31
|
+
"test:ts:back": "run -T tsc -p server/tsconfig.json"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@strapi/design-system": "^2.0.0-rc.30",
|
|
35
|
+
"@strapi/icons": "^2.0.0-rc.30",
|
|
36
|
+
"@types/js-yaml": "^4.0.9",
|
|
37
|
+
"js-yaml": "^4.1.1",
|
|
38
|
+
"react-intl": "^8.0.10"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@strapi/sdk-plugin": "^5.4.0",
|
|
42
|
+
"@strapi/strapi": "^5.33.1",
|
|
43
|
+
"@strapi/typescript-utils": "^5.33.1",
|
|
44
|
+
"@types/react": "^19.2.7",
|
|
45
|
+
"@types/react-dom": "^19.2.3",
|
|
46
|
+
"prettier": "^3.7.4",
|
|
47
|
+
"react": "^18.3.1",
|
|
48
|
+
"react-dom": "^18.3.1",
|
|
49
|
+
"react-router-dom": "^6.30.2",
|
|
50
|
+
"styled-components": "^6.1.19",
|
|
51
|
+
"typescript": "^5.9.3"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"@strapi/sdk-plugin": "^5.4.0",
|
|
55
|
+
"@strapi/strapi": "^5.33.1",
|
|
56
|
+
"react": "^18.3.1",
|
|
57
|
+
"react-dom": "^18.3.1",
|
|
58
|
+
"react-router-dom": "^6.30.2",
|
|
59
|
+
"styled-components": "^6.1.19"
|
|
60
|
+
},
|
|
61
|
+
"strapi": {
|
|
62
|
+
"kind": "plugin",
|
|
63
|
+
"name": "content-serialization",
|
|
64
|
+
"displayName": "Content CLI",
|
|
65
|
+
"description": ""
|
|
66
|
+
},
|
|
67
|
+
"name": "content-serialization",
|
|
68
|
+
"description": "",
|
|
69
|
+
"license": "MIT",
|
|
70
|
+
"author": "Jaydeep <jaydeepkotak10@gmail.com>"
|
|
71
|
+
}
|