strapi-plugin-publish-media-validation 1.1.1 → 1.1.3
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/server/index.d.ts.map +1 -1
- package/dist/server/index.js +39 -18
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
- package/src/server/__tests__/index.test.ts +14 -8
- package/src/server/index.ts +46 -17
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAO,MAAM,gBAAgB,CAAC;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAO,MAAM,gBAAgB,CAAC;;2BA+GlB;QAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAA;KAAE;uBAgE3B;QAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAA;KAAE;qBACzB;QAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAA;KAAE;;AAE/C,wBAAgD"}
|
package/dist/server/index.js
CHANGED
|
@@ -24,7 +24,6 @@ function resolveValidationError() {
|
|
|
24
24
|
* component references.
|
|
25
25
|
*/
|
|
26
26
|
function buildPopulate(modelUID, strapi, visited = new Set()) {
|
|
27
|
-
var _a;
|
|
28
27
|
if (visited.has(modelUID))
|
|
29
28
|
return {};
|
|
30
29
|
const seen = new Set(visited);
|
|
@@ -45,14 +44,12 @@ function buildPopulate(modelUID, strapi, visited = new Set()) {
|
|
|
45
44
|
populate[key] = { populate: Object.keys(nested).length ? nested : '*' };
|
|
46
45
|
}
|
|
47
46
|
else if (a.type === 'dynamiczone') {
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
populate[key] = { populate: Object.keys(merged).length ? merged : '*' };
|
|
47
|
+
// Strapi v5 does NOT accept a flat merged-field-name object for
|
|
48
|
+
// dynamic zones — only '*' or the per-component 'on' syntax are
|
|
49
|
+
// valid. A flat object causes findOne to throw, producing the
|
|
50
|
+
// generic "Internal Server Error" before our ValidationError is
|
|
51
|
+
// ever reached. Use '*' to load all direct fields of every block.
|
|
52
|
+
populate[key] = { populate: '*' };
|
|
56
53
|
}
|
|
57
54
|
}
|
|
58
55
|
return populate;
|
|
@@ -106,19 +103,43 @@ const register = ({ strapi }) => {
|
|
|
106
103
|
const documentId = (_a = ctx.params) === null || _a === void 0 ? void 0 : _a.documentId;
|
|
107
104
|
if (!documentId)
|
|
108
105
|
return next();
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
let missing = [];
|
|
107
|
+
try {
|
|
108
|
+
const populate = buildPopulate(ctx.uid, strapi);
|
|
109
|
+
if (Object.keys(populate).length > 0) {
|
|
110
|
+
const doc = await strapi.documents(ctx.uid).findOne({
|
|
111
|
+
documentId,
|
|
112
|
+
populate,
|
|
113
|
+
});
|
|
114
|
+
missing = collectMissingMedia(doc, ctx.uid, strapi);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
// Re-throw only our own validation errors; swallow unexpected plugin
|
|
119
|
+
// errors so a bug here never blocks a legitimate publish.
|
|
120
|
+
if (err instanceof ValidationError)
|
|
121
|
+
throw err;
|
|
122
|
+
strapi.log.warn(`[publish-media-validation] Skipping validation due to unexpected error: ${err}`);
|
|
111
123
|
return next();
|
|
112
|
-
|
|
113
|
-
documentId,
|
|
114
|
-
populate,
|
|
115
|
-
});
|
|
116
|
-
const missing = collectMissingMedia(doc, ctx.uid, strapi);
|
|
124
|
+
}
|
|
117
125
|
if (missing.length > 0) {
|
|
118
126
|
const labels = missing
|
|
119
|
-
.map((
|
|
127
|
+
.map((rawPath) => rawPath
|
|
128
|
+
.split(' > ')
|
|
129
|
+
.map((seg) => {
|
|
130
|
+
const indexed = seg.match(/^(.+?)\[(\d+)\]$/);
|
|
131
|
+
if (indexed) {
|
|
132
|
+
const name = indexed[1].charAt(0).toUpperCase() +
|
|
133
|
+
indexed[1].slice(1).replace(/([A-Z])/g, ' $1').replace(/_/g, ' ');
|
|
134
|
+
return `${name} (item ${indexed[2]})`;
|
|
135
|
+
}
|
|
136
|
+
return seg.charAt(0).toUpperCase() +
|
|
137
|
+
seg.slice(1).replace(/([A-Z])/g, ' $1').replace(/_/g, ' ');
|
|
138
|
+
})
|
|
139
|
+
.join(' › '))
|
|
120
140
|
.join(', ');
|
|
121
|
-
|
|
141
|
+
const verb = missing.length === 1 ? 'field is' : 'fields are';
|
|
142
|
+
throw new ValidationError(`Cannot publish: required media ${verb} missing — ${labels}`);
|
|
122
143
|
}
|
|
123
144
|
return next();
|
|
124
145
|
});
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";;AAIA,SAAS,sBAAsB;IAC7B,IAAI,CAAC;QACH,wEAAwE;QACxE,iEAAiE;QACjE,sEAAsE;QACtE,OAAQ,OAAO,CAAC,eAAe,CAAS,CAAC,MAAM,CAAC,eAAe,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAM,SAAQ,KAAK;YACxB,YAAY,OAAe;gBACzB,KAAK,CAAC,OAAO,CAAC,CAAC;gBACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;gBAC7B,IAAY,CAAC,MAAM,GAAG,GAAG,CAAC;YAC7B,CAAC;SACF,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,aAAa,CACpB,QAAgB,EAChB,MAAmB,EACnB,UAAU,IAAI,GAAG,EAAU
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strapi-plugin-publish-media-validation",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "Strapi v5 plugin that enforces required media fields at publish time — works around the known limitation where required: true on media fields is not validated on publish.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -37,6 +37,7 @@ function buildStrapi(
|
|
|
37
37
|
getModel: jest.fn().mockReturnValue(
|
|
38
38
|
modelAttributes !== null ? { attributes: modelAttributes } : null
|
|
39
39
|
),
|
|
40
|
+
log: { warn: jest.fn() },
|
|
40
41
|
};
|
|
41
42
|
|
|
42
43
|
plugin.register({ strapi: strapi as any });
|
|
@@ -76,6 +77,7 @@ function buildStrapiFromModels(
|
|
|
76
77
|
const attrs = modelsByUid[uid];
|
|
77
78
|
return attrs !== undefined ? { attributes: attrs } : null;
|
|
78
79
|
}),
|
|
80
|
+
log: { warn: jest.fn() },
|
|
79
81
|
};
|
|
80
82
|
|
|
81
83
|
plugin.register({ strapi: strapi as any });
|
|
@@ -165,7 +167,7 @@ describe('middleware — flat media fields', () => {
|
|
|
165
167
|
);
|
|
166
168
|
const { result } = runMiddleware(baseCtx);
|
|
167
169
|
await expect(result).rejects.toThrow(errors.ValidationError);
|
|
168
|
-
await expect(result).rejects.toThrow('Cover');
|
|
170
|
+
await expect(result).rejects.toThrow('Cannot publish: required media field is missing — Cover');
|
|
169
171
|
});
|
|
170
172
|
|
|
171
173
|
it('lists all missing fields in the error message', async () => {
|
|
@@ -177,6 +179,7 @@ describe('middleware — flat media fields', () => {
|
|
|
177
179
|
{ cover: null, heroImage: null }
|
|
178
180
|
);
|
|
179
181
|
const { result } = runMiddleware(baseCtx);
|
|
182
|
+
await expect(result).rejects.toThrow('Cannot publish: required media fields are missing');
|
|
180
183
|
await expect(result).rejects.toThrow('Cover');
|
|
181
184
|
await expect(result).rejects.toThrow('Hero Image');
|
|
182
185
|
});
|
|
@@ -247,7 +250,7 @@ describe('middleware — component fields', () => {
|
|
|
247
250
|
);
|
|
248
251
|
const { result } = runMiddleware();
|
|
249
252
|
await expect(result).rejects.toThrow(errors.ValidationError);
|
|
250
|
-
await expect(result).rejects.toThrow('Hero
|
|
253
|
+
await expect(result).rejects.toThrow('Hero › Photo');
|
|
251
254
|
});
|
|
252
255
|
|
|
253
256
|
it('passes through when all repeatable component items have required media', async () => {
|
|
@@ -272,7 +275,7 @@ describe('middleware — component fields', () => {
|
|
|
272
275
|
{ cards: [{ photo: { id: 1 } }, { photo: null }] }
|
|
273
276
|
);
|
|
274
277
|
const { result } = runMiddleware();
|
|
275
|
-
await expect(result).rejects.toThrow('Cards
|
|
278
|
+
await expect(result).rejects.toThrow('Cards (item 2) › Photo');
|
|
276
279
|
});
|
|
277
280
|
|
|
278
281
|
it('builds populate that includes the component media field', async () => {
|
|
@@ -332,7 +335,7 @@ describe('middleware — dynamiczone fields', () => {
|
|
|
332
335
|
);
|
|
333
336
|
const { result } = runMiddleware();
|
|
334
337
|
await expect(result).rejects.toThrow(errors.ValidationError);
|
|
335
|
-
await expect(result).rejects.toThrow('Blocks
|
|
338
|
+
await expect(result).rejects.toThrow('Blocks (item 1) › Photo');
|
|
336
339
|
});
|
|
337
340
|
|
|
338
341
|
it('reports the correct block index in a multi-block dynamic zone', async () => {
|
|
@@ -349,7 +352,7 @@ describe('middleware — dynamiczone fields', () => {
|
|
|
349
352
|
}
|
|
350
353
|
);
|
|
351
354
|
const { result } = runMiddleware();
|
|
352
|
-
await expect(result).rejects.toThrow('Blocks
|
|
355
|
+
await expect(result).rejects.toThrow('Blocks (item 2) › Photo');
|
|
353
356
|
});
|
|
354
357
|
|
|
355
358
|
it('skips blocks whose component type has no required media', async () => {
|
|
@@ -386,10 +389,10 @@ describe('middleware — dynamiczone fields', () => {
|
|
|
386
389
|
}
|
|
387
390
|
);
|
|
388
391
|
const { result } = runMiddleware();
|
|
389
|
-
await expect(result).rejects.toThrow('Blocks
|
|
392
|
+
await expect(result).rejects.toThrow('Blocks (item 2) › Photo');
|
|
390
393
|
});
|
|
391
394
|
|
|
392
|
-
it('builds populate
|
|
395
|
+
it('builds populate using * for dynamic zone fields', async () => {
|
|
393
396
|
const findOne = jest.fn().mockResolvedValue({
|
|
394
397
|
blocks: [{ __component: COPY_PHOTO_UID, photo: { id: 1 } }],
|
|
395
398
|
});
|
|
@@ -406,14 +409,17 @@ describe('middleware — dynamiczone fields', () => {
|
|
|
406
409
|
if (uid === TEXT_UID) return { attributes: { content: { type: 'richtext' } } };
|
|
407
410
|
return null;
|
|
408
411
|
}),
|
|
412
|
+
log: { warn: jest.fn() },
|
|
409
413
|
};
|
|
410
414
|
|
|
411
415
|
plugin.register({ strapi: strapi as any });
|
|
412
416
|
await capturedMiddleware!(baseCtx, jest.fn().mockResolvedValue(undefined));
|
|
413
417
|
|
|
418
|
+
// Dynamic zones must use populate:'*' — Strapi v5 rejects flat merged
|
|
419
|
+
// field-name objects for dynamic zones, which would cause findOne to throw.
|
|
414
420
|
expect(findOne).toHaveBeenCalledWith({
|
|
415
421
|
documentId: 'abc123',
|
|
416
|
-
populate: { blocks: { populate:
|
|
422
|
+
populate: { blocks: { populate: '*' } },
|
|
417
423
|
});
|
|
418
424
|
});
|
|
419
425
|
});
|
package/src/server/index.ts
CHANGED
|
@@ -50,14 +50,12 @@ function buildPopulate(
|
|
|
50
50
|
// to populate:'*' when there are no explicitly known nested keys.
|
|
51
51
|
populate[key] = { populate: Object.keys(nested).length ? nested : '*' };
|
|
52
52
|
} else if (a.type === 'dynamiczone') {
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
populate[key] = { populate: Object.keys(merged).length ? merged : '*' };
|
|
53
|
+
// Strapi v5 does NOT accept a flat merged-field-name object for
|
|
54
|
+
// dynamic zones — only '*' or the per-component 'on' syntax are
|
|
55
|
+
// valid. A flat object causes findOne to throw, producing the
|
|
56
|
+
// generic "Internal Server Error" before our ValidationError is
|
|
57
|
+
// ever reached. Use '*' to load all direct fields of every block.
|
|
58
|
+
populate[key] = { populate: '*' };
|
|
61
59
|
}
|
|
62
60
|
}
|
|
63
61
|
|
|
@@ -123,21 +121,52 @@ const register = ({ strapi }: { strapi: Core.Strapi }) => {
|
|
|
123
121
|
const documentId = (ctx.params as { documentId?: string })?.documentId;
|
|
124
122
|
if (!documentId) return next();
|
|
125
123
|
|
|
126
|
-
|
|
127
|
-
if (Object.keys(populate).length === 0) return next();
|
|
124
|
+
let missing: string[] = [];
|
|
128
125
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
populate,
|
|
132
|
-
});
|
|
126
|
+
try {
|
|
127
|
+
const populate = buildPopulate(ctx.uid as string, strapi);
|
|
133
128
|
|
|
134
|
-
|
|
129
|
+
if (Object.keys(populate).length > 0) {
|
|
130
|
+
const doc = await strapi.documents(ctx.uid as UID.ContentType).findOne({
|
|
131
|
+
documentId,
|
|
132
|
+
populate,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
missing = collectMissingMedia(doc, ctx.uid as string, strapi);
|
|
136
|
+
}
|
|
137
|
+
} catch (err) {
|
|
138
|
+
// Re-throw only our own validation errors; swallow unexpected plugin
|
|
139
|
+
// errors so a bug here never blocks a legitimate publish.
|
|
140
|
+
if (err instanceof ValidationError) throw err;
|
|
141
|
+
strapi.log.warn(
|
|
142
|
+
`[publish-media-validation] Skipping validation due to unexpected error: ${err}`
|
|
143
|
+
);
|
|
144
|
+
return next();
|
|
145
|
+
}
|
|
135
146
|
|
|
136
147
|
if (missing.length > 0) {
|
|
137
148
|
const labels = missing
|
|
138
|
-
.map((
|
|
149
|
+
.map((rawPath) =>
|
|
150
|
+
rawPath
|
|
151
|
+
.split(' > ')
|
|
152
|
+
.map((seg) => {
|
|
153
|
+
const indexed = seg.match(/^(.+?)\[(\d+)\]$/);
|
|
154
|
+
if (indexed) {
|
|
155
|
+
const name = indexed[1].charAt(0).toUpperCase() +
|
|
156
|
+
indexed[1].slice(1).replace(/([A-Z])/g, ' $1').replace(/_/g, ' ');
|
|
157
|
+
return `${name} (item ${indexed[2]})`;
|
|
158
|
+
}
|
|
159
|
+
return seg.charAt(0).toUpperCase() +
|
|
160
|
+
seg.slice(1).replace(/([A-Z])/g, ' $1').replace(/_/g, ' ');
|
|
161
|
+
})
|
|
162
|
+
.join(' › ')
|
|
163
|
+
)
|
|
139
164
|
.join(', ');
|
|
140
|
-
|
|
165
|
+
|
|
166
|
+
const verb = missing.length === 1 ? 'field is' : 'fields are';
|
|
167
|
+
throw new ValidationError(
|
|
168
|
+
`Cannot publish: required media ${verb} missing — ${labels}`
|
|
169
|
+
);
|
|
141
170
|
}
|
|
142
171
|
|
|
143
172
|
return next();
|