apostrophe 3.29.0 → 3.30.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/CHANGELOG.md +20 -0
- package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.config.js +2 -0
- package/modules/@apostrophecms/attachment/index.js +23 -0
- package/modules/@apostrophecms/cache/index.js +1 -1
- package/modules/@apostrophecms/express/index.js +60 -1
- package/modules/@apostrophecms/schema/lib/addFieldTypes.js +2 -2
- package/package.json +2 -2
- package/test/attachments.js +69 -0
- package/test/data/upload_tests/tiny.mp4 +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.30.0 (2022-10-12)
|
|
4
|
+
|
|
5
|
+
### Adds
|
|
6
|
+
|
|
7
|
+
* New `APOS_LOG_ALL_ROUTES` environment variable. If set, Apostrophe logs information about all middleware functions and routes that are executed on behalf of a particular URL.
|
|
8
|
+
* Adds the `addFileGroups` option to the `attachment` module. Additionally it exposes a new method, `addFileGroup(group)`. These allow easier addition of new file groups or extension of the existing groups.
|
|
9
|
+
|
|
10
|
+
### Fixes
|
|
11
|
+
|
|
12
|
+
* Vue 3 may now be used in a separate webpack build at project level without causing problems for the admin UI Vue 2 build.
|
|
13
|
+
* Fixes `cache` module `clear-cache` CLI task message
|
|
14
|
+
* Fixes help message for `express` module `list-routes` CLI task
|
|
15
|
+
|
|
16
|
+
## 3.29.1 (2022-10-03)
|
|
17
|
+
|
|
18
|
+
### Fixes
|
|
19
|
+
|
|
20
|
+
* Hotfix to restore Node 14 support. Of course Node 16 is also supported.
|
|
21
|
+
|
|
22
|
+
|
|
3
23
|
## 3.29.0 (2022-10-03)
|
|
4
24
|
|
|
5
25
|
### Adds
|
|
@@ -24,6 +24,8 @@ module.exports = ({
|
|
|
24
24
|
|
|
25
25
|
const config = {
|
|
26
26
|
entry: importFile,
|
|
27
|
+
// Ensure that the correct version of vue-loader is found
|
|
28
|
+
context: __dirname,
|
|
27
29
|
mode: process.env.NODE_ENV || 'development',
|
|
28
30
|
optimization: {
|
|
29
31
|
minimize: process.env.NODE_ENV === 'production'
|
|
@@ -87,6 +87,12 @@ module.exports = {
|
|
|
87
87
|
}
|
|
88
88
|
];
|
|
89
89
|
|
|
90
|
+
if (self.options.addFileGroups) {
|
|
91
|
+
self.options.addFileGroups.forEach(newGroup => {
|
|
92
|
+
self.addFileGroup(newGroup);
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
90
96
|
// Do NOT add keys here unless they have the value `true`
|
|
91
97
|
self.croppable = {
|
|
92
98
|
gif: true,
|
|
@@ -1183,6 +1189,23 @@ module.exports = {
|
|
|
1183
1189
|
});
|
|
1184
1190
|
});
|
|
1185
1191
|
},
|
|
1192
|
+
|
|
1193
|
+
addFileGroup(newGroup) {
|
|
1194
|
+
if (self.fileGroups.some(existingGroup => existingGroup.name === newGroup.name)) {
|
|
1195
|
+
const existingGroup = self.fileGroups.find(existingGroup => existingGroup.name === newGroup.name);
|
|
1196
|
+
if (newGroup.extensions) {
|
|
1197
|
+
existingGroup.extensions = [ ...existingGroup.extensions, ...newGroup.extensions ];
|
|
1198
|
+
};
|
|
1199
|
+
if (newGroup.extensionMaps) {
|
|
1200
|
+
existingGroup.extensionMaps = {
|
|
1201
|
+
...existingGroup.extensionMaps,
|
|
1202
|
+
...newGroup.extensionMaps
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
} else {
|
|
1206
|
+
self.fileGroups.push(newGroup);
|
|
1207
|
+
}
|
|
1208
|
+
},
|
|
1186
1209
|
...require('./lib/legacy-migrations')(self)
|
|
1187
1210
|
};
|
|
1188
1211
|
},
|
|
@@ -97,7 +97,7 @@ module.exports = {
|
|
|
97
97
|
tasks(self) {
|
|
98
98
|
return {
|
|
99
99
|
'clear-cache': {
|
|
100
|
-
|
|
100
|
+
usage: 'usage: node app @apostrophecms/cache:clear-cache namespace1 namespace2...\n\nClears all values stored in a given namespace or namespaces. If you are using apos.cache in your own code you will\nknow the namespace name. Standard caches include "@apostrophecms/oembed". Normally it is not necessary to clear them.',
|
|
101
101
|
task: async (argv) => {
|
|
102
102
|
const namespaces = argv._.slice(1);
|
|
103
103
|
if (!namespaces.length) {
|
|
@@ -171,7 +171,7 @@ module.exports = {
|
|
|
171
171
|
tasks(self) {
|
|
172
172
|
return {
|
|
173
173
|
'list-routes': {
|
|
174
|
-
|
|
174
|
+
usage: 'Usage: node app @apostrophecms/express:list-routes \n\n List all Express routes registered via routes(), apiRoutes(), etc. (not directly via apos.app)',
|
|
175
175
|
async task(argv) {
|
|
176
176
|
for (const info of self.finalModuleMiddlewareAndRoutes) {
|
|
177
177
|
if (info.route) {
|
|
@@ -214,14 +214,23 @@ module.exports = {
|
|
|
214
214
|
await self.findModuleMiddlewareAndRoutes();
|
|
215
215
|
for (const item of self.finalModuleMiddlewareAndRoutes) {
|
|
216
216
|
if (item.method) {
|
|
217
|
+
if (process.env.APOS_LOG_ALL_ROUTES) {
|
|
218
|
+
item.route._aposItem = item;
|
|
219
|
+
}
|
|
217
220
|
self.apos.app[item.method](item.url, item.route);
|
|
218
221
|
} else if (item.middleware) {
|
|
222
|
+
if (process.env.APOS_LOG_ALL_ROUTES) {
|
|
223
|
+
item.middleware._aposItem = item;
|
|
224
|
+
}
|
|
219
225
|
if (item.url) {
|
|
220
226
|
self.apos.app.use(item.url, item.middleware);
|
|
221
227
|
} else {
|
|
222
228
|
self.apos.app.use(item.middleware);
|
|
223
229
|
}
|
|
224
230
|
} else if ((typeof item) === 'function') {
|
|
231
|
+
if (process.env.APOS_LOG_ALL_ROUTES) {
|
|
232
|
+
item._aposItem = item;
|
|
233
|
+
}
|
|
225
234
|
// Simple middleware
|
|
226
235
|
self.apos.app.use(item);
|
|
227
236
|
} else {
|
|
@@ -378,6 +387,42 @@ module.exports = {
|
|
|
378
387
|
strictNullHandling: true
|
|
379
388
|
});
|
|
380
389
|
});
|
|
390
|
+
if (process.env.APOS_LOG_ALL_ROUTES) {
|
|
391
|
+
self.logAllRoutes();
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
logAllRoutes() {
|
|
396
|
+
const superUse = self.apos.app.use.bind(self.apos.app);
|
|
397
|
+
const methods = [ 'get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'all' ];
|
|
398
|
+
self.apos.app.use = function (path, middleware) {
|
|
399
|
+
if (typeof path === 'function') {
|
|
400
|
+
middleware = path;
|
|
401
|
+
path = '';
|
|
402
|
+
}
|
|
403
|
+
superUse(path, (req, ...args) => {
|
|
404
|
+
const moduleName = middleware._aposItem && middleware._aposItem.moduleName;
|
|
405
|
+
const name = moduleName && middleware._aposItem.name;
|
|
406
|
+
self.apos.util.log(`${req.url} invokes middleware ${path ? `for path ${path} ` : ''}${moduleName && `found at ${moduleName}:${name}`}`);
|
|
407
|
+
return middleware(req, ...args);
|
|
408
|
+
});
|
|
409
|
+
};
|
|
410
|
+
for (const method of methods) {
|
|
411
|
+
const superMethod = self.apos.app[method].bind(self.apos.app);
|
|
412
|
+
self.apos.app[method] = (path, ...args) => {
|
|
413
|
+
if ((method === 'get') && (!args.length)) {
|
|
414
|
+
// Handle app.get in its configuration getter form
|
|
415
|
+
return superMethod(path);
|
|
416
|
+
}
|
|
417
|
+
const middleware = args.slice(0, args.length - 1);
|
|
418
|
+
const fn = args[args.length - 1];
|
|
419
|
+
superMethod(path, ...middleware, (req, ...args) => {
|
|
420
|
+
const moduleName = (fn === self.apos.page.serve) ? '@apostrophecms/page' : (fn._aposItem && fn._aposItem.moduleName);
|
|
421
|
+
self.apos.util.log(`${req.url} invokes ${method.toUpperCase()} route for path ${path} ${moduleName ? `in the module ${moduleName}` : ''}`);
|
|
422
|
+
return fn(req, ...args);
|
|
423
|
+
});
|
|
424
|
+
};
|
|
425
|
+
}
|
|
381
426
|
},
|
|
382
427
|
|
|
383
428
|
// Patch Express so that all calls to `res.redirect` honor
|
|
@@ -650,6 +695,11 @@ module.exports = {
|
|
|
650
695
|
const moduleNames = Array.from(new Set([ self.__meta.name, ...Object.keys(self.apos.modules) ]));
|
|
651
696
|
for (const name of moduleNames) {
|
|
652
697
|
const middleware = self.apos.modules[name].middleware || {};
|
|
698
|
+
if (process.env.APOS_LOG_ALL_ROUTES) {
|
|
699
|
+
for (const [ name, item ] of Object.entries(middleware)) {
|
|
700
|
+
item.name = name;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
653
703
|
labeledList.push({
|
|
654
704
|
name: `middleware:${name}`,
|
|
655
705
|
middleware: Object.values(middleware).filter(middleware => !middleware.before)
|
|
@@ -657,6 +707,11 @@ module.exports = {
|
|
|
657
707
|
}
|
|
658
708
|
for (const name of Object.keys(self.apos.modules)) {
|
|
659
709
|
const _routes = self.apos.modules[name]._routes;
|
|
710
|
+
if (process.env.APOS_LOG_ALL_ROUTES) {
|
|
711
|
+
for (const [ name, item ] of Object.entries(_routes)) {
|
|
712
|
+
item.name = name;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
660
715
|
labeledList.push({
|
|
661
716
|
name: `routes:${name}`,
|
|
662
717
|
routes: _routes.filter(route => !route.before)
|
|
@@ -682,8 +737,12 @@ module.exports = {
|
|
|
682
737
|
before.prepending = before.prepending || [];
|
|
683
738
|
before.prepending.push(item);
|
|
684
739
|
}
|
|
740
|
+
if (process.env.APOS_LOG_ALL_ROUTES) {
|
|
741
|
+
item.moduleName = name;
|
|
742
|
+
}
|
|
685
743
|
}
|
|
686
744
|
}
|
|
745
|
+
|
|
687
746
|
self.finalModuleMiddlewareAndRoutes = labeledList.map(item => (item.prepending || []).concat(item.middleware || item.routes)).flat();
|
|
688
747
|
}
|
|
689
748
|
};
|
|
@@ -51,9 +51,9 @@ module.exports = (self) => {
|
|
|
51
51
|
return _.isEqual(one[field.name], two[field.name]);
|
|
52
52
|
},
|
|
53
53
|
validate: function (field, options, warn, fail) {
|
|
54
|
-
let widgets = field.options
|
|
54
|
+
let widgets = (field.options && field.options.widgets) || {};
|
|
55
55
|
|
|
56
|
-
if (field.options
|
|
56
|
+
if (field.options && field.options.groups) {
|
|
57
57
|
for (const group of Object.keys(field.options.groups)) {
|
|
58
58
|
widgets = {
|
|
59
59
|
...widgets,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apostrophe",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.30.0",
|
|
4
4
|
"description": "The Apostrophe Content Management System.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"vue": "^2.6.14",
|
|
110
110
|
"vue-advanced-cropper": "^1.10.1",
|
|
111
111
|
"vue-click-outside-element": "^1.0.15",
|
|
112
|
-
"vue-loader": "
|
|
112
|
+
"vue-loader": "^15.10.0",
|
|
113
113
|
"vue-material-design-icons": "~4.12.1",
|
|
114
114
|
"vue-style-loader": "^4.1.2",
|
|
115
115
|
"vue-template-compiler": "^2.6.14",
|
package/test/attachments.js
CHANGED
|
@@ -80,6 +80,75 @@ describe('Attachment', function() {
|
|
|
80
80
|
assert(good);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
+
it('should allow upload if extension has been added to the current fileGroups', async function() {
|
|
84
|
+
let apos;
|
|
85
|
+
let good = true;
|
|
86
|
+
try {
|
|
87
|
+
apos = await t.create({
|
|
88
|
+
root: module,
|
|
89
|
+
modules: {
|
|
90
|
+
'@apostrophecms/attachment': {
|
|
91
|
+
options: {
|
|
92
|
+
addFileGroups: [
|
|
93
|
+
{
|
|
94
|
+
name: 'images',
|
|
95
|
+
extensions: [
|
|
96
|
+
'mp4'
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
const filename = 'tiny.mp4';
|
|
105
|
+
await apos.attachment.insert(apos.task.getReq(), {
|
|
106
|
+
name: filename,
|
|
107
|
+
path: uploadSource + filename
|
|
108
|
+
});
|
|
109
|
+
} catch (e) {
|
|
110
|
+
good = false;
|
|
111
|
+
} finally {
|
|
112
|
+
assert(good);
|
|
113
|
+
t.destroy(apos);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should allow upload if extension has been added in a new fileGroup', async function() {
|
|
118
|
+
let apos;
|
|
119
|
+
let good = true;
|
|
120
|
+
try {
|
|
121
|
+
apos = await t.create({
|
|
122
|
+
root: module,
|
|
123
|
+
modules: {
|
|
124
|
+
'@apostrophecms/attachment': {
|
|
125
|
+
options: {
|
|
126
|
+
addFileGroups: [
|
|
127
|
+
{
|
|
128
|
+
name: 'testGroup',
|
|
129
|
+
extensions: [
|
|
130
|
+
'mp4'
|
|
131
|
+
],
|
|
132
|
+
extensionMaps: {}
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
const filename = 'tiny.mp4';
|
|
140
|
+
await apos.attachment.insert(apos.task.getReq(), {
|
|
141
|
+
name: filename,
|
|
142
|
+
path: uploadSource + filename
|
|
143
|
+
});
|
|
144
|
+
} catch (e) {
|
|
145
|
+
good = false;
|
|
146
|
+
} finally {
|
|
147
|
+
assert(good);
|
|
148
|
+
t.destroy(apos);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
83
152
|
it('should crop an image file when requested', async function() {
|
|
84
153
|
let result = await insert('crop_image.png');
|
|
85
154
|
const crop = {
|
|
Binary file
|