apostrophe 3.5.0 → 3.8.1
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/.eslintrc +4 -0
- package/.scratch.md +2 -0
- package/CHANGELOG.md +96 -3
- package/README.md +1 -1
- package/index.js +108 -3
- package/lib/moog-require.js +23 -0
- package/lib/moog.js +1 -0
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +30 -14
- package/modules/@apostrophecms/area/lib/custom-tags/area.js +1 -1
- package/modules/@apostrophecms/area/lib/custom-tags/widget.js +1 -1
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +2 -2
- package/modules/@apostrophecms/asset/index.js +77 -13
- package/modules/@apostrophecms/attachment/index.js +1 -0
- package/modules/@apostrophecms/db/index.js +5 -6
- package/modules/@apostrophecms/doc/index.js +2 -0
- package/modules/@apostrophecms/doc-type/index.js +39 -16
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +13 -1
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +3 -0
- package/modules/@apostrophecms/i18n/i18n/en.json +19 -6
- package/modules/@apostrophecms/i18n/i18n/es.json +382 -0
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +379 -0
- package/modules/@apostrophecms/i18n/i18n/sk.json +380 -0
- package/modules/@apostrophecms/i18n/index.js +10 -1
- package/modules/@apostrophecms/image/index.js +2 -1
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +6 -3
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +2 -1
- package/modules/@apostrophecms/image-widget/index.js +2 -1
- package/modules/@apostrophecms/image-widget/views/widget.html +12 -2
- package/modules/@apostrophecms/job/index.js +164 -212
- package/modules/@apostrophecms/login/index.js +1 -16
- package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +5 -0
- package/modules/@apostrophecms/migration/index.js +1 -1
- package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +151 -61
- package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +6 -2
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +8 -6
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +12 -15
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +6 -0
- package/modules/@apostrophecms/notification/index.js +116 -8
- package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +89 -11
- package/modules/@apostrophecms/notification/ui/apos/components/TheAposNotifications.vue +1 -1
- package/modules/@apostrophecms/page/index.js +37 -30
- package/modules/@apostrophecms/permission/index.js +1 -1
- package/modules/@apostrophecms/permission/ui/apos/components/AposInputRole.vue +4 -2
- package/modules/@apostrophecms/piece-type/index.js +178 -61
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +179 -47
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +1 -3
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +138 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +35 -6
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Classes.js +1 -3
- package/modules/@apostrophecms/schema/index.js +97 -20
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +4 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +11 -160
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +24 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +24 -6
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +0 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +0 -7
- package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +25 -3
- package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +10 -2
- package/modules/@apostrophecms/task/index.js +2 -2
- package/modules/@apostrophecms/template/index.js +63 -36
- package/modules/@apostrophecms/template/lib/custom-tags/component.js +1 -1
- package/modules/@apostrophecms/template/lib/custom-tags/render.js +6 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +5 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +205 -0
- package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +16 -2
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +4 -3
- package/modules/@apostrophecms/util/index.js +2 -2
- package/modules/@apostrophecms/util/ui/src/http.js +12 -8
- package/modules/@apostrophecms/util/ui/src/util.js +15 -0
- package/modules/@apostrophecms/widget-type/index.js +1 -1
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +1 -0
- package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +15 -7
- package/package.json +3 -3
- package/test/extra_node_modules/improve-global/index.js +7 -0
- package/test/extra_node_modules/improve-piece-type/index.js +7 -0
- package/test/improve-overrides.js +30 -0
- package/test/job.js +224 -0
- package/test/modules/@apostrophecms/global/index.js +8 -0
- package/test/modules/fragment-all/views/aux-test.html +7 -0
- package/test/modules/fragment-all/views/fragment.html +5 -0
- package/test/package.json +5 -4
- package/test/pieces.js +34 -0
- package/test/reverse-relationship.js +170 -0
- package/test/templates.js +7 -1
- package/test-lib/test.js +23 -12
- package/test-lib/util.js +33 -0
package/.eslintrc
CHANGED
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
"no-var": "error",
|
|
11
11
|
"no-console": 0,
|
|
12
12
|
"multiline-ternary": "off",
|
|
13
|
+
"no-unused-vars": [
|
|
14
|
+
"error",
|
|
15
|
+
{ "varsIgnorePattern": "^_.", "args": "none" }
|
|
16
|
+
],
|
|
13
17
|
"vue/no-deprecated-destroyed-lifecycle": 0,
|
|
14
18
|
"vue/v-on-event-hyphenation": 1,
|
|
15
19
|
"vue/custom-event-name-casing": ["warn", "kebab-case"],
|
package/.scratch.md
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,102 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 3.
|
|
3
|
+
## 3.8.1 - 2021-11-23
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
* The search field of the pieces manager modal works properly. Thanks to [Miro Yovchev](https://github.com/myovchev) for pointing out the issue and providing a solution.
|
|
8
|
+
* Fixes a bug in `AposRichTextWidgetEditor.vue` when a rich text widget was specifically configured with an empty array as the `styles` option. In that case a new empty rich text widget will initiate with an empty paragraph tag.
|
|
9
|
+
* The`fieldsPresent` method that is used with the `presentFieldsOnly` option in doc-type was broken, looking for properties in strings and wasn't returning anything.
|
|
10
|
+
|
|
11
|
+
## 3.8.0 - 2021-11-15
|
|
12
|
+
|
|
13
|
+
### Adds
|
|
14
|
+
|
|
15
|
+
* Checkboxes for pieces are back, a main checkbox allows to select all page items. When all pieces on a page are checked, a banner where the user can select all pieces appears. A launder for mongo projections has been added.
|
|
16
|
+
* Registered `batchOperations` on a piece-type will now become buttons in the manager batch operations "more menu" (styled as a kebab icon). Batch operations should include a label, `messages` object, and `modalOptions` for the confirmation modal.
|
|
17
|
+
* `batchOperations` can be grouped into a single button with a menu using the `group` cascade subproperty.
|
|
18
|
+
* `batchOperations` can be conditional with an `if` conditional object. This allows developers to pass a single value or an array of values.
|
|
19
|
+
* Piece types can have `utilityOperations` configured as a top-level cascade property. These operations are made available in the piece manager as new buttons.
|
|
20
|
+
* Notifications may now include an `event` property, which the AposNotification component will emit on mount. The `event` property should be set to an object with `name` (the event name) and optionally `data` (data included with the event emission).
|
|
21
|
+
* Adds support for using the attachments query builder in REST API calls via the query string.
|
|
22
|
+
* Adds contextual menu for pieces, any module extending the piece-type one can add actions in this contextual menu.
|
|
23
|
+
* When clicking on a batch operation, it opens a confirmation modal using modal options from the batch operation, it also works for operations in grouped ones. operations name property has been renamed in action to work with AposContextMenu component.
|
|
24
|
+
* Beginning with this release, a module-specific static asset in your project such as `modules/mymodulename/public/images/bg.png` can always be referenced in your `.scss` and `.css` files as `/modules/mymodulename/images/bg.png`, even if assets are actually being deployed to S3, CDNs, etc. Note that `public` and `ui/public` module subdirectories have separate functions. See the documentation for more information.
|
|
25
|
+
* Adds AposFile.vue component to abstract file dropzone UI, uses it in AposInputAttachment, and uses it in the confirmation modal for pieces import.
|
|
26
|
+
* Optionally add `dimensionAttrs` option to image widget, which sets width & height attributes to optimize for Cumulative Layout Shift. Thank you to [Qiao Lin](https://github.com/qclin) for the contribution.
|
|
27
|
+
|
|
28
|
+
### Fixes
|
|
29
|
+
|
|
30
|
+
* The `apos.util.attachmentUrl` method now works correctly. To facilitate that, `apos.uploadsUrl` is now populated browser-side at all times as the frontend logic originally expected. For backwards compatibility `apos.attachment.uploadsUrl` is still populated when logged in.
|
|
31
|
+
* Widget players are now prevented from being played twice by the implementing vue component.
|
|
32
|
+
|
|
33
|
+
### Changes
|
|
34
|
+
* Removes Apostrophe 2 documentation and UI configuration from the `@apostrophecms/job` module. These options were not yet in use for A3.
|
|
35
|
+
* Renames methods and removes unsupported routes in the `@apostrophecms/job` module that were not yet in use. This was not done lightly, but specifically because of the minimal likelihood that they were in use in project code given the lack of UI support.
|
|
36
|
+
* The deprecated `cancel` route was removed and will likely be replaced at a later date.
|
|
37
|
+
* `run` was renamed `runBatch` as its purpose is specifically to run processes on a "batch selected" array of pieces or pages.
|
|
38
|
+
* `runNonBatch` was renamed to `run` as it is the more generic job-running method. It is likely that `runBatch` will eventually be refactored to use this method.
|
|
39
|
+
* The `good` and `bad` methods are renamed `success` and `failure`, respectively. The expected methods used in the `run` method were similarly renamed. They still increment job document properties called `good` and `bad`.
|
|
40
|
+
* Comments out the unused `batchSimpleRoute` methods in the page and piece-type modules to avoid usage before they are fully implemented.
|
|
41
|
+
* Optionally add `dimensionAttrs` option to image widget, which sets width & height attributes to optimize for Cumulative Layout Shift.
|
|
42
|
+
* Temporarily removes `npm audit` from our automated tests because of a sub-dependency of uploadfs that doesn't actually cause a security vulnerability for apostrophe.
|
|
43
|
+
|
|
44
|
+
## 3.7.0 - 2021-10-28
|
|
45
|
+
|
|
46
|
+
### Adds
|
|
47
|
+
|
|
48
|
+
* Schema select field choices can now be populated by a server side function, like an API call. Set the `choices` property to a method name of the calling module. That function should take a single argument of `req`, and return an array of objects with `label` and `value` properties. The function can be async and will be awaited.
|
|
49
|
+
* Apostrophe now has built-in support for the Node.js cluster module. If the `APOS_CLUSTER_PROCESSES` environment variable is set to a number, that number of child processes are forked, sharing the same listening port. If the variable is set to `0`, one process is forked for each CPU core, with a minimum of `2` to provide availability during restarts. If the variable is set to a negative number, that number is added to the number of CPU cores, e.g. `-1` is a good way to reserve one core for MongoDB if it is running on the same server. This is for production use only (`NODE_ENV=production`). If a child process fails it is restarted automatically.
|
|
4
50
|
|
|
5
51
|
### Fixes
|
|
6
52
|
|
|
53
|
+
* Prevents double-escaping interpolated localization strings in the UI.
|
|
54
|
+
* Rich text editor style labels are now run through a localization method to get the translated strings from their l10n keys.
|
|
55
|
+
* Fixes README Node version requirement (Node 12+).
|
|
56
|
+
* The text alignment buttons now work immediately in a new rich text widget. Previously they worked only after manually setting a style or refreshing the page. Thanks to Michelin for their support of this fix.
|
|
57
|
+
* Users can now activate the built-in date and time editing popups of modern browsers when using the `date` and `time` schema field types.
|
|
58
|
+
* Developers can now `require` their project `app.js` file in the Node.js REPL for debugging and inspection. Thanks to [Matthew Francis Brunetti](https://github.com/zenflow).
|
|
59
|
+
* If a static text phrase is unavailable in both the current locale and the default locale, Apostrophe will always fall back to the `en` locale as a last resort, which ensures the admin UI works if it has not been translated.
|
|
60
|
+
* Developers can now `require` their project `app.js` in the Node.js REPL for debugging and inspection
|
|
61
|
+
* Ensure array field items have valid _id prop before storing. Thanks to Thanks to [Matthew Francis Brunetti](https://github.com/zenflow).
|
|
62
|
+
|
|
63
|
+
### Changes
|
|
64
|
+
|
|
65
|
+
* In 3.x, `relationship` fields have an optional `builders` property, which replaces `filters` from 2.x, and within that an optional `project` property, which replaces `projection` from 2.x (to match MongoDB's `cursor.project`). Prior to this release leaving the old syntax in place could lead to severe performance problems due to a lack of projections. Starting with this release the 2.x syntax results in an error at startup to help the developer correct their code.
|
|
66
|
+
* The `className` option from the widget options in a rich text area field is now also applied to the rich text editor itself, for a consistently WYSIWYG appearance when editing and when viewing. Thanks to [Max Mulatz](https://github.com/klappradla) for this contribution.
|
|
67
|
+
* Adds deprecation notes to doc module `afterLoad` events, which are deprecated.
|
|
68
|
+
* Removes unused `afterLogin` method in the login module.
|
|
69
|
+
|
|
70
|
+
## 3.6.0 - 2021-10-13
|
|
71
|
+
|
|
72
|
+
### Adds
|
|
73
|
+
|
|
74
|
+
* The `context-editing` apostrophe admin UI bus event can now take a boolean parameter, explicitly indicating whether the user is actively typing or performing a similar active manipulation of controls right now. If a boolean parameter is not passed, the existing 1100-millisecond debounced timeout is used.
|
|
75
|
+
* Adds 'no-search' modifier to relationship fields as a UI simplification option.
|
|
76
|
+
* Fields can now have their own `modifiers` array. This is combined with the schema modifiers, allowing for finer grained control of field rendering.
|
|
77
|
+
* Adds a Slovak localization file. Activate the `sk` locale to use this. Many thanks to [Michael Huna](https://github.com/Miselrkba) for the contribution.
|
|
78
|
+
* Adds a Spanish localization file. Activate the `es` locale to use this. Many thanks to [Eugenio Gonzalez](https://github.com/egonzalezg9) for the contribution.
|
|
79
|
+
* Adds a Brazilian Portuguese localization file. Activate the `pt-BR` locale to use this. Many thanks to [Pietro Rutzen](https://github.com/pietro-rutzen) for the contribution.
|
|
80
|
+
|
|
81
|
+
### Fixes
|
|
82
|
+
|
|
83
|
+
* Fixed missing translation for "New Piece" option on the "more" menu of the piece manager, seen when using it as a chooser.
|
|
84
|
+
* Piece types with relationships to multiple other piece types may now be configured in any order, relative to the other piece types. This sometimes appeared to be a bug in reverse relationships.
|
|
85
|
+
* Code at the project level now overrides code found in modules that use `improve` for the same module name. For example, options set by the `@apostrophecms/seo-global` improvement that ships with `@apostrophecms/seo` can now be overridden at project level by `/modules/@apostrophecms/global/index.js` in the way one would expect.
|
|
86
|
+
* Array input component edit button label is now propertly localized.
|
|
87
|
+
* A memory leak on each request has been fixed, and performance improved, by avoiding the use of new Nunjucks environments for each request. Thanks to Miro Yovchev for pointing out the leak.
|
|
88
|
+
* Fragments now have access to `__t()`, `getOptions` and other features passed to regular templates.
|
|
89
|
+
* Fixes field group cascade merging, using the original group label if none is given in the new field group configuration.
|
|
90
|
+
* If a field is conditional (using an `if` option), is required, but the condition has not been met, it no longer throws a validation error.
|
|
91
|
+
* Passing `busy: true` to `apos.http.post` and related methods no longer produces an error if invoked when logged out, however note that there will likely never be a UI for this when logged out, so indicate busy state in your own way.
|
|
92
|
+
* Bugs in document modification detection have been fixed. These bugs caused edge cases where modifications were not detected and the "Update" button did not appear, and could cause false positives as well.
|
|
93
|
+
|
|
94
|
+
### Changes
|
|
95
|
+
|
|
96
|
+
* No longer logs a warning about no users if `testModule` is true on the app.
|
|
97
|
+
|
|
98
|
+
## 3.5.0 - 2021-09-23
|
|
99
|
+
|
|
7
100
|
* Pinned dependency on `vue-material-design-icons` to fix `apos-build.js` build error in production.
|
|
8
101
|
* The file size of uploaded media is visible again when selected in the editor, and media information such as upload date, dimensions and file size is now properly localized.
|
|
9
102
|
* Fixes moog error messages to reflect the recommended pattern of customization functions only taking `self` as an argument.
|
|
@@ -18,6 +111,7 @@
|
|
|
18
111
|
### Changes
|
|
19
112
|
|
|
20
113
|
* Cascade grouping (e.g., grouping fields) will now concatenate a group's field name array with the field name array of an existing group of the same name. Put simply, if a new piece module adds their custom fields to a `basics` group, that field will be added to the default `basics` group fields. Previously the new group would have replaced the old, leaving inherited fields in the "Ungrouped" section.
|
|
114
|
+
* AposButton's `block` modifier now less login-specific
|
|
21
115
|
|
|
22
116
|
### Adds
|
|
23
117
|
|
|
@@ -133,8 +227,7 @@ No changes. Publishing to correctly mark the latest 3.x release as "latest" in n
|
|
|
133
227
|
can include the widget on the clipboard, a special Clipboard widget will appear in area's Add UI. This works across pages as well.
|
|
134
228
|
|
|
135
229
|
### Changes
|
|
136
|
-
* Apostrophe's Global's UI (the @apostrophecms/global singleton has moved from the admin bar's content controls to
|
|
137
|
-
the admin utility tray under a cog icon.
|
|
230
|
+
* Apostrophe's Global's UI (the @apostrophecms/global singleton has moved from the admin bar's content controls to the admin utility tray under a cog icon.
|
|
138
231
|
* The context bar's document Edit button, which was a cog icon, has been rolled into the doc's context menu.
|
|
139
232
|
|
|
140
233
|
## 3.1.3 - 2021-07-16
|
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ We recommend installing the following with [Homebrew](https://brew.sh/) on macOS
|
|
|
43
43
|
|
|
44
44
|
| Software | Minimum Version | Notes
|
|
45
45
|
| ------------- | ------------- | -----
|
|
46
|
-
| Node.js |
|
|
46
|
+
| Node.js | 12.x | Or better
|
|
47
47
|
| npm | 6.x | Or better
|
|
48
48
|
| MongoDB | 3.6 | Or better
|
|
49
49
|
| Imagemagick | Any | Faster image uploads, GIF support (optional)
|
package/index.js
CHANGED
|
@@ -2,11 +2,43 @@ const path = require('path');
|
|
|
2
2
|
const _ = require('lodash');
|
|
3
3
|
const argv = require('boring')({ end: true });
|
|
4
4
|
const fs = require('fs');
|
|
5
|
+
const { stripIndent } = require('common-tags');
|
|
6
|
+
const cluster = require('cluster');
|
|
7
|
+
const { cpus } = require('os');
|
|
8
|
+
const process = require('process');
|
|
5
9
|
const npmResolve = require('resolve');
|
|
10
|
+
|
|
6
11
|
let defaults = require('./defaults.js');
|
|
7
|
-
const { stripIndent } = require('common-tags');
|
|
8
12
|
|
|
9
|
-
//
|
|
13
|
+
// ## Top-level options
|
|
14
|
+
//
|
|
15
|
+
// `cluster`
|
|
16
|
+
//
|
|
17
|
+
// If set to `true`, Apostrophe will spawn as many processes as
|
|
18
|
+
// there are CPU cores on the server, or a minimum of 2, and balance
|
|
19
|
+
// incoming connections among them. This ensures availability while one
|
|
20
|
+
// process is restarting due to a crash and also increases scalability if
|
|
21
|
+
// the server has multiple CPU cores.
|
|
22
|
+
//
|
|
23
|
+
// If set to an object with a `processes` property, that many
|
|
24
|
+
// processes are started. If `processes` is 0 or a negative number,
|
|
25
|
+
// it is added to the number of CPU cores reported by the server.
|
|
26
|
+
// Notably, `-1` can be a good way to reserve one CPU core for MongoDB
|
|
27
|
+
// in a single-server deployment.
|
|
28
|
+
//
|
|
29
|
+
// However when in cluster mode no fewer than 2 processes will be
|
|
30
|
+
// started as there is no availability benefit without at least 2.
|
|
31
|
+
//
|
|
32
|
+
// If a child process exits with a failure status code it will be
|
|
33
|
+
// restarted. However, if it exits in less than 20 seconds after
|
|
34
|
+
// startup there will be a 20 second delay to avoid flooding logs
|
|
35
|
+
// and pinning the CPU.
|
|
36
|
+
//
|
|
37
|
+
// Alternatively the `APOS_CLUSTER_PROCESSES` environment variable
|
|
38
|
+
// can be set to a number, which will effectively set the cluster
|
|
39
|
+
// option to `cluster: { processes: n }`.
|
|
40
|
+
//
|
|
41
|
+
// ## Awaiting the Apostrophe function
|
|
10
42
|
//
|
|
11
43
|
// The apos function is async, but in typical cases you do not
|
|
12
44
|
// need to await it. If you simply call it, Apostrophe will
|
|
@@ -21,8 +53,63 @@ const { stripIndent } = require('common-tags');
|
|
|
21
53
|
// To avoid exiting on errors, pass the `exit: false` option.
|
|
22
54
|
// This can option also can be used to allow awaiting a command line
|
|
23
55
|
// task, as they also normally exit on completion.
|
|
56
|
+
//
|
|
57
|
+
// If `options.cluster` is truthy, the function quickly resolves to
|
|
58
|
+
// `null` in the primary process. In the child process it resolves as
|
|
59
|
+
// documented above.
|
|
24
60
|
|
|
25
61
|
module.exports = async function(options) {
|
|
62
|
+
const guardTime = 20000;
|
|
63
|
+
if (process.env.APOS_CLUSTER_PROCESSES) {
|
|
64
|
+
options.cluster = {
|
|
65
|
+
processes: parseInt(process.env.APOS_CLUSTER_PROCESSES)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (options.cluster && (process.env.NODE_ENV !== 'production')) {
|
|
69
|
+
console.log('NODE_ENV is not set to production, disabling cluster mode');
|
|
70
|
+
options.cluster = false;
|
|
71
|
+
}
|
|
72
|
+
if (options.cluster && !argv._.length) {
|
|
73
|
+
// For bc with node 14 and below we need to check both
|
|
74
|
+
if (cluster.isPrimary || cluster.isMaster) {
|
|
75
|
+
let processes = options.cluster.processes || cpus().length;
|
|
76
|
+
if (processes <= 0) {
|
|
77
|
+
processes = cpus().length + processes;
|
|
78
|
+
}
|
|
79
|
+
let capped = '';
|
|
80
|
+
if (processes > cpus().length) {
|
|
81
|
+
processes = cpus().length;
|
|
82
|
+
capped = ' (capped to number of CPU cores)';
|
|
83
|
+
}
|
|
84
|
+
if (processes < 2) {
|
|
85
|
+
processes = 2;
|
|
86
|
+
if (capped) {
|
|
87
|
+
capped = ' (less than 2 cores, capped to minimum of 2)';
|
|
88
|
+
} else {
|
|
89
|
+
capped = ' (using minimum of 2)';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
console.log(`Starting ${processes} cluster child processes${capped}`);
|
|
93
|
+
for (let i = 0; i < processes; i++) {
|
|
94
|
+
clusterFork();
|
|
95
|
+
}
|
|
96
|
+
cluster.on('exit', (worker, code, signal) => {
|
|
97
|
+
if (code !== 0) {
|
|
98
|
+
if ((Date.now() - worker.bornAt) < guardTime) {
|
|
99
|
+
console.error(`Worker process ${worker.process.pid} failed in ${seconds(Date.now() - worker.bornAt)}, waiting ${seconds(guardTime)} before restart`);
|
|
100
|
+
setTimeout(() => {
|
|
101
|
+
respawn(worker);
|
|
102
|
+
}, guardTime);
|
|
103
|
+
} else {
|
|
104
|
+
respawn(worker);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return null;
|
|
109
|
+
} else {
|
|
110
|
+
console.log(`Cluster worker ${process.pid} started`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
26
113
|
|
|
27
114
|
// The core is not a true moog object but it must look enough like one
|
|
28
115
|
// to participate as an async event emitter
|
|
@@ -174,7 +261,7 @@ module.exports = async function(options) {
|
|
|
174
261
|
function getRoot() {
|
|
175
262
|
let _module = module;
|
|
176
263
|
let m = _module;
|
|
177
|
-
while (m.parent) {
|
|
264
|
+
while (m.parent && m.parent.filename) {
|
|
178
265
|
// The test file is the root as far as we are concerned,
|
|
179
266
|
// not mocha itself
|
|
180
267
|
if (m.parent.filename.match(/\/node_modules\/mocha\//)) {
|
|
@@ -334,6 +421,10 @@ module.exports = async function(options) {
|
|
|
334
421
|
synth.define(name, options);
|
|
335
422
|
});
|
|
336
423
|
|
|
424
|
+
// Apostrophe prefers that any improvements to @apostrophecms/global
|
|
425
|
+
// be applied before any project level version of @apostrophecms/global
|
|
426
|
+
synth.applyImprovementsBeforeProjectLevel();
|
|
427
|
+
|
|
337
428
|
return synth;
|
|
338
429
|
}
|
|
339
430
|
|
|
@@ -514,3 +605,17 @@ module.exports.bundle = {
|
|
|
514
605
|
modules: abstractClasses.concat(_.keys(defaults.modules)),
|
|
515
606
|
directory: 'modules'
|
|
516
607
|
};
|
|
608
|
+
|
|
609
|
+
function seconds(msec) {
|
|
610
|
+
return (Math.round(msec / 100) / 10) + ' seconds';
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function clusterFork() {
|
|
614
|
+
const worker = cluster.fork();
|
|
615
|
+
worker.bornAt = Date.now();
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function respawn(worker) {
|
|
619
|
+
console.error(`Respawning worker process ${worker.process.pid}`);
|
|
620
|
+
clusterFork();
|
|
621
|
+
}
|
package/lib/moog-require.js
CHANGED
|
@@ -202,5 +202,28 @@ module.exports = function(options) {
|
|
|
202
202
|
return _.has(self.improvements, name);
|
|
203
203
|
};
|
|
204
204
|
|
|
205
|
+
self.applyImprovementsBeforeProjectLevel = () => {
|
|
206
|
+
for (const [ name, definition ] of Object.entries(self.definitions)) {
|
|
207
|
+
// At this stage the complete definition of a type is a linked list of `extend`
|
|
208
|
+
// properties starting from what should be project level, unless there
|
|
209
|
+
// are improvements. Shuffle project level to be the first in the
|
|
210
|
+
// linked list
|
|
211
|
+
if (definition.__meta.name !== self.originalToMy(name)) {
|
|
212
|
+
let candidate = definition;
|
|
213
|
+
let predecessor = null;
|
|
214
|
+
while (candidate.extend && ((typeof candidate.extend) === 'object')) {
|
|
215
|
+
predecessor = candidate;
|
|
216
|
+
candidate = candidate.extend;
|
|
217
|
+
if (candidate.__meta.name === self.originalToMy(name)) {
|
|
218
|
+
predecessor.extend = candidate.extend;
|
|
219
|
+
candidate.extend = definition;
|
|
220
|
+
self.definitions[name] = candidate;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
205
228
|
return self;
|
|
206
229
|
};
|
package/lib/moog.js
CHANGED
|
@@ -211,6 +211,7 @@ module.exports = function(options) {
|
|
|
211
211
|
for (const [ key, value ] of Object.entries(properties.group)) {
|
|
212
212
|
if (groups[key] && Array.isArray(groups[key].fields)) {
|
|
213
213
|
value.fields = groups[key].fields.concat(value.fields);
|
|
214
|
+
value.label = value.label || groups[key].label;
|
|
214
215
|
}
|
|
215
216
|
}
|
|
216
217
|
|
|
@@ -259,20 +259,32 @@ export default {
|
|
|
259
259
|
// So that areas revert to being editable
|
|
260
260
|
await this.refresh();
|
|
261
261
|
},
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
262
|
+
// Accept a hint that a user is actively typing and/or manipulating controls
|
|
263
|
+
// and it would best not to enable a save button or a "...Saved" indication yet
|
|
264
|
+
// to avoid a frenetic display and/or a situation where not everything is ready
|
|
265
|
+
// to be saved yet.
|
|
266
|
+
//
|
|
267
|
+
// If the event is emitted with a boolean value of `true`, the emitter takes
|
|
268
|
+
// responsibility for later emitting `false` to indicate active typing/manipulating
|
|
269
|
+
// is no longer in progress. If the event is emitted with no value then there is a
|
|
270
|
+
// 1100-millisecond, debounced timeout.
|
|
271
|
+
|
|
272
|
+
async onContextEditing(state) {
|
|
273
|
+
if ((typeof state) === 'boolean') {
|
|
274
|
+
this.editing = state;
|
|
275
|
+
} else {
|
|
276
|
+
if (!this.editing) {
|
|
277
|
+
this.editing = true;
|
|
278
|
+
}
|
|
279
|
+
if (this.editingTimeout) {
|
|
280
|
+
clearTimeout(this.editingTimeout);
|
|
281
|
+
}
|
|
282
|
+
this.editingTimeout = setTimeout(() => {
|
|
283
|
+
this.editing = false;
|
|
284
|
+
// Wait slightly longer than the rich text editor does
|
|
285
|
+
// before sending us a context-edited event
|
|
286
|
+
}, 1100);
|
|
270
287
|
}
|
|
271
|
-
this.editingTimeout = setTimeout(() => {
|
|
272
|
-
this.editing = false;
|
|
273
|
-
// Wait slightly longer than the rich text editor does
|
|
274
|
-
// before sending us a context-edited event
|
|
275
|
-
}, 1100);
|
|
276
288
|
},
|
|
277
289
|
async onPublish(e) {
|
|
278
290
|
if (!this.canPublish) {
|
|
@@ -451,7 +463,11 @@ export default {
|
|
|
451
463
|
}
|
|
452
464
|
},
|
|
453
465
|
async onContentChanged(e) {
|
|
454
|
-
|
|
466
|
+
|
|
467
|
+
if (
|
|
468
|
+
(e.doc && (e.doc._id === this.context._id)) ||
|
|
469
|
+
(e.docIds && e.docIds.includes(this.context._id))
|
|
470
|
+
) {
|
|
455
471
|
if (e.action === 'delete') {
|
|
456
472
|
if (!this.contextStack.length) {
|
|
457
473
|
// With the current page gone, we need to move to safe ground
|
|
@@ -46,7 +46,7 @@ module.exports = function(self) {
|
|
|
46
46
|
return { args };
|
|
47
47
|
},
|
|
48
48
|
async run(context, doc, name, _with) {
|
|
49
|
-
const req = context.
|
|
49
|
+
const req = context.ctx.__req;
|
|
50
50
|
let area;
|
|
51
51
|
if ((!doc) || ((typeof doc) !== 'object')) {
|
|
52
52
|
throw usage('You must pass an existing doc or widget as the first argument.');
|
|
@@ -40,7 +40,7 @@ module.exports = function(self) {
|
|
|
40
40
|
return { args };
|
|
41
41
|
},
|
|
42
42
|
async run(context, item, options, _with) {
|
|
43
|
-
const req = context.
|
|
43
|
+
const req = context.ctx.__req;
|
|
44
44
|
if (!item) {
|
|
45
45
|
self.apos.util.warn('a null widget was encountered.');
|
|
46
46
|
return '';
|
|
@@ -615,8 +615,8 @@ export default {
|
|
|
615
615
|
}
|
|
616
616
|
|
|
617
617
|
.apos-area-widget-wrapper--foreign .apos-area-widget-inner .apos-area-widget__breadcrumbs {
|
|
618
|
-
background-color: var(--a-
|
|
619
|
-
& ::v-deep .apos-
|
|
618
|
+
background-color: var(--a-background-inverted);
|
|
619
|
+
& ::v-deep .apos-button__content {
|
|
620
620
|
color: var(--a-text-inverted);
|
|
621
621
|
}
|
|
622
622
|
}
|
|
@@ -73,6 +73,12 @@ module.exports = {
|
|
|
73
73
|
await fs.remove(buildDir);
|
|
74
74
|
await fs.mkdirp(buildDir);
|
|
75
75
|
|
|
76
|
+
// Static asset files in `public` subdirs of each module are copied
|
|
77
|
+
// to the same relative path `/public/apos-frontend/namespace/modules/modulename`.
|
|
78
|
+
// Inherited files are also copied, with the deepest subclass overriding in the
|
|
79
|
+
// event of a conflict
|
|
80
|
+
await moduleOverrides(`${bundleDir}/modules`, 'public');
|
|
81
|
+
|
|
76
82
|
for (const [ name, options ] of Object.entries(self.builds)) {
|
|
77
83
|
// If the option is not present always rebuild everything
|
|
78
84
|
let rebuild = argv && !argv['check-apos-build'];
|
|
@@ -122,12 +128,17 @@ module.exports = {
|
|
|
122
128
|
// just `public` and `apos`) by examining those specified as
|
|
123
129
|
// targets for the various builds
|
|
124
130
|
const scenes = [ ...new Set(Object.values(self.builds).map(options => options.scenes).flat()) ];
|
|
125
|
-
let
|
|
131
|
+
let deployFiles = [];
|
|
126
132
|
for (const scene of scenes) {
|
|
127
|
-
|
|
133
|
+
deployFiles = [ ...deployFiles, ...merge(scene) ];
|
|
128
134
|
}
|
|
129
|
-
|
|
130
|
-
|
|
135
|
+
// enumerate public assets and include them in deployment if appropriate
|
|
136
|
+
const publicAssets = glob.sync('modules/**/*', {
|
|
137
|
+
cwd: bundleDir,
|
|
138
|
+
mark: true
|
|
139
|
+
}).filter(match => !match.endsWith('/'));
|
|
140
|
+
deployFiles = [ ...deployFiles, ...publicAssets ];
|
|
141
|
+
await deploy(deployFiles);
|
|
131
142
|
|
|
132
143
|
if (process.env.APOS_BUNDLE_ANALYZER) {
|
|
133
144
|
return new Promise((resolve, reject) => {
|
|
@@ -162,7 +173,7 @@ module.exports = {
|
|
|
162
173
|
for (const name of names) {
|
|
163
174
|
const moduleDir = `${modulesDir}/${name}`;
|
|
164
175
|
for (const dir of directories[name]) {
|
|
165
|
-
const srcDir = `${dir}
|
|
176
|
+
const srcDir = `${dir}/${source}`;
|
|
166
177
|
if (fs.existsSync(srcDir)) {
|
|
167
178
|
await fs.copy(srcDir, moduleDir);
|
|
168
179
|
}
|
|
@@ -176,7 +187,7 @@ module.exports = {
|
|
|
176
187
|
}));
|
|
177
188
|
const modulesDir = `${buildDir}/${name}/modules`;
|
|
178
189
|
const source = options.source || name;
|
|
179
|
-
await moduleOverrides(modulesDir, source);
|
|
190
|
+
await moduleOverrides(modulesDir, `ui/${source}`);
|
|
180
191
|
|
|
181
192
|
let iconImports, componentImports, tiptapExtensionImports, appImports, indexJsImports, indexSassImports;
|
|
182
193
|
if (options.apos) {
|
|
@@ -233,7 +244,8 @@ module.exports = {
|
|
|
233
244
|
// Remove previous build artifacts, as some pipelines won't build all artifacts
|
|
234
245
|
// if there is no input, and we don't want stale output in the bundle
|
|
235
246
|
fs.removeSync(`${bundleDir}/${outputFilename}`);
|
|
236
|
-
|
|
247
|
+
const cssPath = `${bundleDir}/${outputFilename}`.replace(/\.js$/, '.css');
|
|
248
|
+
fs.removeSync(cssPath);
|
|
237
249
|
await Promise.promisify(webpackModule)(require(`./lib/webpack/${name}/webpack.config`)(
|
|
238
250
|
{
|
|
239
251
|
importFile,
|
|
@@ -243,6 +255,11 @@ module.exports = {
|
|
|
243
255
|
},
|
|
244
256
|
self.apos
|
|
245
257
|
));
|
|
258
|
+
if (fs.existsSync(cssPath)) {
|
|
259
|
+
fs.writeFileSync(cssPath, self.filterCss(fs.readFileSync(cssPath, 'utf8'), {
|
|
260
|
+
modulesPrefix: `${self.getAssetBaseUrl()}/modules`
|
|
261
|
+
}));
|
|
262
|
+
}
|
|
246
263
|
if (options.apos) {
|
|
247
264
|
const now = Date.now().toString();
|
|
248
265
|
fs.writeFileSync(`${bundleDir}/${name}-build-timestamp.txt`, now);
|
|
@@ -269,7 +286,9 @@ module.exports = {
|
|
|
269
286
|
const publicImports = getImports(name, '*.css', { });
|
|
270
287
|
fs.writeFileSync(`${bundleDir}/${name}-build.css`,
|
|
271
288
|
publicImports.paths.map(path => {
|
|
272
|
-
return fs.readFileSync(path, 'utf8')
|
|
289
|
+
return self.filterCss(fs.readFileSync(path, 'utf8'), {
|
|
290
|
+
modulesPrefix: `${self.getAssetBaseUrl()}/modules`
|
|
291
|
+
});
|
|
273
292
|
}).join('\n')
|
|
274
293
|
);
|
|
275
294
|
}
|
|
@@ -358,7 +377,19 @@ module.exports = {
|
|
|
358
377
|
return [ jsModules, jsNoModules, css ];
|
|
359
378
|
}
|
|
360
379
|
|
|
361
|
-
|
|
380
|
+
// If NODE_ENV is production, this function will copy the given
|
|
381
|
+
// array of asset files from `${bundleDir}/${file}` to
|
|
382
|
+
// the same relative location in the appropriate release subdirectory in
|
|
383
|
+
// `/public/apos-frontend/releases`, or in `/apos-frontend/releases` in
|
|
384
|
+
// uploadfs if `APOS_UPLOADFS_ASSETS` is present.
|
|
385
|
+
//
|
|
386
|
+
// If NODE_ENV is not production this function does nothing and
|
|
387
|
+
// the assets are served directly from `/public/apos-frontend/${file}`.
|
|
388
|
+
//
|
|
389
|
+
// The namespace (e.g. default) should be part of each filename given.
|
|
390
|
+
// A leading slash should NOT be passed.
|
|
391
|
+
|
|
392
|
+
async function deploy(files) {
|
|
362
393
|
if (process.env.NODE_ENV !== 'production') {
|
|
363
394
|
return;
|
|
364
395
|
}
|
|
@@ -373,17 +404,23 @@ module.exports = {
|
|
|
373
404
|
} else {
|
|
374
405
|
// The right choice with Docker if uploadfs is just the local filesystem
|
|
375
406
|
// mapped to a volume (a Docker build step can't access that)
|
|
376
|
-
copyIn =
|
|
407
|
+
copyIn = fsCopyIn;
|
|
377
408
|
releaseDir = `${self.apos.rootDir}/public/apos-frontend/releases/${releaseId}/${namespace}`;
|
|
378
409
|
await fs.mkdirp(releaseDir);
|
|
379
410
|
}
|
|
380
|
-
for (const
|
|
381
|
-
const src = `${bundleDir}/${
|
|
382
|
-
await copyIn(src, `${releaseDir}/${
|
|
411
|
+
for (const file of files) {
|
|
412
|
+
const src = `${bundleDir}/${file}`;
|
|
413
|
+
await copyIn(src, `${releaseDir}/${file}`);
|
|
383
414
|
await fs.remove(src);
|
|
384
415
|
}
|
|
385
416
|
}
|
|
386
417
|
|
|
418
|
+
async function fsCopyIn(from, to) {
|
|
419
|
+
const base = path.dirname(to);
|
|
420
|
+
await fs.mkdirp(base);
|
|
421
|
+
return fs.copyFile(from, to);
|
|
422
|
+
}
|
|
423
|
+
|
|
387
424
|
function getImports(folder, pattern, options) {
|
|
388
425
|
let components = [];
|
|
389
426
|
const seen = {};
|
|
@@ -650,6 +687,30 @@ module.exports = {
|
|
|
650
687
|
if (!self.options.es5) {
|
|
651
688
|
delete self.builds['src-es5'];
|
|
652
689
|
}
|
|
690
|
+
},
|
|
691
|
+
// Filter the given css performing any necessary transformations,
|
|
692
|
+
// such as support for the /modules path regardless of where
|
|
693
|
+
// static assets are actually deployed
|
|
694
|
+
filterCss(css, { modulesPrefix }) {
|
|
695
|
+
return self.filterCssUrls(css, url => {
|
|
696
|
+
if (url.startsWith('/modules')) {
|
|
697
|
+
return url.replace('/modules', modulesPrefix);
|
|
698
|
+
}
|
|
699
|
+
return url;
|
|
700
|
+
});
|
|
701
|
+
},
|
|
702
|
+
// Run all URLs in CSS through a filter function
|
|
703
|
+
filterCssUrls(css, filter) {
|
|
704
|
+
css = css.replace(/url\(([^'"].*?)\)/g, function(s, url) {
|
|
705
|
+
return 'url(' + filter(url) + ')';
|
|
706
|
+
});
|
|
707
|
+
css = css.replace(/url\("([^"]+?)"\)/g, function(s, url) {
|
|
708
|
+
return 'url("' + filter(url) + '")';
|
|
709
|
+
});
|
|
710
|
+
css = css.replace(/url\('([^']+?)'\)/g, function(s, url) {
|
|
711
|
+
return 'url(\'' + filter(url) + '\')';
|
|
712
|
+
});
|
|
713
|
+
return css;
|
|
653
714
|
}
|
|
654
715
|
};
|
|
655
716
|
},
|
|
@@ -667,6 +728,9 @@ module.exports = {
|
|
|
667
728
|
}
|
|
668
729
|
const script = fs.readFileSync(path.join(__dirname, '/lib/refresh-on-restart.js'), 'utf8');
|
|
669
730
|
return self.apos.template.safe(`<script data-apos-refresh-on-restart="${self.action}/restart-id">\n${script}</script>`);
|
|
731
|
+
},
|
|
732
|
+
url(path) {
|
|
733
|
+
return `${self.getAssetBaseUrl()}${path}`;
|
|
670
734
|
}
|
|
671
735
|
};
|
|
672
736
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// This module establishes `apos.db`, the
|
|
1
|
+
// This module establishes `apos.db`, the MongoDB database object.
|
|
2
2
|
//
|
|
3
3
|
// ## Options
|
|
4
4
|
//
|
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
//
|
|
21
21
|
// ### `client`
|
|
22
22
|
//
|
|
23
|
-
// An existing MongoDB connection (MongoClient) object. If present,
|
|
24
|
-
// connection instance is created that reuses the same sockets,
|
|
23
|
+
// An existing MongoDB connection (MongoClient) object. If present, it is used
|
|
25
24
|
// and `uri`, `host`, `connect`, etc. are ignored.
|
|
26
25
|
//
|
|
27
26
|
// ### `versionCheck`
|
|
@@ -40,7 +39,7 @@
|
|
|
40
39
|
// allow other modules to drop related non-MongoDB resources at the
|
|
41
40
|
// same time, if desired.
|
|
42
41
|
//
|
|
43
|
-
// Note that `apos.db` is the
|
|
42
|
+
// Note that `apos.db` is the MongoDB database object, not this module.
|
|
44
43
|
// You shouldn't need to talk to this module after startup, but you can
|
|
45
44
|
// access it as `apos.modules['@apostrophecms/db']` if you wish. You can
|
|
46
45
|
// also access `apos.dbClient` if you need the MongoClient object.
|
|
@@ -81,8 +80,8 @@ module.exports = {
|
|
|
81
80
|
},
|
|
82
81
|
methods(self) {
|
|
83
82
|
return {
|
|
84
|
-
// Open the database connection. Always
|
|
85
|
-
// sensible defaults.
|
|
83
|
+
// Open the database connection. Always uses MongoClient with its
|
|
84
|
+
// sensible defaults. Builds a URI if necessary, so we can call it
|
|
86
85
|
// in a consistent way.
|
|
87
86
|
//
|
|
88
87
|
// One default we override: if the connection is lost, we keep
|