apostrophe 3.20.0 → 3.21.1-alpha.2022060701

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 (45) hide show
  1. package/CHANGELOG.md +48 -6
  2. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBar.vue +5 -1
  3. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +0 -32
  4. package/modules/@apostrophecms/asset/index.js +219 -69
  5. package/modules/@apostrophecms/asset/lib/webpack/utils.js +133 -21
  6. package/modules/@apostrophecms/doc/index.js +6 -0
  7. package/modules/@apostrophecms/doc-type/index.js +61 -21
  8. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +15 -0
  9. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +6 -1
  10. package/modules/@apostrophecms/i18n/i18n/en.json +3 -1
  11. package/modules/@apostrophecms/i18n/i18n/es.json +1 -0
  12. package/modules/@apostrophecms/i18n/i18n/fr.json +16 -16
  13. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +1 -0
  14. package/modules/@apostrophecms/i18n/i18n/sk.json +1 -0
  15. package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +93 -74
  16. package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalizeErrors.vue +15 -4
  17. package/modules/@apostrophecms/image/ui/apos/components/AposImageCropper.vue +5 -7
  18. package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +2 -2
  19. package/modules/@apostrophecms/login/index.js +3 -1
  20. package/modules/@apostrophecms/modal/ui/apos/components/AposModalBreadcrumbs.vue +1 -1
  21. package/modules/@apostrophecms/oembed-field/ui/apos/components/AposInputOembed.vue +6 -2
  22. package/modules/@apostrophecms/page/index.js +11 -18
  23. package/modules/@apostrophecms/page-type/index.js +16 -4
  24. package/modules/@apostrophecms/piece-type/index.js +1 -16
  25. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +3 -3
  26. package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +51 -0
  27. package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +2 -0
  28. package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +5 -3
  29. package/modules/@apostrophecms/task/index.js +7 -3
  30. package/modules/@apostrophecms/ui/ui/apos/components/AposButtonGroup.vue +6 -3
  31. package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +11 -3
  32. package/modules/@apostrophecms/ui/ui/apos/components/AposPagerDots.vue +1 -1
  33. package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +14 -2
  34. package/modules/@apostrophecms/ui/ui/apos/components/AposSpinner.vue +7 -7
  35. package/modules/@apostrophecms/ui/ui/apos/mixins/AposPublishMixin.js +26 -0
  36. package/package.json +7 -3
  37. package/test/assets.js +809 -8
  38. package/test/draft-published.js +167 -0
  39. package/test/modules/default-page/ui/apos/components/FakeComponent.vue +1 -0
  40. package/test/modules/default-page/ui/public/index.css +1 -0
  41. package/test/modules/default-page/ui/public/index.js +1 -0
  42. package/test/modules/default-page/ui/src/index.js +1 -0
  43. package/test/modules/default-page/ui/src/index.scss +1 -0
  44. package/test/pages.js +67 -0
  45. package/test/pieces.js +64 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # Changelog
2
2
 
