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.
- package/CHANGELOG.md +48 -6
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBar.vue +5 -1
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +0 -32
- package/modules/@apostrophecms/asset/index.js +219 -69
- package/modules/@apostrophecms/asset/lib/webpack/utils.js +133 -21
- package/modules/@apostrophecms/doc/index.js +6 -0
- package/modules/@apostrophecms/doc-type/index.js +61 -21
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +15 -0
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +6 -1
- package/modules/@apostrophecms/i18n/i18n/en.json +3 -1
- package/modules/@apostrophecms/i18n/i18n/es.json +1 -0
- package/modules/@apostrophecms/i18n/i18n/fr.json +16 -16
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +1 -0
- package/modules/@apostrophecms/i18n/i18n/sk.json +1 -0
- package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +93 -74
- package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalizeErrors.vue +15 -4
- package/modules/@apostrophecms/image/ui/apos/components/AposImageCropper.vue +5 -7
- package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +2 -2
- package/modules/@apostrophecms/login/index.js +3 -1
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalBreadcrumbs.vue +1 -1
- package/modules/@apostrophecms/oembed-field/ui/apos/components/AposInputOembed.vue +6 -2
- package/modules/@apostrophecms/page/index.js +11 -18
- package/modules/@apostrophecms/page-type/index.js +16 -4
- package/modules/@apostrophecms/piece-type/index.js +1 -16
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +3 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +51 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +2 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +5 -3
- package/modules/@apostrophecms/task/index.js +7 -3
- package/modules/@apostrophecms/ui/ui/apos/components/AposButtonGroup.vue +6 -3
- package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +11 -3
- package/modules/@apostrophecms/ui/ui/apos/components/AposPagerDots.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +14 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposSpinner.vue +7 -7
- package/modules/@apostrophecms/ui/ui/apos/mixins/AposPublishMixin.js +26 -0
- package/package.json +7 -3
- package/test/assets.js +809 -8
- package/test/draft-published.js +167 -0
- package/test/modules/default-page/ui/apos/components/FakeComponent.vue +1 -0
- package/test/modules/default-page/ui/public/index.css +1 -0
- package/test/modules/default-page/ui/public/index.js +1 -0
- package/test/modules/default-page/ui/src/index.js +1 -0
- package/test/modules/default-page/ui/src/index.scss +1 -0
- package/test/pages.js +67 -0
- 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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
876
|
-
|
|
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
|
-
|
|
890
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
(
|
|
932
|
-
|
|
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
|
-
|
|
941
|
-
|
|
942
|
-
|
|
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',
|
|
947
|
-
.on('change',
|
|
948
|
-
.on('unlink',
|
|
949
|
-
.on('addDir',
|
|
950
|
-
.on('unlinkDir',
|
|
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(
|
|
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
|