apostrophe 3.30.0 → 3.32.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.
Files changed (75) hide show
  1. package/.eslintrc +3 -0
  2. package/CHANGELOG.md +26 -0
  3. package/modules/@apostrophecms/area/index.js +6 -0
  4. package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +18 -2
  5. package/modules/@apostrophecms/asset/index.js +65 -7
  6. package/modules/@apostrophecms/asset/lib/webpack/utils.js +242 -28
  7. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +26 -16
  8. package/modules/@apostrophecms/i18n/i18n/en.json +19 -3
  9. package/modules/@apostrophecms/i18n/i18n/es.json +3 -0
  10. package/modules/@apostrophecms/i18n/i18n/fr.json +3 -0
  11. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +4 -1
  12. package/modules/@apostrophecms/i18n/i18n/sk.json +3 -0
  13. package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +41 -20
  14. package/modules/@apostrophecms/image-widget/index.js +4 -1
  15. package/modules/@apostrophecms/image-widget/public/placeholder.jpg +0 -0
  16. package/modules/@apostrophecms/image-widget/ui/src/index.scss +3 -0
  17. package/modules/@apostrophecms/image-widget/views/widget.html +35 -27
  18. package/modules/@apostrophecms/login/index.js +123 -26
  19. package/modules/@apostrophecms/login/ui/apos/components/AposForgotPasswordForm.vue +124 -0
  20. package/modules/@apostrophecms/login/ui/apos/components/AposLoginForm.vue +339 -0
  21. package/modules/@apostrophecms/login/ui/apos/components/AposResetPasswordForm.vue +163 -0
  22. package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +135 -293
  23. package/modules/@apostrophecms/login/ui/apos/components/TheAposLoginHeader.vue +65 -14
  24. package/modules/@apostrophecms/login/ui/apos/mixins/AposLoginFormMixin.js +45 -0
  25. package/modules/@apostrophecms/login/views/passwordResetEmail.html +9 -0
  26. package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +17 -6
  27. package/modules/@apostrophecms/modal/ui/apos/components/AposModalBody.vue +4 -1
  28. package/modules/@apostrophecms/modal/ui/apos/components/AposModalTabs.vue +7 -1
  29. package/modules/@apostrophecms/piece-type/index.js +1 -1
  30. package/modules/@apostrophecms/rich-text-widget/index.js +4 -1
  31. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +75 -12
  32. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Default.js +11 -5
  33. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Heading.js +6 -2
  34. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Link.js +7 -4
  35. package/modules/@apostrophecms/schema/lib/addFieldTypes.js +1 -1
  36. package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +4 -1
  37. package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +4 -0
  38. package/modules/@apostrophecms/template/index.js +11 -12
  39. package/modules/@apostrophecms/template/lib/bundlesLoader.js +20 -5
  40. package/modules/@apostrophecms/ui/ui/apos/mixins/AposAdvisoryLockMixin.js +2 -1
  41. package/modules/@apostrophecms/video-widget/index.js +4 -1
  42. package/modules/@apostrophecms/video-widget/views/widget.html +24 -16
  43. package/modules/@apostrophecms/widget-type/index.js +44 -8
  44. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidget.vue +5 -2
  45. package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +11 -0
  46. package/package.json +6 -5
  47. package/test/assets.js +338 -25
  48. package/test/extra_node_modules/@company/bundle/index.js +8 -0
  49. package/test/extra_node_modules/@company/bundle/ui/src/company.js +3 -0
  50. package/test/extra_node_modules/@company/bundle/ui/src/company.scss +3 -0
  51. package/test/login.js +427 -12
  52. package/test/modules/@company/bundle/index.js +10 -0
  53. package/test/modules/@company/bundle/ui/src/company.js +3 -0
  54. package/test/modules/@company/bundle/ui/src/company.scss +3 -0
  55. package/test/modules/bundle-edge/index.js +10 -0
  56. package/test/modules/bundle-edge/ui/src/edge.js +3 -0
  57. package/test/modules/bundle-edge/ui/src/edge.scss +3 -0
  58. package/test/modules/bundle-page/index.js +2 -1
  59. package/test/modules/bundle-page/ui/src/extra.js +3 -1
  60. package/test/modules/bundle-page/ui/src/extra.scss +3 -0
  61. package/test/modules/bundle-page/ui/src/main.js +3 -0
  62. package/test/modules/bundle-page/ui/src/main.scss +3 -0
  63. package/test/modules/bundle-page-type/index.js +12 -0
  64. package/test/modules/bundle-page-type/ui/src/another.js +3 -0
  65. package/test/modules/bundle-page-type/ui/src/index.js +3 -0
  66. package/test/modules/bundle-page-type/ui/src/index.scss +3 -0
  67. package/test/modules/bundle-page-type/ui/src/main.js +3 -0
  68. package/test/modules/bundle-page-type/ui/src/main.scss +3 -0
  69. package/test/modules/bundle-widget/ui/src/extra2.js +3 -1
  70. package/test/modules/placeholder-page/index.js +21 -0
  71. package/test/modules/placeholder-page/views/page.html +3 -0
  72. package/test/modules/placeholder-widget/index.js +36 -0
  73. package/test/modules/placeholder-widget/views/widget.html +5 -0
  74. package/test/widgets.js +453 -114
  75. package/test-lib/test.js +9 -1
