apostrophe 3.55.0 → 3.56.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 +24 -1
- package/lib/moog-require.js +35 -3
- package/modules/@apostrophecms/module/index.js +6 -9
- package/modules/@apostrophecms/rich-text-widget/index.js +159 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +1 -0
- package/package.json +1 -1
- package/test/weird-api-errors.js +81 -0
- package/test/workspaces-project/app.js +16 -0
- package/test/workspaces-project/modules/@apostrophecms/log/index.js +40 -0
- package/test/workspaces-project/package.json +18 -0
- package/test/workspaces-project/workspace-a/package.json +15 -0
- package/test/workspaces-project.js +38 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 3.
|
|
3
|
+
## 3.56.0 (2023-09-13)
|
|
4
|
+
|
|
5
|
+
### Adds
|
|
6
|
+
|
|
7
|
+
* Add ability for custom tiptap extensions to access the options passed to rich text widgets at the area level.
|
|
8
|
+
* Add support for [npm workspaces](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#workspaces) dependencies. A workspace dependency can now be used as an Apostrophe module even if it is not a direct dependency of the Apostrophe project. Only direct workspaces dependencies of the Apostrophe project are supported, meaning this will only work with workspaces set in the Apostrophe project. Workspaces set in npm modules are not supported, please use [`bundle`](https://v3.docs.apostrophecms.org/reference/module-api/module-overview.html#bundle) instead. For instance, I have an Apostrophe project called `website`. `website` is set with two [npm workspaces](https://docs.npmjs.com/cli/v10/using-npm/workspaces), `workspace-a` & `workspace-b`. `workspace-a` `package.json` contains a module named `blog` as a dependency. `website` can reference `blog` as enabled in the Apostrophe `modules` configuration.
|
|
9
|
+
* The actual invocation of `renderPageForModule` by the `sendPage` method of all modules has been
|
|
10
|
+
factored out to `renderPage`, which is no longer deprecated. This provides a convenient override point
|
|
11
|
+
for those who wish to substitute something else for Nunjucks or just wrap the HTML in a larger data
|
|
12
|
+
structure. For consistent results, one might also choose to override the `renderWidget` and `render`
|
|
13
|
+
methods of the `@apostrophecms/area` module, which are used to render content while editing.
|
|
14
|
+
Thanks to Michelin for their support of this work.
|
|
15
|
+
* Add `@apostrophecms/rich-text-widget:lint-fix-figure` task to wrap text nodes in paragraph tags when next to figure tags. Figure tags are not valid children of paragraph tags.
|
|
16
|
+
* Add `@apostrophecms/rich-text-widget:remove-empty-paragraph` task to remove empty paragraphs from all existing rich-texts.
|
|
17
|
+
|
|
18
|
+
## 3.55.1 (2023-09-11)
|
|
19
|
+
|
|
20
|
+
### Fixes
|
|
21
|
+
|
|
22
|
+
* The structured logging for API routes now responds properly if an API route throws a `string` as an exception, rather than
|
|
23
|
+
a politely `Error`-derived object with a `stack` property. Previously this resulted in an error message about the logging
|
|
24
|
+
system itself, which was not useful for debugging the original exception.
|
|
25
|
+
|
|
26
|
+
## 3.55.0 (2023-08-30)
|
|
4
27
|
|
|
5
28
|
### Adds
|
|
6
29
|
|
package/lib/moog-require.js
CHANGED
|
@@ -186,10 +186,13 @@ module.exports = function(options) {
|
|
|
186
186
|
const initialFolder = path.dirname(self.root.filename);
|
|
187
187
|
let folder = initialFolder;
|
|
188
188
|
while (true) {
|
|
189
|
-
const file =
|
|
189
|
+
const file = path.resolve(folder, 'package.json');
|
|
190
190
|
if (fs.existsSync(file)) {
|
|
191
191
|
const info = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
192
|
-
self.validPackages = new Set([
|
|
192
|
+
self.validPackages = new Set([
|
|
193
|
+
...getDependencies(info),
|
|
194
|
+
...getWorkspacesDependencies(folder)(info)
|
|
195
|
+
]);
|
|
193
196
|
break;
|
|
194
197
|
} else {
|
|
195
198
|
folder = path.dirname(folder);
|
|
@@ -231,7 +234,36 @@ module.exports = function(options) {
|
|
|
231
234
|
}
|
|
232
235
|
self.symlinksCache.set(type, link);
|
|
233
236
|
return link;
|
|
234
|
-
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function getDependencies({
|
|
240
|
+
dependencies = {},
|
|
241
|
+
devDependencies = {}
|
|
242
|
+
} = {}) {
|
|
243
|
+
return [
|
|
244
|
+
...Object.keys(dependencies || {}),
|
|
245
|
+
...Object.keys(devDependencies || {})
|
|
246
|
+
];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function getWorkspacesDependencies(folder) {
|
|
250
|
+
return ({ workspaces = [] } = {}) => {
|
|
251
|
+
if (workspaces.length === 0) {
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Ternary is required because glob expects at least 2 entries when using curly braces
|
|
256
|
+
const pattern = workspaces.length === 1 ? workspaces[0] : `{${workspaces.join(',')}}`;
|
|
257
|
+
const packagePath = path.resolve(folder, pattern, 'package.json');
|
|
258
|
+
const workspacePackages = glob.sync(packagePath, { follow: true });
|
|
259
|
+
|
|
260
|
+
return workspacePackages.flatMap(packagePath => {
|
|
261
|
+
const info = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
262
|
+
|
|
263
|
+
return getDependencies(info);
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
}
|
|
235
267
|
|
|
236
268
|
self.isImprovement = function(name) {
|
|
237
269
|
return _.has(self.improvements, name);
|
|
@@ -258,7 +258,7 @@ module.exports = {
|
|
|
258
258
|
self.logError(req, `api-error${typeTrail}`, msg, {
|
|
259
259
|
name: response.name,
|
|
260
260
|
status: response.code,
|
|
261
|
-
stack: error.stack.split('\n').slice(1).map(line => line.trim()),
|
|
261
|
+
stack: (error.stack || '').split('\n').slice(1).map(line => line.trim()),
|
|
262
262
|
errorPath: response.path,
|
|
263
263
|
data: response.data
|
|
264
264
|
});
|
|
@@ -418,13 +418,11 @@ module.exports = {
|
|
|
418
418
|
// `data.query` (req.query)
|
|
419
419
|
//
|
|
420
420
|
// This method is async in 3.x and must be awaited.
|
|
421
|
+
//
|
|
422
|
+
// No longer deprecated because it is a useful override point
|
|
423
|
+
// for this part of the behavior of sendPage.
|
|
421
424
|
|
|
422
425
|
async renderPage(req, template, data) {
|
|
423
|
-
// TODO Remove in next major version.
|
|
424
|
-
self.apos.util.warnDevOnce(
|
|
425
|
-
'deprecate-renderPage',
|
|
426
|
-
'self.renderPage() is deprecated. Use self.sendPage() instead.'
|
|
427
|
-
);
|
|
428
426
|
return self.apos.template.renderPageForModule(req, template, data, self);
|
|
429
427
|
},
|
|
430
428
|
|
|
@@ -482,9 +480,8 @@ module.exports = {
|
|
|
482
480
|
try {
|
|
483
481
|
await self.apos.page.emit('beforeSend', req);
|
|
484
482
|
await self.apos.area.loadDeferredWidgets(req);
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
);
|
|
483
|
+
const result = await self.renderPage(req, template, data);
|
|
484
|
+
req.res.send(result);
|
|
488
485
|
span.setStatus({ code: telemetry.api.SpanStatusCode.OK });
|
|
489
486
|
} catch (err) {
|
|
490
487
|
telemetry.handleError(span, err);
|
|
@@ -739,5 +739,164 @@ module.exports = {
|
|
|
739
739
|
return finalData;
|
|
740
740
|
}
|
|
741
741
|
};
|
|
742
|
+
},
|
|
743
|
+
tasks(self) {
|
|
744
|
+
const confirm = async (isConfirmed) => {
|
|
745
|
+
if (isConfirmed) {
|
|
746
|
+
return true;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
console.log('This task will perform an update on all existing rich-text widget. You should manually backup your database before running this command in case it becomes necessary to revert the changes. You can add --confirm to the command to skip this message and run the command');
|
|
750
|
+
|
|
751
|
+
return false;
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
return {
|
|
755
|
+
'remove-empty-paragraph': {
|
|
756
|
+
usage: 'Usage: node app @apostrophecms/rich-text-widget:remove-empty-paragraph --confirm\n\nRemove empty paragraph. If a paragraph contains no visible text or only blank characters, it will be removed.\n',
|
|
757
|
+
task: async (argv) => {
|
|
758
|
+
const iterator = async (doc, widget, dotPath) => {
|
|
759
|
+
if (widget.type !== self.name) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const updates = {};
|
|
764
|
+
if (widget.content.includes('<p>')) {
|
|
765
|
+
const dom = cheerio.load(widget.content);
|
|
766
|
+
const paragraph = dom('body').find('p');
|
|
767
|
+
|
|
768
|
+
paragraph.each((index, element) => {
|
|
769
|
+
const isEmpty = /^(\s| )*$/.test(dom(element).text());
|
|
770
|
+
isEmpty && dom(element).remove();
|
|
771
|
+
|
|
772
|
+
if (isEmpty) {
|
|
773
|
+
updates[dotPath] = {
|
|
774
|
+
...widget,
|
|
775
|
+
content: dom('body').html()
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (Object.keys(updates).length) {
|
|
782
|
+
await self.apos.doc.db.updateOne(
|
|
783
|
+
{ _id: doc._id },
|
|
784
|
+
{ $set: updates }
|
|
785
|
+
);
|
|
786
|
+
self.apos.util.log(`Document ${doc._id} rich-texts have been updated`);
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
const isConfirmed = await confirm(argv.confirm);
|
|
791
|
+
|
|
792
|
+
return isConfirmed && self.apos.migration.eachWidget({}, iterator);
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
'lint-fix-figure': {
|
|
796
|
+
usage: 'Usage: node app @apostrophecms/rich-text-widget:lint-fix-figure --confirm\n\nFigure tags is allowed inside paragraph. This task will look for figure tag next to empty paragraph and wrap the text node around inside paragraph.\n',
|
|
797
|
+
task: async (argv) => {
|
|
798
|
+
const blockNodes = [
|
|
799
|
+
'address',
|
|
800
|
+
'article',
|
|
801
|
+
'aside',
|
|
802
|
+
'blockquote',
|
|
803
|
+
'canvas',
|
|
804
|
+
'dd',
|
|
805
|
+
'div',
|
|
806
|
+
'dl',
|
|
807
|
+
'dt',
|
|
808
|
+
'fieldset',
|
|
809
|
+
'figcaption',
|
|
810
|
+
'figure',
|
|
811
|
+
'footer',
|
|
812
|
+
'form',
|
|
813
|
+
'h1',
|
|
814
|
+
'h2',
|
|
815
|
+
'h3',
|
|
816
|
+
'h4',
|
|
817
|
+
'h5',
|
|
818
|
+
'h6',
|
|
819
|
+
'header',
|
|
820
|
+
'hr',
|
|
821
|
+
'li',
|
|
822
|
+
'main',
|
|
823
|
+
'nav',
|
|
824
|
+
'noscript',
|
|
825
|
+
'ol',
|
|
826
|
+
'p',
|
|
827
|
+
'pre',
|
|
828
|
+
'section',
|
|
829
|
+
'table',
|
|
830
|
+
'tfoot',
|
|
831
|
+
'ul',
|
|
832
|
+
'video'
|
|
833
|
+
];
|
|
834
|
+
const append = ({
|
|
835
|
+
dom,
|
|
836
|
+
wrapper,
|
|
837
|
+
element
|
|
838
|
+
}) => {
|
|
839
|
+
return wrapper
|
|
840
|
+
? wrapper.append(element) && wrapper
|
|
841
|
+
: dom(element).wrap('<p></p>').parent();
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
const iterator = async (doc, widget, dotPath) => {
|
|
845
|
+
if (widget.type !== self.name) {
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const updates = {};
|
|
850
|
+
if (widget.content.includes('<figure')) {
|
|
851
|
+
const dom = cheerio.load(widget.content);
|
|
852
|
+
// reference: https://stackoverflow.com/questions/28855070/css-select-element-without-text-inside
|
|
853
|
+
const figure = dom('body').find('p:not(:has(:not(:empty)))+figure,figure+p:not(:has(:not(:empty)))');
|
|
854
|
+
const parent = figure.parent().contents();
|
|
855
|
+
|
|
856
|
+
let wrapper = null;
|
|
857
|
+
|
|
858
|
+
parent.each((index, element) => {
|
|
859
|
+
const isFigure = element.type === 'tag' && element.name === 'figure';
|
|
860
|
+
isFigure && (wrapper = null);
|
|
861
|
+
|
|
862
|
+
const isNonWhitespaceTextNode = element.type === 'text' && /^\s*$/.test(element.data) === false;
|
|
863
|
+
isNonWhitespaceTextNode && (wrapper = append({
|
|
864
|
+
dom,
|
|
865
|
+
wrapper,
|
|
866
|
+
element
|
|
867
|
+
}));
|
|
868
|
+
|
|
869
|
+
const isInlineNode = element.type === 'tag' && blockNodes.includes(element.name) === false;
|
|
870
|
+
isInlineNode && (wrapper = append({
|
|
871
|
+
dom,
|
|
872
|
+
wrapper,
|
|
873
|
+
element
|
|
874
|
+
}));
|
|
875
|
+
|
|
876
|
+
const hasUpdate = isNonWhitespaceTextNode || isInlineNode;
|
|
877
|
+
if (hasUpdate) {
|
|
878
|
+
updates[dotPath] = {
|
|
879
|
+
...widget,
|
|
880
|
+
content: dom('body').html()
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
if (Object.keys(updates).length) {
|
|
887
|
+
await self.apos.doc.db.updateOne(
|
|
888
|
+
{ _id: doc._id },
|
|
889
|
+
{ $set: updates }
|
|
890
|
+
);
|
|
891
|
+
self.apos.util.log(`Document ${doc._id} rich-texts have been updated`);
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
const isConfirmed = await confirm(argv.confirm);
|
|
896
|
+
|
|
897
|
+
return isConfirmed && self.apos.migration.eachWidget({}, iterator);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
};
|
|
742
901
|
}
|
|
743
902
|
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const t = require('../test-lib/test.js');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
describe('Don\'t crash on weird API errors', function() {
|
|
5
|
+
|
|
6
|
+
after(async function() {
|
|
7
|
+
return t.destroy(apos);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
this.timeout(t.timeout);
|
|
11
|
+
|
|
12
|
+
let apos;
|
|
13
|
+
|
|
14
|
+
it('should initialize apos', async function() {
|
|
15
|
+
apos = await t.create({
|
|
16
|
+
root: module,
|
|
17
|
+
modules: {
|
|
18
|
+
'api-test': {
|
|
19
|
+
apiRoutes(self) {
|
|
20
|
+
return {
|
|
21
|
+
get: {
|
|
22
|
+
fetchItFine(req) {
|
|
23
|
+
return {
|
|
24
|
+
nifty: true
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
fetchItFailWeird(req) {
|
|
28
|
+
throw 'not-an-error-object';
|
|
29
|
+
},
|
|
30
|
+
fetchItFailNormal(req) {
|
|
31
|
+
throw new Error('normal error');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
it('should fetch fine in the normal case', async function() {
|
|
41
|
+
const body = await apos.http.get('/api/v1/api-test/fetch-it-fine', {});
|
|
42
|
+
assert(typeof body === 'object');
|
|
43
|
+
assert.strictEqual(body.nifty, true);
|
|
44
|
+
});
|
|
45
|
+
it('should fail politely in the weird case of a non-Error exception', async function() {
|
|
46
|
+
let msgWas;
|
|
47
|
+
const consoleError = console.error;
|
|
48
|
+
console.error = msg => {
|
|
49
|
+
msgWas = msg;
|
|
50
|
+
};
|
|
51
|
+
try {
|
|
52
|
+
await apos.http.get('/api/v1/api-test/fetch-it-fail-weird', {});
|
|
53
|
+
// Should not get here
|
|
54
|
+
assert(false);
|
|
55
|
+
} catch (e) {
|
|
56
|
+
// Make sure the logging system itself is not at fault
|
|
57
|
+
assert(!msgWas.toString().includes('Structured logging error'));
|
|
58
|
+
} finally {
|
|
59
|
+
console.error = consoleError;
|
|
60
|
+
console.error(msgWas);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
it('should fail politely in the normal case of an Error exception', async function() {
|
|
64
|
+
let msgWas;
|
|
65
|
+
const consoleError = console.error;
|
|
66
|
+
console.error = msg => {
|
|
67
|
+
msgWas = msg;
|
|
68
|
+
};
|
|
69
|
+
try {
|
|
70
|
+
await apos.http.get('/api/v1/api-test/fetch-it-fail-normal', {});
|
|
71
|
+
// Should not get here
|
|
72
|
+
assert(false);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
// Make sure the logging system itself is not at fault
|
|
75
|
+
assert(!msgWas.toString().includes('Structured logging error'));
|
|
76
|
+
} finally {
|
|
77
|
+
console.error = consoleError;
|
|
78
|
+
console.error(msgWas);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
root: module,
|
|
3
|
+
shortName: 'workspaces-project',
|
|
4
|
+
modules: {
|
|
5
|
+
'@apostrophecms/express': {
|
|
6
|
+
options: {
|
|
7
|
+
address: '127.0.0.1'
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
'@apostrophecms/sitemap': {
|
|
11
|
+
options: {
|
|
12
|
+
baseUrl: 'http://localhost:3000'
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const createLogger = () => {
|
|
2
|
+
const messages = {
|
|
3
|
+
debug: [],
|
|
4
|
+
info: [],
|
|
5
|
+
warn: [],
|
|
6
|
+
error: []
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
debug: (...args) => {
|
|
11
|
+
console.debug(...args);
|
|
12
|
+
messages.debug.push(...args);
|
|
13
|
+
},
|
|
14
|
+
info: (...args) => {
|
|
15
|
+
console.info(...args);
|
|
16
|
+
messages.info.push(...args);
|
|
17
|
+
},
|
|
18
|
+
warn: (...args) => {
|
|
19
|
+
console.warn(...args);
|
|
20
|
+
messages.warn.push(...args);
|
|
21
|
+
},
|
|
22
|
+
error: (...args) => {
|
|
23
|
+
console.error(...args);
|
|
24
|
+
messages.error.push(...args);
|
|
25
|
+
},
|
|
26
|
+
destroy: () => {
|
|
27
|
+
delete messages.debug;
|
|
28
|
+
delete messages.info;
|
|
29
|
+
delete messages.warn;
|
|
30
|
+
delete messages.error;
|
|
31
|
+
},
|
|
32
|
+
getMessages: () => messages
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
options: {
|
|
38
|
+
logger: createLogger()
|
|
39
|
+
}
|
|
40
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "workspace-project",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "app.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [],
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"apostrophe": "file:../../."
|
|
14
|
+
},
|
|
15
|
+
"workspaces": [
|
|
16
|
+
"workspace-a"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "workspace-a",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [],
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@apostrophecms/sitemap": "^1.0.2"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const assert = require('node:assert').strict;
|
|
2
|
+
const util = require('node:util');
|
|
3
|
+
const { exec } = require('node:child_process');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const t = require('../test-lib/test.js');
|
|
6
|
+
const app = require('./workspaces-project/app.js');
|
|
7
|
+
|
|
8
|
+
describe('workspaces dependencies', function() {
|
|
9
|
+
this.timeout(t.timeout);
|
|
10
|
+
|
|
11
|
+
before(async function() {
|
|
12
|
+
await util.promisify(exec)('npm install', { cwd: path.resolve(process.cwd(), 'test/workspaces-project') });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should allow workspaces dependency in the project', async function() {
|
|
16
|
+
let apos;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
apos = await t.create(app);
|
|
20
|
+
const { server } = apos.modules['@apostrophecms/express'];
|
|
21
|
+
const { address, port } = server.address();
|
|
22
|
+
|
|
23
|
+
const actual = apos.util.logger.getMessages();
|
|
24
|
+
const expected = {
|
|
25
|
+
debug: [],
|
|
26
|
+
info: [ `Listening at http://${address}:${port}` ],
|
|
27
|
+
warn: [],
|
|
28
|
+
error: []
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
assert.deepEqual(actual, expected);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
assert.fail('Should have found @apostrophecms/sitemap hidden in workspace-a as a valid dependency. '.concat(error.message));
|
|
34
|
+
} finally {
|
|
35
|
+
apos && await t.destroy(apos);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|