3
+ ## UNRELEASED
4
+
5
+ ### Adds
6
+
7
+ * Possibility to pass options to webpack extensions from any module.
8
+
9
+ ### Fixes
10
+
11
+ * Fix a Webpack cache issue leading to modules symlinked in `node_modules` not being rebuilt.
12
+ * Fixes login maximum attempts error message that wasn't showing the plural when lockoutMinutes is more than 1.
13
+ * Fixes the text color of the current array item's slat label in the array editor modal
14
+ * Fixes the maximum width of an array item's slat label so as to not obscure the Remove button in narrow viewports
15
+
16
+ ### Changes
17
+
18
+ * If an array field's titleField option is set to a select field, use the selected option's label as the slat label rather it's value.
19
+ * Disable the slat controls of the attachment component while uploading.
20
+ * Fixes bug when re-attaching the same file won't trigger an upload.
21
+ * AposSlat now fully respects the disabled state.
22
+
23
+ ## 3.21.1 (2022-06-04)
24
+
25
+ ### Fixes
26
+
27
+ * Work around backwards compatibility break in `sass` module by pinning to `sass` `1.50.x` while we investigate. If you saw the error `RangeError: Invalid value: Not in inclusive range 0..145: -1` you can now fix that by upgrading with `npm update`. If it does not immediately clear up the issue in development, try `node app @apostrophecms/asset:clear-cache`.
28
+
29
+ ## 3.21.0 (2022-05-25)
30
+
31
+ ### Adds
32
+
33
+ * Trigger only the relevant build when in a watch mode (development). The build paths should not contain comma (`,`).
34
+ * Adds an `unpublish` method, available for any doc-type.
35
+ An _Unpublish_ option has also been added to the context menu of the modal when editing a piece or a page.
36
+
37
+ ### Fixes
38
+
39
+ * Vue files not being parsed when running eslint through command line, fixes all lint errors in vue files.
40
+ * Fix a bug where some Apostrophe modules symlinked in `node_modules` are not being watched.
41
+ * Recover after webpack build error in watch mode (development only).
42
+ * Fixes an edge case when failing (throw) task invoked via `task.invoke` will result in `apos.isTask()` to always return true due to `apos.argv` not reverted properly.
43
+
44
+ ## 3.20.1 (2022-05-17)
45
+
46
+ ### Fixes
47
+
48
+ * Minor corrections to French translation.
49
+
3
50
  ## 3.20.0
4
51
 
5
52
  ### Adds
@@ -14,15 +61,10 @@
14
61
  * Webpack disk cache for better build performance in development and, if appropriately configured, production as well.
15
62
  * In development, Webpack rebuilds the front end without the need to restart the Node.js process, yielding an additional speedup. To get this speedup for existing projects, see the `nodemonConfig` section of the latest `package.json` in [a3-boilerplate](https://github.com/apostrophecms/a3-boilerplate) for the new "ignore" rules you'll need to prevent nodemon from stopping the process and restarting.
16
63
  * Added the new command line task `apostrophecms/asset:clear-cache` for clearing the webpack disk cache. This should be necessary only in rare cases where the configuration has changed in ways Apostrophe can't automatically detect.
64
+ * A separate `publishedLabel` field can be set for any schema field of a page or piece. If present it is displayed instead of `label` if the document has already been published.
17
65
 
18
66
  ### 3.18.1
19
67
 
20
- ### Adds
21
-
22
- * Webpack cache for build performance in development and production mode.
23
- * Add a watcher in development mode to rebuild the assets on changes in the same process.
24
- * Add asset task `apostrophecms/asset:clear-cache` for force clearing the webpack build cache.
25
-
26
68
  ### Fixes
27
69
 
28
70
  * The admin UI now rebuilds properly in a development environment when new npm modules are installed in a multisite project (`apos.rootDir` differs from `apos.npmRootDir`).
@@ -1,5 +1,9 @@
1
1
  <template>
2
- <div data-apos-test="adminBar" class="apos-admin-bar-wrapper" :class="themeClass">
2
+ <div
3
+ data-apos-test="adminBar"
4
+ class="apos-admin-bar-wrapper"
5
+ :class="themeClass"
6
+ >
3
7
  <div class="apos-admin-bar-spacer" ref="spacer" />
4
8
  <nav class="apos-admin-bar" ref="adminBar">
5
9
  <div class="apos-admin-bar__row">
@@ -131,7 +131,6 @@ export default {
131
131
  },
132
132
  async mounted() {
133
133
  apos.bus.$on('revert-published-to-previous', this.onRevertPublishedToPrevious);
134
- apos.bus.$on('unpublish', this.onUnpublish);
135
134
  apos.bus.$on('set-context', this.onSetContext);
136
135
  apos.bus.$on('push-context', this.onPushContext);
137
136
  apos.bus.$on('pop-context', this.onPopContext);
@@ -598,37 +597,6 @@ export default {
598
597
  });
599
598
  }
600
599
  },
