apostrophe 3.17.0 → 3.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +3 -0
- package/.eslintrc +4 -3
- package/.github/workflows/main.yml +2 -2
- package/.stylelintrc +12 -2
- package/CHANGELOG.md +34 -2
- package/defaults.js +2 -2
- package/index.js +124 -33
- package/lib/escape-host.js +8 -0
- package/lib/mongodb-connect.js +55 -0
- package/lib/opentelemetry.js +144 -0
- package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +2 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +20 -8
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +10 -0
- package/modules/@apostrophecms/asset/lib/globalIcons.js +1 -0
- package/modules/@apostrophecms/attachment/index.js +81 -29
- package/modules/@apostrophecms/db/index.js +7 -10
- package/modules/@apostrophecms/doc/index.js +138 -23
- package/modules/@apostrophecms/doc-type/index.js +162 -63
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +39 -1
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +11 -1
- package/modules/@apostrophecms/email/index.js +1 -1
- package/modules/@apostrophecms/express/index.js +2 -2
- package/modules/@apostrophecms/http/index.js +2 -1
- package/modules/@apostrophecms/i18n/i18n/en.json +10 -0
- package/modules/@apostrophecms/i18n/i18n/es.json +7 -0
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +7 -0
- package/modules/@apostrophecms/i18n/i18n/sk.json +7 -0
- package/modules/@apostrophecms/image/index.js +182 -1
- package/modules/@apostrophecms/image/ui/apos/apps/AposImageRelationshipQueryFilter.js +13 -0
- package/modules/@apostrophecms/image/ui/apos/components/AposImageCropper.vue +460 -0
- package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +510 -0
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +5 -1
- package/modules/@apostrophecms/image/ui/apos/lib/aspectRatios.js +26 -0
- package/modules/@apostrophecms/image-widget/views/widget.html +5 -2
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +45 -1
- package/modules/@apostrophecms/module/index.js +98 -17
- package/modules/@apostrophecms/module/lib/events.js +46 -11
- package/modules/@apostrophecms/page/index.js +55 -22
- package/modules/@apostrophecms/piece-page-type/index.js +1 -0
- package/modules/@apostrophecms/piece-type/index.js +13 -4
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposRelationshipEditor.vue +2 -2
- package/modules/@apostrophecms/rich-text-widget/index.js +1 -3
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +4 -0
- package/modules/@apostrophecms/schema/index.js +79 -73
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArea.vue +10 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputObject.vue +22 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +72 -36
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +7 -26
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +8 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +45 -15
- package/modules/@apostrophecms/task/index.js +106 -52
- package/modules/@apostrophecms/template/index.js +111 -76
- package/modules/@apostrophecms/template/lib/custom-tags/component.js +42 -22
- package/modules/@apostrophecms/ui/ui/apos/components/AposSelect.vue +61 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +46 -11
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlatList.vue +10 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposTreeHeader.vue +2 -22
- package/modules/@apostrophecms/ui/ui/apos/utils/index.js +9 -0
- package/modules/@apostrophecms/widget-type/index.js +2 -23
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidget.vue +1 -1
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +20 -1
- package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +0 -9
- package/package.json +16 -12
- package/scripts/lint-i18n.js +2 -2
- package/test/assets.js +2 -1
- package/test/attachments.js +119 -26
- package/test/bundle.js +1 -1
- package/test/content-i18n.js +6 -6
- package/test/docs.js +244 -4
- package/test/draft-published.js +41 -41
- package/test/express.js +1 -1
- package/test/http.js +2 -2
- package/test/images.js +94 -4
- package/test/job.js +1 -1
- package/test/locks.js +1 -1
- package/test/middleware-and-route-order.js +3 -3
- package/test/pages-public-api.js +48 -4
- package/test/pages-rest.js +20 -20
- package/test/pages.js +377 -11
- package/test/parked-pages.js +1 -1
- package/test/permissions.js +10 -10
- package/test/pieces-public-api.js +130 -6
- package/test/pieces.js +247 -60
- package/test/recursionGuard.js +6 -6
- package/test/restApiRoutes.js +6 -6
- package/test/schemaBuilders.js +7 -7
- package/test/schemas.js +59 -59
- package/test/search.js +3 -3
- package/test/soft-redirects.js +13 -13
- package/test/static-i18n.js +1 -1
- package/test/templates.js +10 -10
- package/test/urls.js +2 -2
- package/test/users.js +21 -21
- package/test/utils.js +13 -13
- package/test/widgets.js +2 -2
- package/test-lib/util.js +2 -5
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidget.vue +0 -26
package/.editorconfig
CHANGED
package/.eslintrc
CHANGED
|
@@ -20,8 +20,8 @@ jobs:
|
|
|
20
20
|
runs-on: ubuntu-latest
|
|
21
21
|
strategy:
|
|
22
22
|
matrix:
|
|
23
|
-
node-version: [
|
|
24
|
-
mongodb-version: [4.2,
|
|
23
|
+
node-version: [14, 16, 18]
|
|
24
|
+
mongodb-version: [4.2, 5.0]
|
|
25
25
|
|
|
26
26
|
# Steps represent a sequence of tasks that will be executed as part of the job
|
|
27
27
|
steps:
|
package/.stylelintrc
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
'color-hex-length': 'short',
|
|
7
7
|
'color-named': 'never',
|
|
8
8
|
'color-no-invalid-hex': true,
|
|
9
|
-
'declaration-property-unit-
|
|
9
|
+
'declaration-property-unit-allowed-list': { 'line-height': [] },
|
|
10
10
|
'font-family-no-duplicate-names': true,
|
|
11
11
|
'number-leading-zero': 'always',
|
|
12
12
|
'number-max-precision': 2,
|
|
@@ -80,5 +80,15 @@
|
|
|
80
80
|
]
|
|
81
81
|
}
|
|
82
82
|
]
|
|
83
|
-
}
|
|
83
|
+
},
|
|
84
|
+
overrides: [
|
|
85
|
+
{
|
|
86
|
+
files: ['**/*.scss'],
|
|
87
|
+
customSyntax: 'postcss-scss'
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
files: ['**/*.vue'],
|
|
91
|
+
customSyntax: 'postcss-html'
|
|
92
|
+
}
|
|
93
|
+
]
|
|
84
94
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
# 3.
|
|
3
|
+
# 3.18.0 (2022-05-03)
|
|
4
|
+
|
|
5
|
+
### Adds
|
|
6
|
+
|
|
7
|
+
* Images may now be cropped to suit a particular placement after selecting them. SVG files may not be cropped as it is not possible in the general case.
|
|
8
|
+
* Editors may also select a "focal point" for the image after selecting it. This ensures that this particular point remains visible even if CSS would otherwise crop it, which is a common issue in responsive design. See the `@apostrophecms/image` widget for a sample implementation of the necessary styles.
|
|
9
|
+
* Adds the `aspectRatio` option for image widgets. When set to `[ w, h ]` (a ratio of width to height), images are automatically cropped to this aspect ratio when chosen for that particular widget. If the user does not crop manually, then cropping happens automatically.
|
|
10
|
+
* Adds the `minSize` option for image widgets. This ensures that the images chosen are at least the given size `[ width, height ]`, and also ensures the user cannot choose something smaller than that when cropping.
|
|
11
|
+
* Implements OpenTelemetry instrumentation.
|
|
12
|
+
* Developers may now specify an alternate Vue component to be used for editing the subfields of relationships, either at the field level or as a default for all relationships with a particular piece type.
|
|
13
|
+
* The widget type base module now always passes on the `components` option as browser data, so that individual widget type modules that support contextual editing can be implemented more conveniently.
|
|
14
|
+
* In-context widget editor components now receive a `focused` prop which is helpful in deciding when to display additional UI.
|
|
15
|
+
* Adds new configuration option - `beforeExit` async handler.
|
|
16
|
+
* Handlers listening for the `apostrophe:run` event are now able to send an exit code to the Apostrophe bootstrap routine.
|
|
17
|
+
* Support for Node.js 17 and 18. MongoDB connections to `localhost` will now successfully find a typical dev MongoDB server bound only to `127.0.0.1`, Apostrophe can generate valid ipv6 URLs pointing back to itself, and `webpack` and `vue-loader` have been updated to address incompatibilities.
|
|
18
|
+
* Adds support for custom context menus provided by any module (see `apos.doc.addContextOperation()`).
|
|
19
|
+
* The `AposSchema` component now supports an optional `generation` prop which may be used to force a refresh when the value of the object changes externally. This is a compromise to avoid the performance hit of checking numerous subfields for possible changes every time the `value` prop changes in response to an `input` event.
|
|
20
|
+
* Adds new event `@apostrophecms/doc:afterAllModesDeleted` fired after all modes of a given document are purged.
|
|
21
|
+
|
|
22
|
+
### Fixes
|
|
23
|
+
|
|
24
|
+
* Documentation of obsolete options has been removed.
|
|
25
|
+
* Dead code relating to activating in-context widget editors have been removed. They are always active and have been for some time. In the future they might be swapped in on scroll, but there will never be a need to swap them in "on click."
|
|
26
|
+
* The `self.email` method of modules now correctly accepts a default `from` address configured for a specific module via the `from` subproperty of the `email` option to that module. Thanks to `chmdebeer` for pointing out the issue and the fix.
|
|
27
|
+
* Fixes `_urls` not added on attachment fields when pieces API index is requested (#3643)
|
|
28
|
+
* Fixes float field UI bug that transforms the value to integer when there is no field error and the first number after the decimal is `0`.
|
|
29
|
+
|
|
30
|
+
## 3.17.0 (2022-03-31)
|
|
4
31
|
|
|
5
32
|
### Adds
|
|
6
33
|
|
|
@@ -9,13 +36,18 @@
|
|
|
9
36
|
* Adds possibility for modules to [extend the webpack configuration](https://v3.docs.apostrophecms.org/guide/webpack.html).
|
|
10
37
|
* Adds possibility for modules to [add extra frontend bundles for scss and js](https://v3.docs.apostrophecms.org/guide/webpack.html). This is useful when the `ui/src` build would otherwise be very large due to code used on rarely accessed pages.
|
|
11
38
|
* Loads the right bundles on the right pages depending on the page template and the loaded widgets. Logged-in users have all the bundles on every page, because they might introduce widgets at any time.
|
|
39
|
+
* Fixes deprecation warnings displayed after running `npm install`, for dependencies that are directly included by this package.
|
|
40
|
+
* Implement custom ETags emission when `etags` cache option is enabled. [See the documentation for more information](https://v3.docs.apostrophecms.org/guide/caching.html).
|
|
41
|
+
It allows caching of pages and pieces, using a cache invalidation mechanism that takes into account related (and reverse related) document updates, thanks to backlinks mentioned above.
|
|
42
|
+
Note that for now, only single pages and pieces benefit from the ETags caching system (pages' and pieces' `getOne` REST API route, and regular served pages).
|
|
43
|
+
The cache of an index page corresponding to the type of a piece that was just saved will automatically be invalidated. However, please consider that it won't be effective when a related piece is saved, therefore the cache will automatically be invalidated _after_ the cache lifetime set in `maxAge` cache option.
|
|
12
44
|
|
|
13
45
|
### Fixes
|
|
14
46
|
|
|
15
47
|
* Apostrophe's webpack build now works properly when developing code that imports module-specific npm dependencies from `ui/src` or `ui/apos` when using `npm link` to develop the module in question.
|
|
16
48
|
* The `es5: true` option to `@apostrophecms/asset` works again.
|
|
17
49
|
|
|
18
|
-
|
|
50
|
+
## 3.16.1 (2022-03-21)
|
|
19
51
|
|
|
20
52
|
### Fixes
|
|
21
53
|
|
package/defaults.js
CHANGED
|
@@ -7,6 +7,7 @@ module.exports = {
|
|
|
7
7
|
'@apostrophecms/schema': {},
|
|
8
8
|
'@apostrophecms/uploadfs': {},
|
|
9
9
|
'@apostrophecms/asset': {},
|
|
10
|
+
'@apostrophecms/busy': {},
|
|
10
11
|
'@apostrophecms/launder': {},
|
|
11
12
|
'@apostrophecms/http': {},
|
|
12
13
|
'@apostrophecms/db': {},
|
|
@@ -50,7 +51,6 @@ module.exports = {
|
|
|
50
51
|
'@apostrophecms/file': {},
|
|
51
52
|
'@apostrophecms/file-tag': {},
|
|
52
53
|
'@apostrophecms/soft-redirect': {},
|
|
53
|
-
'@apostrophecms/submitted-draft': {}
|
|
54
|
-
'@apostrophecms/busy': {}
|
|
54
|
+
'@apostrophecms/submitted-draft': {}
|
|
55
55
|
}
|
|
56
56
|
};
|
package/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// this should be loaded first
|
|
2
|
+
const opentelemetry = require('./lib/opentelemetry');
|
|
1
3
|
const path = require('path');
|
|
2
4
|
const _ = require('lodash');
|
|
3
5
|
const argv = require('boring')({ end: true });
|
|
@@ -39,6 +41,20 @@ let defaults = require('./defaults.js');
|
|
|
39
41
|
// can be set to a number, which will effectively set the cluster
|
|
40
42
|
// option to `cluster: { processes: n }`.
|
|
41
43
|
//
|
|
44
|
+
// `openTelemetryProvider`
|
|
45
|
+
//
|
|
46
|
+
// If set, Apostrophe will register it as a global OpenTelemetry tracer provider.
|
|
47
|
+
// The expected value is an object, an instance of TracerProvider.
|
|
48
|
+
// If the Node SDK is used in the application instead of manual configuration,
|
|
49
|
+
// the provider instance is only available as a
|
|
50
|
+
// private property: `sdkInstance._tracerProvider`. An issue can be opened
|
|
51
|
+
// to discuss the exposure of a public getter with the OpenTelemetry developers.
|
|
52
|
+
//
|
|
53
|
+
// `beforeExit`
|
|
54
|
+
//
|
|
55
|
+
// If set, Apostrophe will invoke it (await) before invoking process.exit.
|
|
56
|
+
// `beforeExit` may be an async function, will be awaited, and takes no arguments.
|
|
57
|
+
//
|
|
42
58
|
// ## Awaiting the Apostrophe function
|
|
43
59
|
//
|
|
44
60
|
// The apos function is async, but in typical cases you do not
|
|
@@ -59,8 +75,14 @@ let defaults = require('./defaults.js');
|
|
|
59
75
|
// `null` in the primary process. In the child process it resolves as
|
|
60
76
|
// documented above.
|
|
61
77
|
|
|
78
|
+
// The actual entry point, a wrapper that enables the telemetry and starts the
|
|
79
|
+
// root span
|
|
62
80
|
module.exports = async function(options) {
|
|
81
|
+
const telemetry = opentelemetry(options);
|
|
82
|
+
let spanName = 'apostrophe:boot';
|
|
63
83
|
const guardTime = 20000;
|
|
84
|
+
|
|
85
|
+
// Detect cluster options
|
|
64
86
|
if (process.env.APOS_CLUSTER_PROCESSES) {
|
|
65
87
|
options.cluster = {
|
|
66
88
|
processes: parseInt(process.env.APOS_CLUSTER_PROCESSES)
|
|
@@ -70,48 +92,72 @@ module.exports = async function(options) {
|
|
|
70
92
|
console.log('NODE_ENV is not set to production, disabling cluster mode');
|
|
71
93
|
options.cluster = false;
|
|
72
94
|
}
|
|
95
|
+
|
|
96
|
+
// Execute if cluster enabled
|
|
73
97
|
if (options.cluster && !argv._.length) {
|
|
74
98
|
// For bc with node 14 and below we need to check both
|
|
75
99
|
if (cluster.isPrimary || cluster.isMaster) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
processes = cpus().length
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (processes > cpus().length) {
|
|
82
|
-
processes = cpus().length;
|
|
83
|
-
capped = ' (capped to number of CPU cores)';
|
|
84
|
-
}
|
|
85
|
-
if (processes < 2) {
|
|
86
|
-
processes = 2;
|
|
87
|
-
if (capped) {
|
|
88
|
-
capped = ' (less than 2 cores, capped to minimum of 2)';
|
|
89
|
-
} else {
|
|
90
|
-
capped = ' (using minimum of 2)';
|
|
100
|
+
// Activate and return the callback return value
|
|
101
|
+
return telemetry.startActiveSpan(`${spanName}:primary`, async (span) => {
|
|
102
|
+
let processes = options.cluster.processes || cpus().length;
|
|
103
|
+
if (processes <= 0) {
|
|
104
|
+
processes = cpus().length + processes;
|
|
91
105
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
setTimeout(() => {
|
|
102
|
-
respawn(worker);
|
|
103
|
-
}, guardTime);
|
|
106
|
+
let capped = '';
|
|
107
|
+
if (processes > cpus().length) {
|
|
108
|
+
processes = cpus().length;
|
|
109
|
+
capped = ' (capped to number of CPU cores)';
|
|
110
|
+
}
|
|
111
|
+
if (processes < 2) {
|
|
112
|
+
processes = 2;
|
|
113
|
+
if (capped) {
|
|
114
|
+
capped = ' (less than 2 cores, capped to minimum of 2)';
|
|
104
115
|
} else {
|
|
105
|
-
|
|
116
|
+
capped = ' (using minimum of 2)';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
console.log(`Starting ${processes} cluster child processes${capped}`);
|
|
120
|
+
for (let i = 0; i < processes; i++) {
|
|
121
|
+
clusterFork();
|
|
122
|
+
}
|
|
123
|
+
cluster.on('exit', (worker, code, signal) => {
|
|
124
|
+
if (code !== 0) {
|
|
125
|
+
if ((Date.now() - worker.bornAt) < guardTime) {
|
|
126
|
+
console.error(`Worker process ${worker.process.pid} failed in ${seconds(Date.now() - worker.bornAt)}, waiting ${seconds(guardTime)} before restart`);
|
|
127
|
+
setTimeout(() => {
|
|
128
|
+
respawn(worker);
|
|
129
|
+
}, guardTime);
|
|
130
|
+
} else {
|
|
131
|
+
respawn(worker);
|
|
132
|
+
}
|
|
106
133
|
}
|
|
134
|
+
});
|
|
135
|
+
span.end();
|
|
136
|
+
if (typeof options.beforeExit === 'function') {
|
|
137
|
+
await options.beforeExit();
|
|
107
138
|
}
|
|
139
|
+
return null;
|
|
108
140
|
});
|
|
109
|
-
return null;
|
|
110
141
|
} else {
|
|
142
|
+
// continue as a worker operation, the pid should be recorded by the auto instrumentation
|
|
143
|
+
spanName += ':worker';
|
|
111
144
|
console.log(`Cluster worker ${process.pid} started`);
|
|
112
145
|
}
|
|
113
146
|
}
|
|
114
147
|
|
|
148
|
+
// Create and activate the root span for the boot tracer
|
|
149
|
+
const self = await telemetry.startActiveSpan(spanName, async (span) => {
|
|
150
|
+
const res = await apostrophe(options, telemetry, span);
|
|
151
|
+
span.setStatus(telemetry.api.SpanStatusCode.OK);
|
|
152
|
+
span.end();
|
|
153
|
+
return res;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return self;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// The actual apostrophe bootstrap
|
|
160
|
+
async function apostrophe(options, telemetry, rootSpan) {
|
|
115
161
|
// The core is not a true moog object but it must look enough like one
|
|
116
162
|
// to participate as an async event emitter
|
|
117
163
|
const self = {
|
|
@@ -120,6 +166,43 @@ module.exports = async function(options) {
|
|
|
120
166
|
}
|
|
121
167
|
};
|
|
122
168
|
|
|
169
|
+
// Terminates the process. Emits the `apostrophe:beforeExit` async event;
|
|
170
|
+
// use this mechanism to invoke any pre-exit application level tasks. Any
|
|
171
|
+
// `beforeExit` handler errors will be ignored.
|
|
172
|
+
// Invokes and awaits `options.beforeExit` function if available,
|
|
173
|
+
// passing as arguments the exit code and message (if any).
|
|
174
|
+
self._exit = async function(code = 0, message) {
|
|
175
|
+
try {
|
|
176
|
+
if (self.emit) {
|
|
177
|
+
await self.emit('beforeExit');
|
|
178
|
+
}
|
|
179
|
+
} catch (e) {
|
|
180
|
+
// we are at the point where errors are ignored,
|
|
181
|
+
// if emitter is already registered, all handler errors
|
|
182
|
+
// are already recorded by the event module instrumentation
|
|
183
|
+
console.error('beforeExit emit error', e);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (code !== 0) {
|
|
187
|
+
telemetry.handleError(rootSpan, message);
|
|
188
|
+
} else {
|
|
189
|
+
rootSpan.setStatus({
|
|
190
|
+
code: telemetry.api.SpanStatusCode.OK,
|
|
191
|
+
message
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
rootSpan.end();
|
|
195
|
+
|
|
196
|
+
if (typeof options.beforeExit === 'function') {
|
|
197
|
+
try {
|
|
198
|
+
await options.beforeExit(code, message);
|
|
199
|
+
} catch (e) {
|
|
200
|
+
console.error('beforeExit handler error', e);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
process.exit(code);
|
|
204
|
+
};
|
|
205
|
+
|
|
123
206
|
try {
|
|
124
207
|
const matches = process.version.match(/^v(\d+)/);
|
|
125
208
|
const version = parseInt(matches[1]);
|
|
@@ -130,6 +213,9 @@ module.exports = async function(options) {
|
|
|
130
213
|
// promise event emitter code
|
|
131
214
|
self.apos = self;
|
|
132
215
|
|
|
216
|
+
// Register the telemetry API as a pseudo module
|
|
217
|
+
self.apos.telemetry = telemetry;
|
|
218
|
+
|
|
133
219
|
Object.assign(self, require('./modules/@apostrophecms/module/lib/events.js')(self));
|
|
134
220
|
|
|
135
221
|
// Determine root module and root directory
|
|
@@ -212,17 +298,22 @@ module.exports = async function(options) {
|
|
|
212
298
|
await self.apos.page.implementParkAllInOtherLocales();
|
|
213
299
|
});
|
|
214
300
|
await self.emit('ready'); // formerly afterInit
|
|
301
|
+
|
|
215
302
|
if (self.taskRan) {
|
|
216
|
-
|
|
303
|
+
await self._exit();
|
|
217
304
|
} else {
|
|
218
|
-
|
|
305
|
+
const after = { exit: null };
|
|
306
|
+
await self.emit('run', self.isTask(), after);
|
|
307
|
+
if (after.exit !== null) {
|
|
308
|
+
await self._exit(after.exit);
|
|
309
|
+
}
|
|
219
310
|
}
|
|
311
|
+
|
|
220
312
|
return self;
|
|
221
313
|
} catch (e) {
|
|
222
314
|
if (options.exit !== false) {
|
|
223
|
-
/* eslint-disable-next-line no-console */
|
|
224
315
|
console.error(e);
|
|
225
|
-
|
|
316
|
+
await self._exit(1, e);
|
|
226
317
|
}
|
|
227
318
|
}
|
|
228
319
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const mongo = require('mongodb');
|
|
2
|
+
const dns = require('dns');
|
|
3
|
+
|
|
4
|
+
// Connect to MongoDB, using the modern topology and parser, and
|
|
5
|
+
// a tolerant policy to successfully connect to "localhost" even if
|
|
6
|
+
// the first record returned by the resolver doesn't reach mongodb's
|
|
7
|
+
// bind address because localhost resolves first to ::1 (ipv6) and
|
|
8
|
+
// mongodb lists only on 127.0.0.1 (ipv4) by default. For broadest
|
|
9
|
+
// compatibility we don't assume we know this will happen, we try all the
|
|
10
|
+
// addresses that localhost actually resolves to and succeed with the
|
|
11
|
+
// first one that works.
|
|
12
|
+
|
|
13
|
+
module.exports = async (uri, options) => {
|
|
14
|
+
const connectOptions = {
|
|
15
|
+
useUnifiedTopology: true,
|
|
16
|
+
useNewUrlParser: true,
|
|
17
|
+
...options
|
|
18
|
+
};
|
|
19
|
+
const parsed = new URL(uri);
|
|
20
|
+
if ((parsed.protocol !== 'mongodb:') || (parsed.hostname !== 'localhost')) {
|
|
21
|
+
return mongo.MongoClient.connect(parsed.toString(), connectOptions);
|
|
22
|
+
}
|
|
23
|
+
const records = await dns.promises.lookup('localhost', { all: true });
|
|
24
|
+
if (!records.length) {
|
|
25
|
+
// The computer that reaches this point has bigger problems 😅
|
|
26
|
+
throw new Error('Unable to resolve localhost to an IP address.');
|
|
27
|
+
}
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
let failed = 0;
|
|
30
|
+
let succeeded = false;
|
|
31
|
+
records.forEach(attempt);
|
|
32
|
+
async function attempt(record) {
|
|
33
|
+
try {
|
|
34
|
+
const parsed = new URL(uri);
|
|
35
|
+
parsed.hostname = record.address;
|
|
36
|
+
const result = await mongo.MongoClient.connect(parsed.toString(), connectOptions);
|
|
37
|
+
if (!succeeded) {
|
|
38
|
+
succeeded = true;
|
|
39
|
+
resolve(result);
|
|
40
|
+
} else {
|
|
41
|
+
// We succeeded in reaching localhost at both ip4 and ip6,
|
|
42
|
+
// but we only need one of them to succeed
|
|
43
|
+
await result.close();
|
|
44
|
+
}
|
|
45
|
+
} catch (e) {
|
|
46
|
+
failed++;
|
|
47
|
+
if (failed === records.length) {
|
|
48
|
+
// None succeeded, so reject with the last error
|
|
49
|
+
// (which one we reject with doesn't really matter)
|
|
50
|
+
reject(e);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Initialize OpenTelemetry tracer singleton and export
|
|
2
|
+
// the most useful API members, the tracer and some helper functions.
|
|
3
|
+
//
|
|
4
|
+
// The library shouldn't be used directly (although it's not fatal),
|
|
5
|
+
// it is registered as self.apos.telemetry pseudo module (see index.js).
|
|
6
|
+
const api = require('@opentelemetry/api');
|
|
7
|
+
const util = require('util');
|
|
8
|
+
const version = require('../package.json').version;
|
|
9
|
+
|
|
10
|
+
/** @type {api.Tracer} */
|
|
11
|
+
let tracer;
|
|
12
|
+
|
|
13
|
+
const Attributes = {
|
|
14
|
+
SCENE: 'apos.scene',
|
|
15
|
+
TEMPLATE: 'apos.template',
|
|
16
|
+
EVENT_MODULE: 'apos.event.module',
|
|
17
|
+
EVENT_NAME: 'apos.event.name',
|
|
18
|
+
// The module and method targeted by the current operation (see doc module)
|
|
19
|
+
TARGET_NAMESPACE: 'apos.target.namespace',
|
|
20
|
+
TARGET_FUNCTION: 'apos.target.function',
|
|
21
|
+
ARGV: 'apos.argv'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
module.exports = function (options = {}) {
|
|
25
|
+
if (!tracer) {
|
|
26
|
+
tracer = api.trace.getTracer('apostrophe', version);
|
|
27
|
+
|
|
28
|
+
// This shouldn't be needed, but having it doesn't hurt
|
|
29
|
+
if (options.openTelemetryProvider) {
|
|
30
|
+
api.trace.setGlobalTracerProvider(options.openTelemetryProvider);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Start and return a new span. Optionally provide a parent span or allow the parent
|
|
36
|
+
* span to be auto-detected.
|
|
37
|
+
* Use this when the current code block does the tracing and it doesn't expect
|
|
38
|
+
* more tracing to happen down the line.
|
|
39
|
+
*
|
|
40
|
+
* @param {String} name span name
|
|
41
|
+
* @param {api.Span|Boolean} [parentSpan] optional parent span
|
|
42
|
+
* @param {api.SpanOptions} [options] span options
|
|
43
|
+
* @returns {api.Span}
|
|
44
|
+
* @example
|
|
45
|
+
* // Auto-merge with the currently active span context
|
|
46
|
+
* const span = self.apos.telemetry.startSpan('event:someEvent');
|
|
47
|
+
* // Provide a parent span
|
|
48
|
+
* const span = self.apos.telemetry.startSpan('event:someEvent', parent);
|
|
49
|
+
* // Do not merge with any parent, start as a root
|
|
50
|
+
* const span = self.apos.telemetry.startSpan('event:someEvent', false);
|
|
51
|
+
* // Provide options for the newly created span
|
|
52
|
+
* const span = self.apos.telemetry.startSpan('event:someEvent', true, {
|
|
53
|
+
* attributes: [
|
|
54
|
+
* [SemanticAttributes.HTTP_METHOD]: "GET",
|
|
55
|
+
* [SemanticAttributes.HTTP_FLAVOR]: "1.1",
|
|
56
|
+
* [SemanticAttributes.HTTP_URL]: req.url
|
|
57
|
+
* ]
|
|
58
|
+
* });
|
|
59
|
+
*/
|
|
60
|
+
function startSpan(name, parentSpan, options) {
|
|
61
|
+
if ((!parentSpan || parentSpan === true) && parentSpan !== false) {
|
|
62
|
+
parentSpan = api.trace.getSpan(api.context.active());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (parentSpan) {
|
|
66
|
+
const ctx = api.trace.setSpan(api.context.active(), parentSpan);
|
|
67
|
+
return tracer.startSpan(name, options || undefined, ctx);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return tracer.startSpan(name, options || undefined);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Start span and make it active for all the nested spans.
|
|
75
|
+
* Use this when the current code block does the tracing, but it also expects
|
|
76
|
+
* more tracing to happen down the line (async calls).
|
|
77
|
+
*
|
|
78
|
+
* @param {String} name span name
|
|
79
|
+
* @param {Function} fn handler function
|
|
80
|
+
* @param {api.Span|Boolean} [parentSpan] optional parent span
|
|
81
|
+
* @param {api.SpanOptions} [options] span options
|
|
82
|
+
* @returns {api.Span|any} the return value of the handler or the newly created span
|
|
83
|
+
* @example
|
|
84
|
+
* // Activate span, return some value
|
|
85
|
+
* const value = await self.apos.telemetry.startActiveSpan(spanName, async (span) => {
|
|
86
|
+
* // Use the span, do work, end span, return any value
|
|
87
|
+
* span.end();
|
|
88
|
+
* return value;
|
|
89
|
+
* });
|
|
90
|
+
* // Activate span, using the context of parent span, return the active span
|
|
91
|
+
* const span = self.apos.telemetry.startActiveSpan(spanName, async (span) => {
|
|
92
|
+
* // Use the span, do work, return the span
|
|
93
|
+
* return span;
|
|
94
|
+
* });
|
|
95
|
+
*/
|
|
96
|
+
function startActiveSpan(name, fn, parentSpan, options) {
|
|
97
|
+
if (parentSpan) {
|
|
98
|
+
const ctx = api.trace.setSpan(api.context.active(), parentSpan);
|
|
99
|
+
return tracer.startActiveSpan(name, options || undefined, ctx, fn);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return tracer.startActiveSpan(name, options || undefined, fn);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Handle errors helper (catch block).
|
|
107
|
+
*
|
|
108
|
+
* @param {api.Span} span
|
|
109
|
+
* @param {Error|String} err
|
|
110
|
+
*/
|
|
111
|
+
function handleError(span, err) {
|
|
112
|
+
span.recordException(err);
|
|
113
|
+
span.setStatus({
|
|
114
|
+
code: api.SpanStatusCode.ERROR,
|
|
115
|
+
message: err
|
|
116
|
+
? typeof err === 'string' ? err : err.message
|
|
117
|
+
: undefined
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Stringify object - helper to properly set
|
|
123
|
+
* object as a span attribute value.
|
|
124
|
+
*
|
|
125
|
+
* @param {Object} obj
|
|
126
|
+
* @returns {String}
|
|
127
|
+
*/
|
|
128
|
+
function stringify(obj) {
|
|
129
|
+
return util.inspect(obj, {
|
|
130
|
+
depth: 20,
|
|
131
|
+
compact: false
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
api,
|
|
137
|
+
tracer,
|
|
138
|
+
Attributes,
|
|
139
|
+
stringify,
|
|
140
|
+
handleError,
|
|
141
|
+
startSpan,
|
|
142
|
+
startActiveSpan
|
|
143
|
+
};
|
|
144
|
+
};
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
:area-id="areaId"
|
|
39
39
|
:key="widget._id"
|
|
40
40
|
:widget="widget"
|
|
41
|
+
:generation="generation"
|
|
41
42
|
:i="i"
|
|
42
43
|
:options="options"
|
|
43
44
|
:next="next"
|
|
@@ -115,23 +116,23 @@ export default {
|
|
|
115
116
|
default() {
|
|
116
117
|
return {};
|
|
117
118
|
}
|
|
119
|
+
},
|
|
120
|
+
generation: {
|
|
121
|
+
type: Number,
|
|
122
|
+
required: false,
|
|
123
|
+
default() {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
118
126
|
}
|
|
119
127
|
},
|
|
120
128
|
emits: [ 'changed' ],
|
|
121
129
|
data() {
|
|
122
|
-
const validItems = this.items.filter(item => {
|
|
123
|
-
if (!window.apos.modules[`${item.type}-widget`]) {
|
|
124
|
-
console.warn(`The widget type ${item.type} exists in the content but is not configured.`);
|
|
125
|
-
}
|
|
126
|
-
return window.apos.modules[`${item.type}-widget`];
|
|
127
|
-
});
|
|
128
|
-
|
|
129
130
|
return {
|
|
130
131
|
addWidgetEditor: null,
|
|
131
132
|
addWidgetOptions: null,
|
|
132
133
|
addWidgetType: null,
|
|
133
134
|
areaId: cuid(),
|
|
134
|
-
next:
|
|
135
|
+
next: this.getValidItems(),
|
|
135
136
|
hoveredWidget: null,
|
|
136
137
|
focusedWidget: null,
|
|
137
138
|
contextMenuOptions: {
|
|
@@ -189,6 +190,9 @@ export default {
|
|
|
189
190
|
_id: this.id,
|
|
190
191
|
items: this.next
|
|
191
192
|
});
|
|
193
|
+
},
|
|
194
|
+
generation() {
|
|
195
|
+
this.next = this.getValidItems();
|
|
192
196
|
}
|
|
193
197
|
},
|
|
194
198
|
mounted() {
|
|
@@ -506,6 +510,14 @@ export default {
|
|
|
506
510
|
} else {
|
|
507
511
|
return this.renderings[widget._id];
|
|
508
512
|
}
|
|
513
|
+
},
|
|
514
|
+
getValidItems() {
|
|
515
|
+
return this.items.filter(item => {
|
|
516
|
+
if (!window.apos.modules[`${item.type}-widget`]) {
|
|
517
|
+
console.warn(`The widget type ${item.type} exists in the content but is not configured.`);
|
|
518
|
+
}
|
|
519
|
+
return window.apos.modules[`${item.type}-widget`];
|
|
520
|
+
});
|
|
509
521
|
}
|
|
510
522
|
}
|
|
511
523
|
};
|
|
@@ -101,6 +101,8 @@
|
|
|
101
101
|
:options="options.widgets[widget.type]"
|
|
102
102
|
:type="widget.type"
|
|
103
103
|
:doc-id="docId"
|
|
104
|
+
:focused="focused"
|
|
105
|
+
:key="generation"
|
|
104
106
|
/>
|
|
105
107
|
<component
|
|
106
108
|
v-else
|
|
@@ -114,6 +116,7 @@
|
|
|
114
116
|
@edit="$emit('edit', i);"
|
|
115
117
|
:doc-id="docId"
|
|
116
118
|
:rendering="rendering"
|
|
119
|
+
:key="generation"
|
|
117
120
|
/>
|
|
118
121
|
<div
|
|
119
122
|
class="apos-area-widget-controls apos-area-widget-controls--add apos-area-widget-controls--add--bottom"
|
|
@@ -199,6 +202,13 @@ export default {
|
|
|
199
202
|
disabled: {
|
|
200
203
|
type: Boolean,
|
|
201
204
|
default: false
|
|
205
|
+
},
|
|
206
|
+
generation: {
|
|
207
|
+
type: Number,
|
|
208
|
+
required: false,
|
|
209
|
+
default() {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
202
212
|
}
|
|
203
213
|
},
|
|
204
214
|
emits: [ 'clone', 'up', 'down', 'remove', 'edit', 'cut', 'copy', 'update', 'add', 'changed' ],
|
|
@@ -53,6 +53,7 @@ module.exports = {
|
|
|
53
53
|
'help-circle-icon': 'HelpCircle',
|
|
54
54
|
'information-outline-icon': 'InformationOutline',
|
|
55
55
|
'information-icon': 'Information',
|
|
56
|
+
'image-edit-outline': 'ImageEditOutline',
|
|
56
57
|
'image-icon': 'Image',
|
|
57
58
|
'image-size-select-actual-icon': 'ImageSizeSelectActual',
|
|
58
59
|
'play-box-icon': 'PlayBox',
|