@@ -1,20 +1,28 @@
1
- {% if data.options.className %}
2
- {% set className = data.options.className %}
3
- {% elif data.manager.options.className %}
4
- {% set className = data.manager.options.className %}
5
- {% endif %}
6
-
7
- {# oembed repopulates me #}
8
- {% if data.widget.video %}
1
+ {% if data.widget.aposPlaceholder and data.manager.options.placeholderUrl %}
9
2
  <div
10
- {% if className %} class="{{ className }}"{% endif %}
11
3
  data-apos-video-widget
12
- data-apos-video-url={{ data.widget.video.url }}
4
+ data-apos-video-url="{{ data.manager.options.placeholderUrl }}"
13
5
  >
14
- {% if data.widget.video.thumbnail %}
15
- <img src="{{ data.widget.video.thumbnail }}" alt="{{ data.widget.video.thumbnail }}"/>
16
- {% endif %}
17
6
  </div>
18
- {% elif data.user %}
19
- <p {% if data.manager.options.className %} class="{{ data.manager.options.className }} {{ data.manager.options.className }}--error"{% endif %}>No video selected</p>
20
- {% endif %}
7
+ {% else %}
8
+ {% if data.options.className %}
9
+ {% set className = data.options.className %}
10
+ {% elif data.manager.options.className %}
11
+ {% set className = data.manager.options.className %}
12
+ {% endif %}
13
+
14
+ {# oembed repopulates me #}
15
+ {% if data.widget.video %}
16
+ <div
17
+ {% if className %} class="{{ className }}"{% endif %}
18
+ data-apos-video-widget
19
+ data-apos-video-url={{ data.widget.video.url }}
20
+ >
21
+ {% if data.widget.video.thumbnail %}
22
+ <img src="{{ data.widget.video.thumbnail }}" alt="{{ data.widget.video.thumbnail }}"/>
23
+ {% endif %}
24
+ </div>
25
+ {% elif data.user %}
26
+ <p {% if data.manager.options.className %} class="{{ data.manager.options.className }} {{ data.manager.options.className }}--error"{% endif %}>No video selected</p>
27
+ {% endif %}
28
+ {% endif %}
@@ -97,7 +97,10 @@ const _ = require('lodash');
97
97
  module.exports = {
98
98
  cascades: [ 'fields' ],
99
99
  options: {
100
- neverLoadSelf: true
100
+ neverLoadSelf: true,
101
+ initialModal: true,
102
+ placeholder: false,
103
+ placeholderClass: 'apos-placeholder'
101
104
  },
102
105
  init(self) {
103
106
 
@@ -161,8 +164,24 @@ module.exports = {
161
164
  ...self.getWidgetsBundles(`${widget.type}-widget`)
162
165
  };
163
166
 
167
+ let effectiveWidget = widget;
168
+
169
+ if (widget.aposPlaceholder === true) {
170
+ // Do not render widget on preview mode:
171
+ if (req.query.aposEdit !== '1') {
172
+ return '';
173
+ }
174
+
175
+ effectiveWidget = { ...widget };
176
+ self.schema.forEach(field => {
177
+ if (field.placeholder !== undefined) {
178
+ effectiveWidget[field.name] = field.placeholder;
179
+ }
180
+ });
181
+ }
182
+
164
183
  return self.render(req, self.template, {
165
- widget: widget,
184
+ widget: effectiveWidget,
166
185
  options: options,
167
186
  manager: self,
168
187
  contextOptions: _with
@@ -176,11 +195,25 @@ module.exports = {
176
195
  return {};
177
196
  }
178
197
 
179
- return Object.values(widget.__meta.webpack || {})
180
- .reduce((acc, config) => {
198
+ const { rebundleModules } = self.apos.asset;
199
+
200
+ const rebundleConfigs = rebundleModules.filter(entry => {
201
+ const names = widget.__meta?.chain?.map(c => c.name) ?? [ widgetType ];
202
+ return names.includes(entry.name);
203
+ });
204
+
205
+ return Object.entries(widget.__meta.webpack || {})
206
+ .reduce((acc, [ moduleName, config ]) => {
207
+ if (!config || !config.bundles) {
208
+ return acc;
209
+ }
181
210
  return {
182
211
  ...acc,
183
- ...config && config.bundles
212
+ ...self.apos.asset.transformRebundledFor(
213
+ moduleName,
214
+ config.bundles,
215
+ rebundleConfigs
216
+ )
184
217
  };
185
218
  }, {});
186
219
  },
@@ -276,11 +309,14 @@ module.exports = {
276
309
  // Make sure we get default values for contextual fields so
277
310
  // `by` doesn't go missing for `@apostrophecms/image-widget`
278
311
  const output = self.apos.schema.newInstance(self.schema);
279
- const schema = self.allowedSchema(req);
280
312
  output._id = self.apos.launder.id(input._id) || self.apos.util.generateId();
281
- await self.apos.schema.convert(req, schema, input, output);
282
313
  output.metaType = 'widget';
283
314
  output.type = self.name;
315
+ output.aposPlaceholder = self.apos.launder.boolean(input.aposPlaceholder);
316
+ if (!output.aposPlaceholder) {
317
+ const schema = self.allowedSchema(req);
318
+ await self.apos.schema.convert(req, schema, input, output);
319
+ }
284
320
  return output;
285
321
  },
286
322
 
@@ -352,7 +388,7 @@ module.exports = {
352
388
  action: self.action,
353
389
  schema: schema,
354
390
  contextual: self.options.contextual,
355
- skipInitialModal: self.options.skipInitialModal,
391
+ placeholderClass: self.options.placeholderClass,
356
392
  className: self.options.className,
357
393
  components: self.options.components
358
394
  });
@@ -1,6 +1,9 @@
1
+ <!-- eslint-disable vue/no-v-html -->
1
2
  <template>
2
- <!-- eslint-disable-next-line vue/no-v-html -->
3
- <div v-html="rendered" />
3
+ <div
4
+ :class="getClasses()"
5
+ v-html="rendered"
6
+ />
4
7
  </template>
5
8
 
6
9
  <script>
@@ -62,6 +62,17 @@ export default {
62
62
  this.rendered = '<p>Unable to render this widget.</p>';
63
63
  console.error('Unable to render widget. Possibly the schema has been changed and the existing widget does not pass validation.', e);
64
64
  }
65
+ },
66
+ getClasses() {
67
+ const { placeholderClass } = this.moduleOptions;
68
+
69
+ if (!placeholderClass) {
70
+ return {};
71
+ }
72
+
73
+ return {
74
+ [placeholderClass]: this.value.aposPlaceholder === true
75
+ };
65
76
  }
66
77
  }
67
78
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apostrophe",
3
- "version": "3.30.0",
3
+ "version": "3.32.0",
4
4
  "description": "The Apostrophe Content Management System.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -35,6 +35,7 @@
35
35
  "@opentelemetry/semantic-conventions": "^1.0.1",
36
36
  "@tiptap/extension-highlight": "^2.0.0-beta.33",
37
37
  "@tiptap/extension-link": "^2.0.0-beta.38",
38
+ "@tiptap/extension-placeholder": "^2.0.0-beta.196",
38
39
  "@tiptap/extension-text-align": "^2.0.0-beta.29",
39
40
  "@tiptap/extension-text-style": "^2.0.0-beta.23",
40
41
  "@tiptap/extension-underline": "^2.0.0-beta.23",
@@ -124,16 +125,16 @@
124
125
  "eslint-config-apostrophe": "^3.4.0",
125
126
  "eslint-plugin-n": "^15.2.1",
126
127
  "eslint-plugin-node": "^11.1.0",
128
+ "eslint-plugin-promise": "^5.1.0",
127
129
  "eslint-plugin-vue": "^7.9.0",
128
130
  "mocha": "^9.1.2",
129
131
  "nyc": "^15.1.0",
130
132
  "replace-in-file": "^6.1.0",
131
- "vue-eslint-parser": "^7.1.1",
132
- "webpack-bundle-analyzer": "^3.9.0",
133
- "eslint-plugin-promise": "^5.1.0",
134
133
  "stylelint": "^14.6.1",
135
134
  "stylelint-declaration-strict-value": "^1.8.0",
136
- "stylelint-order": "^5.0.0"
135
+ "stylelint-order": "^5.0.0",
136
+ "vue-eslint-parser": "^7.1.1",
137
+ "webpack-bundle-analyzer": "^3.9.0"
137
138
  },
138
139
  "browserslist": [
139
140
  "ie >= 10"
package/test/assets.js CHANGED
@@ -6,6 +6,8 @@ const Promise = require('bluebird');
6
6
 
7
7
  const {
8
8
  checkModulesWebpackConfig,
9
+ formatRebundleConfig,
10
+ verifyRebundleConfig,
9
11
  getWebpackExtensions,
10
12
  fillExtraBundles
11
13
  } = require('../modules/@apostrophecms/asset/lib/webpack/utils');
@@ -99,7 +101,12 @@ describe('Assets', function() {
99
101
  after(async function() {
100
102
  await deleteBuiltFolders(publicFolderPath, true);
101
103
  await removeCache();
102
- return t.destroy(apos);
104
+ await t.destroy(apos);
105
+ });
106
+
107
+ afterEach(function() {
108
+ // Prevent hang forever if particular tests fail while testing prod.
109
+ process.env.NODE_ENV = 'development';
103
110
  });
104
111
 
105
112
  this.timeout(5 * 60 * 1000);
@@ -136,13 +143,16 @@ describe('Assets', function() {
136
143
 
137
144
  it('should get webpack extensions from modules and fill extra bundles', async function () {
138
145
  const expectedEntryPointsNames = {
139
- js: [ 'extra', 'extra2' ],
140
- css: [ 'extra' ]
146
+ js: [ 'company', 'main', 'another', 'extra', 'extra2' ],
147
+ css: [ 'company', 'main', 'extra' ]
141
148
  };
142
149
 
143
150
  apos = await t.create({
144
151
  root: module,
145
- modules
152
+ modules: {
153
+ '@company/bundle': {},
154
+ ...modules
155
+ }
146
156
  });
147
157
 
148
158
  const {
@@ -158,23 +168,28 @@ describe('Assets', function() {
158
168
  assert(extensions.ext1.resolve.alias.ext1Overriden);
159
169
  assert(extensions.ext2.resolve.alias.ext2);
160
170
 
161
- assert(Object.keys(verifiedBundles).length === 2);
171
+ assert.equal(
172
+ Object.keys(verifiedBundles).length,
173
+ Math.max(expectedEntryPointsNames.js.length, expectedEntryPointsNames.css.length)
174
+ );
162
175
 
163
- assert(verifiedBundles.extra.js.length === 1);
164
- assert(verifiedBundles.extra.scss.length === 1);
176
+ assert(verifiedBundles.main.js.length, 2);
177
+ assert(verifiedBundles.main.scss.length, 1);
165
178
 
166
- assert(verifiedBundles.extra2.js.length === 1);
167
- assert(verifiedBundles.extra2.scss.length === 0);
179
+ // The local and the npm module source
180
+ assert.equal(verifiedBundles.company.js.length, 2);
181
+ assert.equal(verifiedBundles.company.scss.length, 2);
168
182
 
169
- const filled = fillExtraBundles(verifiedBundles);
183
+ assert.equal(verifiedBundles.extra.js.length, 1);
184
+ assert.equal(verifiedBundles.extra.scss.length, 1);
170
185
 
171
- filled.js.forEach((name) => {
172
- assert(expectedEntryPointsNames.js.includes(name));
173
- });
186
+ assert.equal(verifiedBundles.extra2.js.length, 1);
187
+ assert.equal(verifiedBundles.extra2.scss.length, 0);
174
188
 
175
- filled.css.forEach((name) => {
176
- assert(expectedEntryPointsNames.css.includes(name));
177
- });
189
+ const filled = fillExtraBundles(verifiedBundles);
190
+
191
+ assert.deepEqual(filled.js, expectedEntryPointsNames.js);
192
+ assert.deepEqual(filled.css, expectedEntryPointsNames.css);
178
193
  });
179
194
 
180
195
  it('should build the right bundles in dev and prod modes', async function () {
@@ -214,18 +229,22 @@ describe('Assets', function() {
214
229
  const bundlePage = await apos.http.get('/bundle', { jar });
215
230
 
216
231
  assert(bundlePage.includes(getStylesheetMarkup('public-bundle')));
232
+ assert(bundlePage.includes(getStylesheetMarkup('main-bundle')));
217
233
  assert(!bundlePage.includes(getStylesheetMarkup('extra-bundle')));
218
234
 
219
235
  assert(bundlePage.includes(getScriptMarkup('public-module-bundle')));
236
+ assert(bundlePage.includes(getScriptMarkup('main-module-bundle')));
220
237
  assert(!bundlePage.includes(getScriptMarkup('extra-module-bundle')));
221
238
  assert(bundlePage.includes(getScriptMarkup('extra2-module-bundle')));
222
239
 
223
240
  const childPage = await apos.http.get('/bundle/child', { jar });
224
241
 
225
242
  assert(childPage.includes(getStylesheetMarkup('public-bundle')));
243
+ assert(bundlePage.includes(getStylesheetMarkup('main-bundle')));
226
244
  assert(childPage.includes(getStylesheetMarkup('extra-bundle')));
227
245
 
228
246
  assert(childPage.includes(getScriptMarkup('public-module-bundle')));
247
+ assert(bundlePage.includes(getScriptMarkup('main-module-bundle')));
229
248
  assert(childPage.includes(getScriptMarkup('extra-module-bundle')));
230
249
  assert(!childPage.includes(getScriptMarkup('extra2-module-bundle')));
231
250
  });
@@ -472,7 +491,7 @@ describe('Assets', function() {
472
491
  // Modify asset and rebuild
473
492
  const assetPath = path.join(process.cwd(), 'test/modules/bundle-page/ui/src/extra.js');
474
493
  const assetPathPublic = path.join(process.cwd(), 'test/public/apos-frontend/default/extra-module-bundle.js');
475
- const assetContent = 'export default () => {};\n';
494
+ const assetContent = fs.readFileSync(assetPath, 'utf-8');
476
495
  fs.writeFileSync(
477
496
  assetPath,
478
497
  'export default () => { \'bundle-page-watcher-test-src\'; };\n',
@@ -526,8 +545,8 @@ describe('Assets', function() {
526
545
  const assetPathPublicCss = path.join(rootPath, 'test/public/apos-frontend/default/public-bundle.css');
527
546
  const assetPathAposJs = path.join(rootPath, 'test/public/apos-frontend/default/apos-module-bundle.js');
528
547
  const assetPathAposCss = path.join(rootPath, 'test/public/apos-frontend/default/apos-bundle.css');
529
- const assetContentJs = 'export default () => {};\n';
530
- const assetContentScss = '.default-page {color:red;}\n';
548
+ const assetContentJs = fs.readFileSync(assetPathJs, 'utf-8');
549
+ const assetContentScss = fs.readFileSync(assetPathScss, 'utf-8');
531
550
  // Resurrect the default assets content if test has failed
532
551
  fs.writeFileSync(assetPathJs, assetContentJs, 'utf8');
533
552
  fs.writeFileSync(assetPathScss, assetContentScss, 'utf8');
@@ -647,8 +666,8 @@ describe('Assets', function() {
647
666
  const assetPathPublicCss = path.join(rootPath, 'test/public/apos-frontend/default/public-bundle.css');
648
667
  const assetPathAposJs = path.join(rootPath, 'test/public/apos-frontend/default/apos-module-bundle.js');
649
668
  const assetPathAposCss = path.join(rootPath, 'test/public/apos-frontend/default/apos-bundle.css');
650
- const assetContentJs = 'export default () => {};\n';
651
- const assetContentScss = '.default-page {color:red;}\n';
669
+ const assetContentJs = fs.readFileSync(assetPathJs, 'utf-8');
670
+ const assetContentScss = fs.readFileSync(assetPathCss, 'utf-8');
652
671
  // Resurrect the default assets content if test has failed
653
672
  fs.writeFileSync(assetPathJs, assetContentJs, 'utf8');
654
673
  fs.writeFileSync(assetPathCss, assetContentScss, 'utf8');
@@ -982,8 +1001,8 @@ describe('Assets', function() {
982
1001
  const assetPathPublicCss = path.join(rootPath, 'test/public/apos-frontend/default/public-bundle.css');
983
1002
  const assetPathAposJs = path.join(rootPath, 'test/public/apos-frontend/default/apos-module-bundle.js');
984
1003
  const assetPathAposCss = path.join(rootPath, 'test/public/apos-frontend/default/apos-bundle.css');
985
- const assetContentJs = 'export default () => {};\n';
986
- const assetContentScss = '.default-page {color:red;}\n';
1004
+ const assetContentJs = fs.readFileSync(assetPathJs, 'utf-8');
1005
+ const assetContentScss = fs.readFileSync(assetPathScss, 'utf-8');
987
1006
  // Resurrect the default assets content if test has failed
988
1007
  fs.writeFileSync(assetPathJs, assetContentJs, 'utf8');
989
1008
  fs.writeFileSync(assetPathScss, assetContentScss, 'utf8');
@@ -1124,7 +1143,7 @@ describe('Assets', function() {
1124
1143
 
1125
1144
  const assetPath = path.join(process.cwd(), 'test/modules/bundle-page/ui/src/extra.js');
1126
1145
  const assetPathPublic = path.join(process.cwd(), 'test/public/apos-frontend/default/extra-module-bundle.js');
1127
- const assetContent = 'export default () => {};\n';
1146
+ const assetContent = fs.readFileSync(assetPath, 'utf-8');
1128
1147
 
1129
1148
  // Modify below the debounce rate
1130
1149
  for (const i of [ 1, 2, 3 ]) {
@@ -1330,6 +1349,289 @@ describe('Assets', function() {
1330
1349
 
1331
1350
  assertWebpackExtensionOptions(extensions, extensionOptions);
1332
1351
  });
1352
+
1353
+ it('should verify that asset re-bundle configs are valid', async function () {
1354
+ assert.doesNotThrow(() => verifyRebundleConfig());
1355
+ assert.doesNotThrow(() => verifyRebundleConfig([]));
1356
+ assert.doesNotThrow(() => formatRebundleConfig());
1357
+ assert.doesNotThrow(() => formatRebundleConfig({}));
1358
+
1359
+ assert.doesNotThrow(() => formatRebundleConfig({
1360
+ 'bundle-page': 'main',
1361
+ 'bundle-page-type': 'new',
1362
+ 'bundle-widget:extra': 'widget',
1363
+ '@company/bundle:company': 'newcompany',
1364
+ 'bundle-edge:edge': 'main'
1365
+ }));
1366
+
1367
+ // too much catch-all
1368
+ assert.throws(() => formatRebundleConfig({
1369
+ 'bundle-page': 'main',
1370
+ 'bundle-page:extra': 'new'
1371
+ }));
1372
+ assert.throws(() => formatRebundleConfig({
1373
+ 'bundle-page:extra': 'new',
1374
+ 'bundle-page': 'main'
1375
+ }));
1376
+ assert.throws(() => formatRebundleConfig({
1377
+ 'bundle-page': 'main',
1378
+ 'bundle-page:extra': 'main'
1379
+ }));
1380
+ assert.throws(() => formatRebundleConfig({
1381
+ 'bundle-page': 'new',
1382
+ 'bundle-page:extra': 'another'
1383
+ }));
1384
+ assert.throws(() => formatRebundleConfig({
1385
+ 'bundle-page:extra': 'another',
1386
+ 'bundle-page': 'new'
1387
+ }));
1388
+ });
1389
+
1390
+ it('should build and remap the right bundles in dev and prod modes', async function () {
1391
+ await t.destroy(apos);
1392
+ const getPath = (p) => `${publicFolderPath}/apos-frontend/` + p;
1393
+ const checkFileExists = async (p) => fs.pathExists(getPath(p));
1394
+ async function checkBundlesExists (folderPath, fileNames) {
1395
+ for (const fileName of fileNames) {
1396
+ const extraBundleExists = await checkFileExists(folderPath + fileName);
1397
+ assert(extraBundleExists);
1398
+ }
1399
+ }
1400
+ function checkBundlesContents (folderPath, bundles, not = false) {
1401
+ for (const [ fileName, regexes ] of Object.entries(bundles)) {
1402
+ const contents = fs.readFileSync(getPath(folderPath + fileName), 'utf-8');
1403
+ for (const regex of regexes) {
1404
+ const method = not ? 'doesNotMatch' : 'match';
1405
+ assert[method](contents, new RegExp(regex), `${fileName} - ${regex}`);
1406
+ }
1407
+ }
1408
+ }
1409
+
1410
+ apos = await t.create({
1411
+ root: module,
1412
+ modules: {
1413
+ '@company/bundle': {},
1414
+ 'bundle-edge': {},
1415
+ ...modules,
1416
+ '@apostrophecms/asset': {
1417
+ options: {
1418
+ rebundleModules: {
1419
+ // Everything from the `bundle-page` module should
1420
+ // go in the regular "main" bundle
1421
+ 'bundle-page': 'main',
1422
+ // all from `bundle-page-type` should go
1423
+ // in a new bundle 'bundle-page'
1424
+ 'bundle-page-type': 'new',
1425
+ // 'extra2' bundle from `bundle-widget` should go
1426
+ // in a new bundle 'widget-bundle'
1427
+ 'bundle-widget:extra2': 'widget',
1428
+ // 'company' bundle from `@company/bundle:company` should go
1429
+ // in a new bundle 'newcompany'. The local module contribution
1430
+ // to the 'company' build should stay.
1431
+ '@company/bundle:company': 'newcompany',
1432
+ // Edge case - send "edge" bundle only to the main bundle
1433
+ 'bundle-edge:edge': 'main'
1434
+ }
1435
+ }
1436
+ }
1437
+ }
1438
+ });
1439
+
1440
+ const existingBundleNames = [
1441
+ 'public-module-bundle.js',
1442
+ 'public-bundle.css',
1443
+ 'new-module-bundle.js',
1444
+ 'new-bundle.css',
1445
+ 'company-module-bundle.js',
1446
+ 'company-bundle.css',
1447
+ 'newcompany-module-bundle.js',
1448
+ 'newcompany-bundle.css',
1449
+ 'widget-module-bundle.js'
1450
+ ];
1451
+ const bundleContents = {
1452
+ 'public-module-bundle.js': [
1453
+ /BUNDLE_MAIN_PAGE['"]+/g,
1454
+ /BUNDLE_EXTRA_PAGE['"]+/g,
1455
+ /BUNDLE_EDGE['"]+/g
1456
+ ],
1457
+ 'public-bundle.css': [
1458
+ /\.main-page[\s]*\{/g,
1459
+ /\.extra-page[\s]*\{/g,
1460
+ /\.edge[\s]*\{/g
1461
+ ],
1462
+ 'new-module-bundle.js': [
1463
+ /BUNDLE_INDEX_PAGE_TYPE['"]+/g,
1464
+ /BUNDLE_ANOTHER_PAGE_TYPE['"]+/g,
1465
+ /BUNDLE_MAIN_PAGE_TYPE['"]+/g
1466
+ ],
1467
+ 'new-bundle.css': [
1468
+ /\.index-page-type[\s]*\{/g,
1469
+ /\.main-page-type[\s]*\{/g
1470
+ ],
1471
+ 'company-module-bundle.js': [
1472
+ /BUNDLE_OVERRIDE_COMPANY['"]+/g
1473
+ ],
1474
+ 'company-bundle.css': [
1475
+ /\.override-company[\s]*\{/g
1476
+ ],
1477
+ 'newcompany-module-bundle.js': [
1478
+ /BUNDLE_COMPANY['"]+/g
1479
+ ],
1480
+ 'newcompany-bundle.css': [
1481
+ /\.company[\s]*\{/g
1482
+ ],
1483
+ 'widget-module-bundle.js': [
1484
+ /BUNDLE_WIDGET_EXTRA2['"]+/g
1485
+ ]
1486
+ };
1487
+ const bundleNoDuplicateContents = {
1488
+ 'public-module-bundle.js': [
1489
+ /BUNDLE_COMPANY['"]+/g,
1490
+ /BUNDLE_WIDGET_EXTRA2['"]+/g,
1491
+ /BUNDLE_OVERRIDE_COMPANY['"]+/g,
1492
+ /BUNDLE_MAIN_PAGE_TYPE['"]+/g,
1493
+ /BUNDLE_ANOTHER_PAGE_TYPE['"]+/g,
1494
+ /BUNDLE_INDEX_PAGE_TYPE['"]+/g
1495
+ ],
1496
+ 'public-bundle.css': [
1497
+ /\.main-page-type[\s]*\{/g,
1498
+ /\.override-company[\s]*\{/g,
1499
+ /\.company[\s]*\{/g
1500
+ ]
1501
+ };
1502
+ const nonExistingBundleNames = [
1503
+ 'main-module-bundle.js',
1504
+ 'extra-module-bundle.js',
1505
+ 'extra2-module-bundle.js',
1506
+ 'edge-module-bundle.js',
1507
+ 'main-bundle.css',
1508
+ 'extra-bundle.css',
1509
+ 'edge-bundle.css'
1510
+ ];
1511
+
1512
+ process.env.NODE_ENV = 'production';
1513
+ await deleteBuiltFolders(publicFolderPath, true);
1514
+ await apos.asset.tasks.build.task();
1515
+ {
1516
+ const [ releaseId ] = await fs.readdir(getPath('releases'));
1517
+ const releasePath = `releases/${releaseId}/default/`;
1518
+ await checkBundlesExists(releasePath, existingBundleNames);
1519
+ checkBundlesContents(releasePath, bundleContents);
1520
+ checkBundlesContents(releasePath, bundleNoDuplicateContents, true);
1521
+ for (const file of nonExistingBundleNames) {
1522
+ assert.throws(() => fs.readFileSync(releasePath + file), {
1523
+ code: 'ENOENT'
1524
+ }, file);
1525
+ }
1526
+ }
1527
+
1528
+ process.env.NODE_ENV = 'development';
1529
+ await deleteBuiltFolders(publicFolderPath, true);
1530
+ await apos.asset.tasks.build.task();
1531
+ {
1532
+ const releasePath = getPath('default/');
1533
+ await checkBundlesExists('default/', existingBundleNames);
1534
+ checkBundlesContents('default/', bundleContents);
1535
+ checkBundlesContents('default/', bundleNoDuplicateContents, true);
1536
+ for (const file of nonExistingBundleNames) {
1537
+ assert.throws(() => fs.readFileSync(releasePath + file), {
1538
+ code: 'ENOENT'
1539
+ }, file);
1540
+ }
1541
+ }
1542
+ });
1543
+
1544
+ it('should load the right remapped bundles inside the right page', async function () {
1545
+ await t.destroy(apos);
1546
+ apos = await t.create({
1547
+ root: module,
1548
+ modules: {
1549
+ '@company/bundle': {},
1550
+ 'bundle-edge': {},
1551
+ ...modules,
1552
+ '@apostrophecms/asset': {
1553
+ options: {
1554
+ rebundleModules: {
1555
+ 'bundle-page': 'main',
1556
+ 'bundle-page-type': 'new',
1557
+ 'bundle-widget:extra2': 'widget',
1558
+ '@company/bundle:company': 'newcompany'
1559
+ }
1560
+ }
1561
+ }
1562
+ }
1563
+ });
1564
+
1565
+ const { _id: homeId } = await apos.page
1566
+ .find(apos.task.getAnonReq(), { level: 0 })
1567
+ .toObject();
1568
+ const jar = apos.http.jar();
1569
+
1570
+ await apos.doc.db.insertMany(pagesToInsert(homeId));
1571
+
1572
+ const bundlePage = await apos.http.get('/bundle', { jar });
1573
+
1574
+ assert(bundlePage.includes(getStylesheetMarkup('public-bundle')));
1575
+ assert(bundlePage.includes(getStylesheetMarkup('new-bundle')));
1576
+ assert(!bundlePage.includes(getStylesheetMarkup('main-bundle')));
1577
+ assert(!bundlePage.includes(getStylesheetMarkup('extra-bundle')));
1578
+ assert(!bundlePage.includes(getStylesheetMarkup('extra2-bundle')));
1579
+ assert(!bundlePage.includes(getStylesheetMarkup('newcompany-bundle')));
1580
+ assert(!bundlePage.includes(getStylesheetMarkup('company-bundle')));
1581
+
1582
+ assert(bundlePage.includes(getScriptMarkup('public-module-bundle')));
1583
+ assert(bundlePage.includes(getScriptMarkup('new-module-bundle')));
1584
+ assert(bundlePage.includes(getScriptMarkup('widget-module-bundle')));
1585
+ assert(!bundlePage.includes(getScriptMarkup('main-module-bundle')));
1586
+ assert(!bundlePage.includes(getScriptMarkup('extra-module-bundle')));
1587
+ assert(!bundlePage.includes(getScriptMarkup('extra2-module-bundle')));
1588
+ assert(!bundlePage.includes(getStylesheetMarkup('newcompany-bundle')));
1589
+ assert(!bundlePage.includes(getStylesheetMarkup('company-bundle')));
1590
+
1591
+ const childPage = await apos.http.get('/bundle/child', { jar });
1592
+
1593
+ assert(childPage.includes(getStylesheetMarkup('public-bundle')));
1594
+ assert(childPage.includes(getStylesheetMarkup('new-bundle')));
1595
+ assert(!childPage.includes(getStylesheetMarkup('main-bundle')));
1596
+ assert(!childPage.includes(getStylesheetMarkup('extra-bundle')));
1597
+ assert(!childPage.includes(getStylesheetMarkup('extra2-bundle')));
1598
+
1599
+ assert(childPage.includes(getScriptMarkup('public-module-bundle')));
1600
+ assert(childPage.includes(getScriptMarkup('new-module-bundle')));
1601
+ assert(!childPage.includes(getScriptMarkup('widget-module-bundle')));
1602
+ assert(!childPage.includes(getScriptMarkup('main-module-bundle')));
1603
+ assert(!childPage.includes(getScriptMarkup('extra-module-bundle')));
1604
+ assert(!childPage.includes(getScriptMarkup('extra2-module-bundle')));
1605
+ });
1606
+
1607
+ it('should load all the remapped bundles on all pages when the user is logged in', async function () {
1608
+ await t.createAdmin(apos);
1609
+ const jar = await t.getUserJar(apos);
1610
+
1611
+ const homePage = await apos.http.get('/', { jar });
1612
+ assert(homePage.match(/logged in/));
1613
+
1614
+ const bundlePage = await apos.http.get('/bundle', { jar });
1615
+
1616
+ assert(bundlePage.includes(getStylesheetMarkup('apos-bundle')));
1617
+ assert(bundlePage.includes(getStylesheetMarkup('new-bundle')));
1618
+ assert(bundlePage.includes(getStylesheetMarkup('company-bundle')));
1619
+ assert(bundlePage.includes(getStylesheetMarkup('newcompany-bundle')));
1620
+ assert(!bundlePage.includes(getStylesheetMarkup('public-bundle')));
1621
+ assert(!bundlePage.includes(getStylesheetMarkup('main-bundle')));
1622
+ assert(!bundlePage.includes(getStylesheetMarkup('extra-bundle')));
1623
+ assert(!bundlePage.includes(getStylesheetMarkup('extra2-bundle')));
1624
+
1625
+ assert(bundlePage.includes(getScriptMarkup('apos-module-bundle')));
1626
+ assert(bundlePage.includes(getScriptMarkup('new-module-bundle')));
1627
+ assert(bundlePage.includes(getScriptMarkup('widget-module-bundle')));
1628
+ assert(bundlePage.includes(getStylesheetMarkup('newcompany-bundle')));
1629
+ assert(bundlePage.includes(getStylesheetMarkup('company-bundle')));
1630
+ assert(!bundlePage.includes(getScriptMarkup('public-module-bundle')));
1631
+ assert(!bundlePage.includes(getScriptMarkup('main-module-bundle')));
1632
+ assert(!bundlePage.includes(getScriptMarkup('extra-module-bundle')));
1633
+ assert(!bundlePage.includes(getScriptMarkup('extra2-module-bundle')));
1634
+ });
1333
1635
  });
1334
1636
 
1335
1637
  function loadUtils () {
@@ -1346,7 +1648,16 @@ function loadUtils () {
1346
1648
  const getStylesheetMarkup = (file) =>
1347
1649
  `<link href="/apos-frontend/default/${file}.css" rel="stylesheet" />`;
1348
1650
 
1349
- const expectedBundlesNames = [ 'extra-module-bundle.js', 'extra2-module-bundle.js', 'extra-bundle.css' ];
1651
+ const expectedBundlesNames = [
1652
+ 'main-module-bundle.js',
1653
+ 'another-module-bundle.js',
1654
+ 'company-module-bundle.js',
1655
+ 'extra-module-bundle.js',
1656
+ 'extra2-module-bundle.js',
1657
+ 'main-bundle.css',
1658
+ 'company-bundle.css',
1659
+ 'extra-bundle.css'
1660
+ ];
1350
1661
 
1351
1662
  const deleteBuiltFolders = async (publicPath, deleteAposBuild = false) => {
1352
1663
  await fs.remove(publicPath + '/apos-frontend');
@@ -1359,9 +1670,11 @@ function loadUtils () {
1359
1670
 
1360
1671
  const allBundlesAreIncluded = (page) => {
1361
1672
  assert(page.includes(getStylesheetMarkup('apos-bundle')));
1673
+ assert(page.includes(getStylesheetMarkup('main-bundle')));
1362
1674
  assert(page.includes(getStylesheetMarkup('extra-bundle')));
1363
1675
 
1364
1676
  assert(page.includes(getScriptMarkup('apos-module-bundle')));
1677
+ assert(page.includes(getScriptMarkup('main-module-bundle')));
1365
1678
  assert(page.includes(getScriptMarkup('extra-module-bundle')));
1366
1679
  assert(page.includes(getScriptMarkup('extra2-module-bundle')));
1367
1680
  };