601
- async onUnpublish(data) {
602
- try {
603
- await apos.http.post(`${data.action}/${data._id}/unpublish`, {
604
- body: {},
605
- busy: true
606
- });
607
- apos.notify('apostrophe:noLongerPublished', {
608
- type: 'success',
609
- dismiss: true
610
- });
611
- // This handler covers the modals too, so make sure it's
612
- // for the context document before altering any admin bar state
613
- // because of it
614
- if (data._id.replace(/:.*$/, '') === (this.context._id.replace(/:.*$/, ''))) {
615
- this.context = {
616
- ...this.context,
617
- modified: true,
618
- lastPublishedAt: null
619
- };
620
- // No refresh is needed here because we're still in draft mode
621
- // looking at the draft mode, and the thing that changed is the
622
- // published mode
623
- }
624
- } catch (e) {
625
- await apos.alert({
626
- heading: this.$t('apostrophe:error'),
627
- description: e.message || this.$t('apostrophe:errorWhileUnpublishing'),
628
- localize: false
629
- });
630
- }
631
- },
632
600
  async undo() {
633
601
  this.undone.push(this.patchesSinceLoaded.pop());
634
602
  await this.refreshAfterHistoryChange('apostrophe:undoFailed');
@@ -16,7 +16,8 @@ const {
16
16
  getWebpackExtensions,
17
17
  fillExtraBundles,
18
18
  getBundlesNames,
19
- writeBundlesImportFiles
19
+ writeBundlesImportFiles,
20
+ findNodeModulesSymlinks
20
21
  } = require('./lib/webpack/utils');
21
22
 
22
23
  module.exports = {
@@ -48,13 +49,16 @@ module.exports = {
48
49
  self.configureBuilds();
49
50
  self.initUploadfs();
50
51
 
51
- const { extensions, verifiedBundles } = await getWebpackExtensions({
52
+ const {
53
+ extensions = {}, extensionOptions = {}, verifiedBundles = {}
54
+ } = await getWebpackExtensions({
52
55
  getMetadata: self.apos.synth.getMetadata,
53
56
  modulesToInstantiate: self.apos.modulesToBeInstantiated()
54
57
  });
55
58
 
56
59
  self.extraBundles = fillExtraBundles(verifiedBundles);
57
60
  self.webpackExtensions = extensions;
61
+ self.webpackExtensionOptions = extensionOptions;
58
62
  self.verifiedBundles = verifiedBundles;
59
63
  self.buildWatcherEnable = process.env.APOS_ASSET_WATCH !== '0' && self.options.watch !== false;
60
64
  self.buildWatcherDebounceMs = parseInt(self.options.watchDebounceMs || 1000, 10);
@@ -116,7 +120,7 @@ module.exports = {
116
120
  build: {
117
121
  usage: 'Build Apostrophe frontend CSS and JS bundles',
118
122
  afterModuleInit: true,
119
- async task(argv) {
123
+ async task(argv = {}) {
120
124
  // The lock could become huge, cache it, see computeCacheMeta()
121
125
  let packageLockContentCached;
122
126
  const req = self.apos.task.getReq();
@@ -124,6 +128,19 @@ module.exports = {
124
128
  const buildDir = `${self.apos.rootDir}/apos-build/${namespace}`;
125
129
  const bundleDir = `${self.apos.rootDir}/public/apos-frontend/${namespace}`;
126
130
  const modulesToInstantiate = self.apos.modulesToBeInstantiated();
131
+ const symLinkModules = await findNodeModulesSymlinks(self.apos.npmRootDir);
132
+ // Make it clear if builds should detect changes
133
+ const detectChanges = typeof argv.changes === 'string';
134
+ // Remove invalid changes. `argv.changes` is a comma separated list of relative
135
+ // to `apos.rootDir` files or folders
136
+ const sourceChanges = detectChanges
137
+ ? filterValidChanges(
138
+ argv.changes.split(',').map(p => p.trim()),
139
+ Object.keys(self.apos.modules)
140
+ )
141
+ : [];
142
+ // Keep track of the executed builds
143
+ const buildsExecuted = [];
127
144
 
128
145
  // Don't clutter up with previous builds.
129
146
  await fs.remove(buildDir);
@@ -136,9 +153,12 @@ module.exports = {
136
153
  await moduleOverrides(`${bundleDir}/modules`, 'public');
137
154
 
138
155
  for (const [ name, options ] of Object.entries(self.builds)) {
139
- // If the option is not present always rebuild everything
156
+ // If the option is not present always rebuild everything...
140
157
  let rebuild = argv && !argv['check-apos-build'];
141
- if (!rebuild) {
158
+ // ...but only when not in a watcher mode
159
+ if (detectChanges) {
160
+ rebuild = shouldRebuildFor(name, options, sourceChanges);
161
+ } else if (!rebuild) {
142
162
  let checkTimestamp = false;
143
163
 
144
164
  // Only builds contributing to the apos admin UI (currently just "apos")
@@ -180,9 +200,21 @@ module.exports = {
180
200
  name,
181
201
  options
182
202
  });
203
+ buildsExecuted.push(name);
183
204
  }
184
205
  }
185
206
 
207
+ // No need of deploy if in a watcher mode.
208
+ // Also we return an array of build names that
209
+ // have been triggered - this is required by the watcher
210
+ // so that page refresh is issued only when needed.
211
+ if (detectChanges) {
212
+ // merge the scenes that have been built
213
+ const scenes = [ ...new Set(buildsExecuted.map(name => self.builds[name].scenes).flat()) ];
214
+ merge(scenes);
215
+ return buildsExecuted;
216
+ }
217
+
186
218
  // Discover the set of unique asset scenes that exist (currently
187
219
  // just `public` and `apos`) by examining those specified as
188
220
  // targets for the various builds
@@ -325,9 +357,16 @@ module.exports = {
325
357
  ? webpackMerge(webpackInstanceConfig, ...Object.values(self.webpackExtensions))
326
358
  : webpackInstanceConfig;
327
359
 
328
- // Inject the cache location at the end - we need the merged
329
- const cacheMeta = await computeCacheMeta(name, webpackInstanceConfigMerged);
360
+ // Inject the cache location at the end - we need the merged config
361
+ const cacheMeta = await computeCacheMeta(name, webpackInstanceConfigMerged, symLinkModules);
330
362
  webpackInstanceConfigMerged.cache.cacheLocation = cacheMeta.location;
363
+ // Exclude symlinked modules from the cache managedPaths, no other way for now
364
+ // https://github.com/webpack/webpack/issues/12112
365
+ if (cacheMeta.managedPathsRegex) {
366
+ webpackInstanceConfigMerged.snapshot = {
367
+ managedPaths: [ cacheMeta.managedPathsRegex ]
368
+ };
369
+ }
331
370
 
332
371
  const result = await webpack(webpackInstanceConfigMerged);
333
372
  await writeCacheMeta(name, cacheMeta);
@@ -688,7 +727,7 @@ module.exports = {
688
727
  // but it can be overridden by an APOS_ASSET_CACHE environment.
689
728
  // In order to compute an accurate hash, this helper needs
690
729
  // the final, merged webpack configuration.
691
- async function computeCacheMeta(name, webpackConfig) {
730
+ async function computeCacheMeta(name, webpackConfig, symLinkModules) {
692
731
  const cacheBase = self.getCacheBasePath();
693
732
 
694
733
  if (!packageLockContentCached) {
@@ -731,10 +770,22 @@ module.exports = {
731
770
  );
732
771
  const location = path.resolve(cacheBase, hash);
733
772
 
773
+ // Retrieve symlinkModules and convert them to managedPaths regex rule
774
+ let managedPathsRegex;
775
+ if (symLinkModules.length > 0) {
776
+ const regex = symLinkModules
777
+ .map(m => self.apos.util.regExpQuote(m))
778
+ .join('|');
779
+ managedPathsRegex = new RegExp(
780
+ '^(.+?[\\/]node_modules)[\\/]((?!' + regex + ')).*[\\/]*'
781
+ );
782
+ }
783
+
734
784
  return {
735
785
  base: cacheBase,
736
786
  hash,
737
- location
787
+ location,
788
+ managedPathsRegex
738
789
  };
739
790
  }
740
791
 
@@ -755,6 +806,36 @@ module.exports = {
755
806
  // Build probably failed, path is missing, ignore
756
807
  }
757
808
  }
809
+
810
+ // Given a set of changes, leave only those that belong to an active
811
+ // Apostrophe module. This would avoid unnecessary builds for non-active
812
+ // watched files (e.g. in multi instance mode).
813
+ // It's an expensive brute force O(n^2), so we do it once for all builds
814
+ // and we rely on the fact that mass changes happen rarely.
815
+ function filterValidChanges(all, modules) {
816
+ return all.filter(c => {
817
+ for (const module of modules) {
818
+ if (c.includes(module)) {
819
+ return true;
820
+ }
821
+ }
822
+ return false;
823
+ });
824
+ }
825
+
826
+ // Detect if a build should be executed based on the changed
827
+ // paths. This function is invoked only when the appropriate `argv.changes`
828
+ // is passed to the task.
829
+ function shouldRebuildFor(buildName, buildOptions, changes) {
830
+ const name = buildOptions.source || buildName;
831
+ const id = `/ui/${name}/`;
832
+ for (const change of changes) {
833
+ if (change.includes(id)) {
834
+ return true;
835
+ }
836
+ }
837
+ return false;
838
+ }
758
839
  }
759
840
  },
760
841
 
@@ -872,8 +953,35 @@ module.exports = {
872
953
  return process.env.APOS_ASSET_CACHE ||
873
954
  path.join(self.apos.rootDir, 'data/temp/webpack-cache');
874
955
  },
875
- // Run build task automatically when appropriate
876
- async autorunUiBuildTask() {
956
+
957
+ // Override to set externally a build watcher (a `chokidar` instance).
958
+ // This method will be invoked only if/when needed.
959
+ // Example:
960
+ // ```js
961
+ // registerBuildWatcher() {
962
+ // self.buildWatcher = chokidar.watch(pathsToWatch, {
963
+ // cwd: self.apos.rootDir,
964
+ // ignoreInitial: true
965
+ // });
966
+ // }
967
+ // ```
968
+ registerBuildWatcher() {
969
+ self.buildWatcher = null;
970
+ },
971
+
972
+ // Run build task automatically when appropriate.
973
+ // If `changes` is provided (array of modified files/folders, relative
974
+ // to the application root), this method will return the result of the
975
+ // build task (array of builds that have been triggered by the changes).
976
+ // If `changes` is not provided (falsy value), a boolean will be returned,
977
+ // indicating if the build task has been invoked or not.
978
+ // IMPORTANT: Be cautious when changing the return type behavior.
979
+ // The build watcher initialization (event triggered) depends on a Boolean value,
980
+ // and the rebuild handler (triggered by the build watcher on
981
+ // detected change) depends on an Array value.
982
+ async autorunUiBuildTask(changes) {
983
+ let result = changes ? [] : false;
984
+ let _changes;
877
985
  if (
878
986
  // Do not automatically build the UI if we're starting from a task
879
987
  !self.apos.isTask() &&
@@ -882,26 +990,83 @@ module.exports = {
882
990
  // Or if we've set an app option to skip the auto build
883
991
  self.apos.options.autoBuild !== false
884
992
  ) {
885
-
886
993
  checkModulesWebpackConfig(self.apos.modules, self.apos.task.getReq().t);
887
994
  // If starting up normally, run the build task, checking if we
888
995
  // really need to update the apos build
889
- await self.apos.task.invoke('@apostrophecms/asset:build', {
890
- 'check-apos-build': true
996
+ if (changes) {
997
+ // Important: don't pass empty string, it will cause the task
998
+ // to enter selective build mode and do nothing. Undefined is OK.
999
+ _changes = changes.join(',');
1000
+ }
1001
+ const buildsTriggered = await self.apos.task.invoke('@apostrophecms/asset:build', {
1002
+ 'check-apos-build': true,
1003
+ changes: _changes
1004
+ });
1005
+ result = _changes ? buildsTriggered : true;
1006
+ }
1007
+ return result;
1008
+ },
1009
+
1010
+ // The rebuild handler triggered (debounced) by the build watcher.
1011
+ // The `changes` argument is a reference to a central pool of changes.
1012
+ // It contains relative to the application root file paths.
1013
+ // The pool is being exhausted before triggering the build task.
1014
+ // Array manipulations are sync only, so no race condition is possible.
1015
+ // `rebuildCallback` is used for testing and debug purposes. It allows
1016
+ // access to the changes processed by the build task,
1017
+ // the new restartId and the build names that the changes have triggered.
1018
+ // This handler has no watcher dependencies and it's safe to be invoked
1019
+ // by any code base.
1020
+ async rebuild(changes, rebuildCallback) {
1021
+ rebuildCallback = typeof rebuildCallback === 'function'
1022
+ ? rebuildCallback
1023
+ : () => {};
1024
+ const result = {
1025
+ changes: [],
1026
+ restartId: self.restartId,
1027
+ builds: []
1028
+ };
1029
+
1030
+ const pulledChanges = [];
1031
+ let change = changes.pop();
1032
+ while (change) {
1033
+ pulledChanges.push(change);
1034
+ change = changes.pop();
1035
+ }
1036
+ // No changes - should never happen.
1037
+ if (pulledChanges.length === 0) {
1038
+ return rebuildCallback(result);
1039
+ }
1040
+ try {
1041
+ const buildsTriggered = await self.autorunUiBuildTask(pulledChanges);
1042
+ if (buildsTriggered.length > 0) {
1043
+ self.restartId = self.apos.util.generateId();
1044
+ }
1045
+ return rebuildCallback({
1046
+ changes: pulledChanges,
1047
+ restartId: self.restartId,
1048
+ builds: buildsTriggered
891
1049
  });
892
- return true;
1050
+ } catch (e) {
1051
+ // The build error is detailed enough, no message
1052
+ // on our end.
1053
+ self.apos.util.error(e);
893
1054
  }
894
- return false;
1055
+
1056
+ rebuildCallback(result);
895
1057
  },
1058
+
896
1059
  // Start watching assets from `modules/` and
897
1060
  // every symlinked package found in `node_modules/`.
898
1061
  // `rebuildCallback` is invoked with queue length argument
899
1062
  // on actual build attempt only.
900
1063
  // It's there mainly for testing and debugging purposes.
901
1064
  async watchUiAndRebuild(rebuildCallback) {
902
- if (!self.buildWatcherEnable || self.buildWatcher) {
1065
+ if (!self.buildWatcherEnable) {
903
1066
  return;
904
1067
  }
1068
+ // Allow custom watcher registration
1069
+ self.registerBuildWatcher();
905
1070
  const rootDir = self.apos.rootDir;
906
1071
  // chokidar may invoke ready event multiple times,
907
1072
  // we want one "watch enabled" message.
@@ -916,51 +1081,55 @@ module.exports = {
916
1081
  const queue = [];
917
1082
  let queueLength = 0;
918
1083
  let queueRunning = false;
1084
+ // The pool of changes - it HAS to be exhausted by the rebuild handler
1085
+ // or we'll end up with a memory leak in development.
1086
+ const changesPool = [];
919
1087
 
920
1088
  const debounceRebuild = _.debounce(chain, self.buildWatcherDebounceMs, {
921
1089
  leading: false,
922
1090
  trailing: true
923
1091
  });
1092
+ const addChangeAndDebounceRebuild = (fileOrDir) => {
1093
+ changesPool.push(fileOrDir);
1094
+ return debounceRebuild();
1095
+ };
924
1096
 
925
- const symLinkModules = await findSymlinks();
926
- const watchDirs = [
927
- './modules/**/ui/apos/**',
928
- './modules/**/ui/src/**',
929
- './modules/**/ui/public/**',
930
- ...symLinkModules.reduce(
931
- (prev, m) => [
932
- ...prev,
1097
+ if (!self.buildWatcher) {
1098
+ const symLinkModules = await findNodeModulesSymlinks(rootDir);
1099
+ const watchDirs = [
1100
+ './modules/**/ui/apos/**',
1101
+ './modules/**/ui/src/**',
1102
+ './modules/**/ui/public/**',
1103
+ ...symLinkModules.reduce(
1104
+ (prev, m) => [
1105
+ ...prev,
1106
+ `./node_modules/${m}/ui/apos/**`,
1107
+ `./node_modules/${m}/ui/src/**`,
1108
+ `./node_modules/${m}/ui/public/**`,
933
1109
  `./node_modules/${m}/modules/**/ui/apos/**`,
934
1110
  `./node_modules/${m}/modules/**/ui/src/**`,
935
1111
  `./node_modules/${m}/modules/**/ui/public/**`
936
- ],
937
- []
938
- )
939
- ];
940
- self.buildWatcher = chokidar.watch(watchDirs, {
941
- cwd: rootDir,
942
- ignoreInitial: true
943
- });
1112
+ ],
1113
+ []
1114
+ )
1115
+ ];
1116
+ self.buildWatcher = chokidar.watch(watchDirs, {
1117
+ cwd: rootDir,
1118
+ ignoreInitial: true
1119
+ });
1120
+ }
944
1121
 
945
1122
  self.buildWatcher
946
- .on('add', debounceRebuild)
947
- .on('change', debounceRebuild)
948
- .on('unlink', debounceRebuild)
949
- .on('addDir', debounceRebuild)
950
- .on('unlinkDir', debounceRebuild)
1123
+ .on('add', addChangeAndDebounceRebuild)
1124
+ .on('change', addChangeAndDebounceRebuild)
1125
+ .on('unlink', addChangeAndDebounceRebuild)
1126
+ .on('addDir', addChangeAndDebounceRebuild)
1127
+ .on('unlinkDir', addChangeAndDebounceRebuild)
951
1128
  .on('error', e => error(`Watcher error: ${e}`))
952
1129
  .on('ready', () => logOnce(
953
1130
  self.apos.task.getReq().t('apostrophe:assetBuildWatchStarted')
954
1131
  ));
955
1132
 
956
- async function rebuild() {
957
- await self.autorunUiBuildTask();
958
- self.restartId = self.apos.util.generateId();
959
- if (typeof rebuildCallback === 'function') {
960
- rebuildCallback(queueLength);
961
- };
962
- };
963
-
964
1133
  // Simple, capped, self-exhausting queue implementation.
965
1134
  function enqueue(fn) {
966
1135
  if (queueLength === 2) {
@@ -975,37 +1144,18 @@ module.exports = {
975
1144
  return;
976
1145
  }
977
1146
  queueRunning = true;
978
- await queue.pop()();
1147
+ await queue.pop()(changesPool, rebuildCallback);
979
1148
  queueLength--;
980
1149
  await dequeue();
981
1150
  }
982
- async function chain(f) {
983
- enqueue(rebuild);
1151
+ async function chain() {
1152
+ enqueue(self.rebuild);
984
1153
  if (!queueRunning) {
985
1154
  await dequeue();
986
1155
  }
987
1156
  }
988
-
989
- // Find all symlinks in node modules.
990
- // This would find both `module-name` and `@company/module-name`
991
- // package symlinks
992
- async function findSymlinks(sub = '') {
993
- let result = [];
994
- const handle = await fs.promises.opendir(path.join(rootDir, 'node_modules', sub));
995
- let mod = await handle.read();
996
- while (mod) {
997
- if (mod.isSymbolicLink()) {
998
- result.push(sub + mod.name);
999
- } else if (!sub && mod.name.startsWith('@')) {
1000
- const dres = await findSymlinks(`${mod.name}/`);
1001
- result = [ ...result, ...dres ];
1002
- }
1003
- mod = await handle.read();
1004
- }
1005
- await handle.close();
1006
- return result;
1007
- }
1008
1157
  },
1158
+
1009
1159
  // An implementation method that you should not need to call.
1010
1160
  // Sets a predetermined configuration for the frontend builds.
1011
1161
  // If you are trying to enable IE11 support for ui/src, use the