apostrophe 2.220.4 → 2.220.9
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 +37 -0
- package/lib/modules/apostrophe-attachments/lib/routes.js +22 -2
- package/lib/modules/apostrophe-attachments/public/js/user.js +6 -1
- package/lib/modules/apostrophe-doc-type-manager/lib/routes.js +8 -1
- package/lib/modules/apostrophe-global/index.js +6 -0
- package/lib/modules/apostrophe-login/index.js +1 -1
- package/lib/modules/apostrophe-modal/public/css/components/modal-tabs.less +0 -1
- package/lib/modules/apostrophe-modal/public/js/modal.js +6 -4
- package/lib/modules/apostrophe-modal/views/batch.html +1 -1
- package/lib/modules/apostrophe-schemas/index.js +50 -44
- package/lib/modules/apostrophe-templates/index.js +3 -1
- package/package.json +7 -7
- package/test/login-throttle.js +5 -1
- package/test/package.json +75 -0
- package/test-lib/build-test-package-json.js +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.220.9 (2022-02-04)
|
|
4
|
+
|
|
5
|
+
## Fixes
|
|
6
|
+
|
|
7
|
+
* Worked around bug preventing proper translation of the "Selected" message in certain modal operations. Thanks to [stepanjakl](https://github.com/stepanjakl).
|
|
8
|
+
|
|
9
|
+
## 2.220.8 (2022-01-20)
|
|
10
|
+
|
|
11
|
+
## Fixes
|
|
12
|
+
|
|
13
|
+
* Fixes overflowing issue in the editor modals when adding multiple options to an area. This used to cause a scroll to appear and the user had to scroll to see all the options.
|
|
14
|
+
* In the editor modal if there was an area with multiple items added (enough to make the page scrollable) when the user wanted to add another item another scroll bar would appear besides the already existing one and the user had to scroll to see all the options.
|
|
15
|
+
* Fixes i18n processing of a login throttling error message.
|
|
16
|
+
|
|
17
|
+
## 2.220.7 (2021-10-13)
|
|
18
|
+
|
|
19
|
+
## Fixes
|
|
20
|
+
|
|
21
|
+
* Avoid 500 errors when `joinByOne` field is hidden and required.
|
|
22
|
+
* Avoid errors at startup when formerly valid locales for the global doc are in the database, but not part of the current configuration.
|
|
23
|
+
|
|
24
|
+
## 2.220.6 (2021-09-13)
|
|
25
|
+
|
|
26
|
+
## Security
|
|
27
|
+
|
|
28
|
+
* SVG files can contain XSS attack vectors. Fortunately, these cannot be exploited when the SVG file is used in an `img` tag or as a CSS background, which is normally the case for SVGs uploaded to Apostrophe. However, Apostrophe does provide a "View File" button in the media manager which could load the file in a way that could trigger XSS attacks in a carefully crafted SVG intentionally designed to phish Apostrophe admins. To mitigate this risk, starting with version 2.220.6 the "View File" button downloads the SVG file to the local computer as an attachment. This removes it from the domain of the website, so that any embedded JavaScript cannot be used to trigger actions in Apostrophe. However please note that it is your responsibility to avoid the use of inline `svg` with untrusted SVG files, or the use of `iframe`, `embed` or `object` tags with untrusted SVG files. If you have not enabled the `svgImages` option to `apostrophe-attachments`, then your site does not accept SVG uploads and this risk is not relevant to you.
|
|
29
|
+
|
|
30
|
+
## Fixes
|
|
31
|
+
|
|
32
|
+
* At least in a nightwatch regression testing context, `transitionend` events have been observed not to fire, resulting in unreliable test suites. A change has been made to accommodate this scenario with a fallback timer relating to indicating the current topmost modal.
|
|
33
|
+
|
|
34
|
+
## 2.220.5 (2021-08-30)
|
|
35
|
+
|
|
36
|
+
## Fixes
|
|
37
|
+
|
|
38
|
+
* The template error page was returning a 200 status code. It now correctly returns a 500 status code.
|
|
39
|
+
|
|
3
40
|
## 2.220.4 (2021-08-03)
|
|
4
41
|
|
|
5
42
|
## Fixes
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
const _ = require('@sailshq/lodash');
|
|
2
|
+
const request = require('request');
|
|
3
|
+
const path = require('path');
|
|
2
4
|
|
|
3
5
|
module.exports = function(self, options) {
|
|
4
6
|
|
|
@@ -25,7 +27,8 @@ module.exports = function(self, options) {
|
|
|
25
27
|
}
|
|
26
28
|
return next(null, { file: file });
|
|
27
29
|
});
|
|
28
|
-
}
|
|
30
|
+
}
|
|
31
|
+
);
|
|
29
32
|
|
|
30
33
|
// Crop a previously uploaded image, based on the `id` POST parameter
|
|
31
34
|
// and the `crop` POST parameter. `id` should refer to an existing
|
|
@@ -65,4 +68,21 @@ module.exports = function(self, options) {
|
|
|
65
68
|
});
|
|
66
69
|
});
|
|
67
70
|
|
|
71
|
+
// Provides a simple route to download a file as an attachment, rather than
|
|
72
|
+
// viewing it. Pipes it from the regular public URL to avoid acting as a
|
|
73
|
+
// bypass of permissions
|
|
74
|
+
self.apiRoute('get', 'download', async (req, res) => {
|
|
75
|
+
const _id = self.apos.launder.id(req.query._id);
|
|
76
|
+
const info = await self.db.findOne({
|
|
77
|
+
_id: _id
|
|
78
|
+
});
|
|
79
|
+
if (!info) {
|
|
80
|
+
return res.status(404).send('notfound');
|
|
81
|
+
}
|
|
82
|
+
const url = self.url(info, { size: 'original' });
|
|
83
|
+
res.setHeader('Content-Disposition', `attachment; filename="${path.basename(url)}"`);
|
|
84
|
+
res.setHeader('Content-Type', 'application/octet-stream');
|
|
85
|
+
const resolvedUrl = (new URL(url, req.absoluteUrl)).toString();
|
|
86
|
+
await request(resolvedUrl).pipe(res);
|
|
87
|
+
});
|
|
68
88
|
};
|
|
@@ -286,8 +286,13 @@ apos.define('apostrophe-attachments', {
|
|
|
286
286
|
$existing.data('existing', info);
|
|
287
287
|
$existing.data('field', field);
|
|
288
288
|
$existing.attr('data-existing', info._id);
|
|
289
|
+
var $link = $existing.find('[data-link]');
|
|
289
290
|
$existing.find('[data-name]').text(info.name);
|
|
290
|
-
|
|
291
|
+
if (info.extension === 'svg') {
|
|
292
|
+
$link.attr('href', self.action + '/download?_id=' + info._id);
|
|
293
|
+
} else {
|
|
294
|
+
$link.attr('href', self.url(info, {size: 'original'}));
|
|
295
|
+
}
|
|
291
296
|
$existing.alterClass('apos-extension-*', 'apos-extension-' + info.extension);
|
|
292
297
|
var $preview = $existing.find('[data-preview]');
|
|
293
298
|
if (info.group === 'images') {
|
|
@@ -63,7 +63,14 @@ module.exports = function(self, options) {
|
|
|
63
63
|
convert: function(callback) {
|
|
64
64
|
// For purposes of previewing, it's OK to ignore readOnly so we can tell which
|
|
65
65
|
// inputs are plausible
|
|
66
|
-
return self.apos.schemas.convert(
|
|
66
|
+
return self.apos.schemas.convert(
|
|
67
|
+
req,
|
|
68
|
+
[_.omit(field, ['readOnly', 'required', 'min', 'max'])],
|
|
69
|
+
'form',
|
|
70
|
+
input,
|
|
71
|
+
receptacle,
|
|
72
|
+
callback
|
|
73
|
+
);
|
|
67
74
|
},
|
|
68
75
|
join: function(callback) {
|
|
69
76
|
return self.apos.schemas.join(req, [ field ], receptacle, true, callback);
|
|
@@ -144,6 +144,8 @@ module.exports = {
|
|
|
144
144
|
self.initGlobal = function(callback) {
|
|
145
145
|
var req = self.apos.tasks.getReq();
|
|
146
146
|
var existing;
|
|
147
|
+
const workflow = self.apos.modules['apostrophe-workflow'];
|
|
148
|
+
const locales = workflow && workflow.locales && Object.keys(workflow.locales);
|
|
147
149
|
return async.series({
|
|
148
150
|
// Early in pre-2.0 code there was no type property for the global page.
|
|
149
151
|
// We can't use a standard migration to fix that because initGlobal
|
|
@@ -178,6 +180,10 @@ module.exports = {
|
|
|
178
180
|
return callback(err);
|
|
179
181
|
}
|
|
180
182
|
existing = result;
|
|
183
|
+
if (workflow) {
|
|
184
|
+
// Do not react to documents whose locale no longer exists in the system
|
|
185
|
+
existing = existing.filter(doc => locales.includes(doc.workflowLocale));
|
|
186
|
+
}
|
|
181
187
|
return callback();
|
|
182
188
|
});
|
|
183
189
|
},
|
|
@@ -461,16 +461,18 @@ apos.define('apostrophe-modal', {
|
|
|
461
461
|
setImmediate(function() {
|
|
462
462
|
$wrapper.find('[data-modal-content]').removeClass('apos-modal-slide-current');
|
|
463
463
|
var fired = false;
|
|
464
|
-
$content.one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend',
|
|
464
|
+
$content.one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', handler);
|
|
465
|
+
// Fallback because the transition has been observed to never happen, at least in nightwatch testing
|
|
466
|
+
setTimeout(handler, 1000);
|
|
467
|
+
$content.addClass('apos-modal-slide-current');
|
|
468
|
+
function handler() {
|
|
465
469
|
if (fired) {
|
|
466
|
-
// Has been seen firing twice in nightwatch tests, in spite of `one`
|
|
467
470
|
return;
|
|
468
471
|
} else {
|
|
469
472
|
fired = true;
|
|
470
473
|
}
|
|
471
474
|
self.indicateCurrentModal(true);
|
|
472
|
-
}
|
|
473
|
-
$content.addClass('apos-modal-slide-current');
|
|
475
|
+
}
|
|
474
476
|
});
|
|
475
477
|
|
|
476
478
|
};
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<select name="batch-operation" class="apos-field-input apos-field-input--small apos-field-input-select apos-manage-batch-operations-select">
|
|
9
9
|
{% for operation in operations %}
|
|
10
10
|
<option value="{{ operation.name }}">
|
|
11
|
-
{{ __ns('apostrophe', '%s Selected', operation.label) }} (0)
|
|
11
|
+
{{ __ns('apostrophe', ' %s Selected', operation.label) }} (0)
|
|
12
12
|
</option>
|
|
13
13
|
{% endfor %}
|
|
14
14
|
</select>
|
|
@@ -2675,58 +2675,64 @@ module.exports = {
|
|
|
2675
2675
|
|
|
2676
2676
|
self.validate = function(schema, options) {
|
|
2677
2677
|
// Infinite recursion prevention
|
|
2678
|
-
|
|
2679
|
-
return;
|
|
2680
|
-
}
|
|
2681
|
-
self.validatedSchemas[options.type + ':' + options.subtype] = true;
|
|
2682
|
-
var unarranged = [];
|
|
2678
|
+
const unarranged = [];
|
|
2683
2679
|
_.each(schema, function(field) {
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
fail('name property is missing.');
|
|
2690
|
-
}
|
|
2691
|
-
if ((!field.label) && (!field.contextual)) {
|
|
2692
|
-
field.label = _.startCase(field.name.replace(/^_/, ''));
|
|
2693
|
-
}
|
|
2694
|
-
if (fieldType.validate) {
|
|
2695
|
-
fieldType.validate(field, options, warn, fail, schema);
|
|
2696
|
-
}
|
|
2697
|
-
// If at least one field is in a non-default group and this one is in the
|
|
2698
|
-
// default group, complain about halfassed grouping. The "Info" tab indicates
|
|
2699
|
-
// insufficient UX consideration, unless it contains all the fields, which
|
|
2700
|
-
// usually indicates a simple array schema that does not need any groups.
|
|
2701
|
-
// Don't ding the developer for things that aren't their fault and are
|
|
2702
|
-
// probably not that obnoxious in practice, like the withTags field of all
|
|
2703
|
-
// pieces-pages, which is usually alone in the default group.
|
|
2704
|
-
if (
|
|
2705
|
-
field.group &&
|
|
2706
|
-
(!field.contextual) &&
|
|
2707
|
-
(field.name !== 'withTags') &&
|
|
2708
|
-
(!field.type.match(/Reverse$/)) &&
|
|
2709
|
-
(field.group.name === 'default') &&
|
|
2710
|
-
(_.find(schema, function(field) {
|
|
2711
|
-
return (field.group) && (field.group.name !== 'default');
|
|
2712
|
-
}))) {
|
|
2713
|
-
unarranged.push(field);
|
|
2714
|
-
}
|
|
2715
|
-
function fail(s) {
|
|
2716
|
-
throw new Error(format(s));
|
|
2717
|
-
}
|
|
2718
|
-
function warn(s) {
|
|
2719
|
-
self.apos.utils.warnDev(format(s));
|
|
2720
|
-
}
|
|
2721
|
-
function format(s) {
|
|
2722
|
-
return '\n⚠️ ' + options.type + ' ' + options.subtype + ', field name ' + field.name + ':\n\n' + s + '\n';
|
|
2680
|
+
const key = `${options.type}:${options.subtype}.${field.name}`;
|
|
2681
|
+
|
|
2682
|
+
if (!self.validatedSchemas[key]) {
|
|
2683
|
+
self.validatedSchemas[key] = true;
|
|
2684
|
+
self.validateField(field, options, schema, unarranged);
|
|
2723
2685
|
}
|
|
2724
2686
|
});
|
|
2687
|
+
|
|
2725
2688
|
if (unarranged.length) {
|
|
2726
2689
|
self.apos.utils.warnDevOnce('unarranged-fields', '\n⚠️ ' + options.type + ' ' + options.subtype + ' contains unarranged field(s): ' + _.pluck(unarranged, 'name').join(', ') + '.\nArrange all of your fields with arrangeFields, using meaningful group labels.\nhttps://apos.dev/arrange-fields');
|
|
2727
2690
|
}
|
|
2728
2691
|
};
|
|
2729
2692
|
|
|
2693
|
+
self.validateField = function(field, options, schema, unarranged) {
|
|
2694
|
+
var fieldType = self.fieldTypes[field.type];
|
|
2695
|
+
if (!fieldType) {
|
|
2696
|
+
fail('Unknown schema field type.');
|
|
2697
|
+
}
|
|
2698
|
+
if (!field.name) {
|
|
2699
|
+
fail('name property is missing.');
|
|
2700
|
+
}
|
|
2701
|
+
if ((!field.label) && (!field.contextual)) {
|
|
2702
|
+
field.label = _.startCase(field.name.replace(/^_/, ''));
|
|
2703
|
+
}
|
|
2704
|
+
if (fieldType.validate) {
|
|
2705
|
+
fieldType.validate(field, options, warn, fail, schema);
|
|
2706
|
+
}
|
|
2707
|
+
// If at least one field is in a non-default group and this one is in the
|
|
2708
|
+
// default group, complain about halfassed grouping. The "Info" tab indicates
|
|
2709
|
+
// insufficient UX consideration, unless it contains all the fields, which
|
|
2710
|
+
// usually indicates a simple array schema that does not need any groups.
|
|
2711
|
+
// Don't ding the developer for things that aren't their fault and are
|
|
2712
|
+
// probably not that obnoxious in practice, like the withTags field of all
|
|
2713
|
+
// pieces-pages, which is usually alone in the default group.
|
|
2714
|
+
if (
|
|
2715
|
+
field.group &&
|
|
2716
|
+
(!field.contextual) &&
|
|
2717
|
+
(field.name !== 'withTags') &&
|
|
2718
|
+
(!field.type.match(/Reverse$/)) &&
|
|
2719
|
+
(field.group.name === 'default') &&
|
|
2720
|
+
(_.find(schema, function(field) {
|
|
2721
|
+
return (field.group) && (field.group.name !== 'default');
|
|
2722
|
+
}))) {
|
|
2723
|
+
unarranged.push(field);
|
|
2724
|
+
}
|
|
2725
|
+
function fail(s) {
|
|
2726
|
+
throw new Error(format(s));
|
|
2727
|
+
}
|
|
2728
|
+
function warn(s) {
|
|
2729
|
+
self.apos.utils.warnDev(format(s));
|
|
2730
|
+
}
|
|
2731
|
+
function format(s) {
|
|
2732
|
+
return '\n⚠️ ' + options.type + ' ' + options.subtype + ', field name ' + field.name + ':\n\n' + s + '\n';
|
|
2733
|
+
}
|
|
2734
|
+
};
|
|
2735
|
+
|
|
2730
2736
|
// Return all standard field names currently associated with permissions editing,
|
|
2731
2737
|
// for consistency in arrangeFields, batch permissions schemas, etc.
|
|
2732
2738
|
self.getPermissionsFieldNames = function() {
|
|
@@ -721,7 +721,9 @@ module.exports = {
|
|
|
721
721
|
self.apos.utils.error(':: ' + now + ': ' + type + ' error at ' + req.url);
|
|
722
722
|
self.apos.utils.error('Current user: ' + (req.user ? req.user.username : 'none'));
|
|
723
723
|
self.apos.utils.error(e);
|
|
724
|
-
req.statusCode
|
|
724
|
+
// It is too late for req.statusCode, which is processed
|
|
725
|
+
// before sendPage is invoked
|
|
726
|
+
req.res.statusCode = 500;
|
|
725
727
|
return self.render(req, 'templateError');
|
|
726
728
|
}
|
|
727
729
|
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apostrophe",
|
|
3
|
-
"version": "2.220.
|
|
3
|
+
"version": "2.220.9",
|
|
4
4
|
"description": "The Apostrophe Content Management System.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "npm run lint && npm run audit && mocha --timeout 50000",
|
|
8
|
-
"audit": "npm audit",
|
|
9
|
-
"lint": "eslint ."
|
|
7
|
+
"test": "npm run build-test-package-json && npm run lint && npm run audit && mocha --timeout 50000",
|
|
8
|
+
"audit": "npm audit --production",
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"build-test-package-json": "node ./test-lib/build-test-package-json.js"
|
|
10
11
|
},
|
|
11
12
|
"repository": {
|
|
12
13
|
"type": "git",
|
|
@@ -32,13 +33,12 @@
|
|
|
32
33
|
"body-parser": "^1.19.0",
|
|
33
34
|
"cheerio": "^1.0.0-rc.10",
|
|
34
35
|
"chokidar": "^3.5.1",
|
|
35
|
-
"cli-progress": "^2.1.1",
|
|
36
36
|
"connect-flash": "^0.1.1",
|
|
37
37
|
"connect-multiparty": "^2.2.0",
|
|
38
38
|
"cookie-parser": "^1.4.5",
|
|
39
39
|
"credential": "^2.0.0",
|
|
40
40
|
"cuid": "^1.3.8",
|
|
41
|
-
"deep-get-set": "^
|
|
41
|
+
"deep-get-set": "^1.1.1",
|
|
42
42
|
"diff": "^4.0.1",
|
|
43
43
|
"emulate-mongo-2-driver": "^1.2.3",
|
|
44
44
|
"express": "^4.17.1",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"tinycolor2": "^1.4.1",
|
|
81
81
|
"uglify-js": "^2.8.29",
|
|
82
82
|
"underscore.string": "^3.3.5",
|
|
83
|
-
"uploadfs": "^1.
|
|
83
|
+
"uploadfs": "^1.18.4",
|
|
84
84
|
"xregexp": "^2.0.0",
|
|
85
85
|
"yargs": "^3.32.0"
|
|
86
86
|
},
|
package/test/login-throttle.js
CHANGED
|
@@ -6,7 +6,7 @@ let apos;
|
|
|
6
6
|
|
|
7
7
|
describe('Login', function() {
|
|
8
8
|
|
|
9
|
-
this.timeout(
|
|
9
|
+
this.timeout(60000);
|
|
10
10
|
|
|
11
11
|
after(function(done) {
|
|
12
12
|
return t.destroy(apos, done);
|
|
@@ -92,6 +92,10 @@ describe('Login', function() {
|
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
it('third failure in a row should cause a lockout', async function() {
|
|
95
|
+
if (process.version.startsWith('v8.')) {
|
|
96
|
+
console.log('Skipping this test on node 8, a partially supported release\nwhere this noncritical test is not reliable');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
95
99
|
const req = apos.tasks.getReq();
|
|
96
100
|
const user = await apos.users.find(req, {
|
|
97
101
|
username: 'LilithIyapo'
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"//": "Automatically generated to satisfy moog-require, do not edit",
|
|
3
|
+
"dependencies": {
|
|
4
|
+
"@apostrophecms/nunjucks": "^2.5.4",
|
|
5
|
+
"@sailshq/lodash": "^3.10.4",
|
|
6
|
+
"async": "^1.5.2",
|
|
7
|
+
"bless": "^3.0.3",
|
|
8
|
+
"bluebird": "^3.7.1",
|
|
9
|
+
"body-parser": "^1.19.0",
|
|
10
|
+
"cheerio": "^1.0.0-rc.10",
|
|
11
|
+
"chokidar": "^3.5.1",
|
|
12
|
+
"cli-progress": "^2.1.1",
|
|
13
|
+
"connect-flash": "^0.1.1",
|
|
14
|
+
"connect-multiparty": "^2.2.0",
|
|
15
|
+
"cookie-parser": "^1.4.5",
|
|
16
|
+
"credential": "^2.0.0",
|
|
17
|
+
"cuid": "^1.3.8",
|
|
18
|
+
"deep-get-set": "^0.1.1",
|
|
19
|
+
"diff": "^4.0.1",
|
|
20
|
+
"emulate-mongo-2-driver": "^1.2.3",
|
|
21
|
+
"express": "^4.17.1",
|
|
22
|
+
"express-session": "^1.17.0",
|
|
23
|
+
"glob": "^5.0.15",
|
|
24
|
+
"he": "^0.5.0",
|
|
25
|
+
"heic-to-jpeg-middleware": "^2.0.0",
|
|
26
|
+
"html-to-plaintext": "^0.1.1",
|
|
27
|
+
"html-to-text": "^5.1.1",
|
|
28
|
+
"i18n": "^0.8.6",
|
|
29
|
+
"is-wsl": "^2.2.0",
|
|
30
|
+
"joinr": "^1.0.2",
|
|
31
|
+
"jpeg-exif": "^1.1.4",
|
|
32
|
+
"launder": "^1.5.0",
|
|
33
|
+
"less": "^3.13.1",
|
|
34
|
+
"less-middleware": "^3.1.0",
|
|
35
|
+
"minimatch": "^3.0.4",
|
|
36
|
+
"mkdirp": "^1.0.3",
|
|
37
|
+
"moment": "^2.29.1",
|
|
38
|
+
"moog-require": "^1.1.0",
|
|
39
|
+
"nodemailer": "^6.6.2",
|
|
40
|
+
"oembetter": "^1.0.1",
|
|
41
|
+
"passport": "^0.3.2",
|
|
42
|
+
"passport-local": "^1.0.0",
|
|
43
|
+
"passport-totp": "0.0.2",
|
|
44
|
+
"path-to-regexp": "^1.7.0",
|
|
45
|
+
"performance-now": "^2.1.0",
|
|
46
|
+
"qs": "^6.9.6",
|
|
47
|
+
"regexp-quote": "0.0.0",
|
|
48
|
+
"request": "^2.88.2",
|
|
49
|
+
"request-promise": "^4.2.4",
|
|
50
|
+
"resolve": "^1.20.0",
|
|
51
|
+
"rimraf": "^2.7.1",
|
|
52
|
+
"sanitize-html": "^2.4.0",
|
|
53
|
+
"server-destroy": "^1.0.1",
|
|
54
|
+
"sluggo": "^0.2.0",
|
|
55
|
+
"syntax-error": "^1.3.0",
|
|
56
|
+
"thirty-two": "^1.0.2",
|
|
57
|
+
"tinycolor2": "^1.4.1",
|
|
58
|
+
"uglify-js": "^2.8.29",
|
|
59
|
+
"underscore.string": "^3.3.5",
|
|
60
|
+
"uploadfs": "^1.17.2",
|
|
61
|
+
"xregexp": "^2.0.0",
|
|
62
|
+
"yargs": "^3.32.0",
|
|
63
|
+
"apostrophe": "^2.0.0"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"eslint": "^6.5.1",
|
|
67
|
+
"eslint-config-apostrophe": "^2.0.2",
|
|
68
|
+
"eslint-config-standard": "^11.0.0",
|
|
69
|
+
"eslint-plugin-import": "^2.18.2",
|
|
70
|
+
"eslint-plugin-node": "^6.0.1",
|
|
71
|
+
"eslint-plugin-promise": "^3.8.0",
|
|
72
|
+
"eslint-plugin-standard": "^3.1.0",
|
|
73
|
+
"mocha": "^7.0.0"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const info = JSON.parse(fs.readFileSync('package.json'));
|
|
3
|
+
|
|
4
|
+
info.dependencies = info.dependencies || {};
|
|
5
|
+
info.dependencies.apostrophe = '^2.0.0';
|
|
6
|
+
fs.writeFileSync('test/package.json', JSON.stringify({
|
|
7
|
+
"//": "Automatically generated to satisfy moog-require, do not edit",
|
|
8
|
+
dependencies: info.dependencies || {},
|
|
9
|
+
devDependencies: info.devDependencies || {}
|
|
10
|
+
}, null, ' '));
|