apostrophe 4.8.1 → 4.9.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/.eslintrc +7 -6
- package/.github/workflows/main.yml +5 -5
- package/CHANGELOG.md +54 -2
- package/index.js +252 -102
- package/lib/moog-require.js +19 -15
- package/lib/moog.js +10 -9
- package/modules/@apostrophecms/admin-bar/index.js +3 -0
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBar.vue +2 -6
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarMenu.vue +5 -5
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +58 -14
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBreakpointPreviewMode.vue +201 -21
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextTitle.vue +4 -25
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextUndoRedo.vue +5 -12
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaExpandedMenu.vue +8 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaMenu.vue +9 -2
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +1 -1
- package/modules/@apostrophecms/asset/index.js +569 -878
- package/modules/@apostrophecms/asset/lib/build/external-module-api.js +745 -0
- package/modules/@apostrophecms/asset/lib/build/internals.js +553 -0
- package/modules/@apostrophecms/asset/lib/build/manager-apos.js +75 -0
- package/modules/@apostrophecms/asset/lib/build/manager-bundled.js +31 -0
- package/modules/@apostrophecms/asset/lib/build/manager-custom.js +62 -0
- package/modules/@apostrophecms/asset/lib/build/manager-index.js +72 -0
- package/modules/@apostrophecms/asset/lib/build/managers.js +20 -0
- package/modules/@apostrophecms/asset/lib/build/task.js +837 -0
- package/modules/@apostrophecms/asset/lib/build/utils.js +199 -0
- package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.scss.js +21 -10
- package/modules/@apostrophecms/asset/lib/webpack/postcss-replace-viewport-units-plugin.js +40 -0
- package/modules/@apostrophecms/asset/lib/webpack/src/webpack.scss.js +23 -11
- package/modules/@apostrophecms/asset/lib/webpack/utils.js +13 -5
- package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuShortcut.vue +1 -0
- package/modules/@apostrophecms/doc/index.js +11 -7
- package/modules/@apostrophecms/doc-type/index.js +41 -3
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +20 -1
- package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +1 -0
- package/modules/@apostrophecms/express/index.js +6 -4
- package/modules/@apostrophecms/i18n/i18n/de.json +119 -3
- package/modules/@apostrophecms/i18n/i18n/en.json +3 -1
- package/modules/@apostrophecms/i18n/i18n/es.json +157 -1
- package/modules/@apostrophecms/i18n/i18n/fr.json +143 -1
- package/modules/@apostrophecms/i18n/i18n/it.json +77 -4
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +160 -1
- package/modules/@apostrophecms/i18n/i18n/sk.json +139 -1
- package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +2 -0
- package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +1 -1
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +4 -1
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerDisplay.vue +8 -1
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaUploader.vue +6 -1
- package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +65 -29
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalBody.vue +0 -1
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +2 -0
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalShareDraft.vue +1 -0
- package/modules/@apostrophecms/modal/ui/apos/composables/AposFocus.js +7 -2
- package/modules/@apostrophecms/page/ui/apos/components/AposPagesManager.vue +2 -0
- package/modules/@apostrophecms/permission/index.js +10 -10
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +2 -0
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposRelationshipEditor.vue +1 -0
- package/modules/@apostrophecms/schema/index.js +173 -36
- package/modules/@apostrophecms/schema/lib/addFieldTypes.js +36 -13
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +13 -11
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +9 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +45 -5
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js +41 -4
- package/modules/@apostrophecms/schema/ui/apos/logic/AposSearchList.js +25 -6
- package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputChoicesMixin.js +47 -20
- package/modules/@apostrophecms/template/index.js +157 -11
- package/modules/@apostrophecms/template/lib/bundlesLoader.js +45 -8
- package/modules/@apostrophecms/template/views/outerLayoutBase.html +4 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +21 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposButtonSplit.vue +8 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +11 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuDialog.vue +5 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuItem.vue +32 -4
- package/modules/@apostrophecms/ui/ui/apos/components/AposLoading.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposSpinner.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/package.json +4 -0
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_breakpoint_preview.scss +5 -2
- package/modules/@apostrophecms/ui/ui/apos/scss/mixins/_responsive.scss +2 -2
- package/modules/@apostrophecms/ui/ui/apos/scss/mixins/_theme_mixins.scss +10 -12
- package/modules/@apostrophecms/ui/ui/apos/utils/index.js +2 -2
- package/modules/@apostrophecms/util/index.js +10 -3
- package/modules/@apostrophecms/widget-type/index.js +5 -0
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +1 -0
- package/package.json +12 -11
- package/test/asset-external.js +263 -0
- package/test/assets.js +22 -17
- package/test/common-js.js +42 -0
- package/test/docs.js +32 -0
- package/test/esm-project/app.js +16 -0
- package/test/esm-project/esm.js +50 -0
- package/test/esm-project/package.json +16 -0
- package/test/extra_node_modules/before-global/index.js +4 -0
- package/test/log.js +6 -9
- package/test/modules/@apostrophecms/home-page/ui/src/main.js +6 -0
- package/test/modules/@apostrophecms/home-page/ui/src/topic.js +6 -0
- package/test/modules/article-page/index.js +4 -0
- package/test/modules/article-page/ui/src/index.js +6 -0
- package/test/modules/article-page/ui/src/main.js +6 -0
- package/test/modules/article-page/ui/src/main.scss +3 -0
- package/test/modules/article-widget/index.js +4 -0
- package/test/modules/article-widget/ui/src/carousel.js +6 -0
- package/test/modules/article-widget/ui/src/carousel.scss +4 -0
- package/test/modules/article-widget/ui/src/topic.js +6 -0
- package/test/modules/inject-test/index.js +81 -0
- package/test/modules/inject-test/views/appendDevTest.html +1 -0
- package/test/modules/inject-test/views/appendDevViteTest.html +1 -0
- package/test/modules/inject-test/views/appendDevWebpackTest.html +1 -0
- package/test/modules/inject-test/views/appendProdWebpackTest.html +1 -0
- package/test/modules/inject-test/views/prependDevTest.html +1 -0
- package/test/modules/inject-test/views/prependDevViteTest.html +1 -0
- package/test/modules/inject-test/views/prependDevWebpackTest.html +1 -0
- package/test/modules/inject-test/views/prependProdTest.html +1 -0
- package/test/modules/inject-test/views/prependViteTest.html +1 -0
- package/test/modules/inject-test/views/prependWebpackTest.html +1 -0
- package/test/modules/selected-article-widget/index.js +4 -0
- package/test/modules/selected-article-widget/ui/src/tabs.js +6 -0
- package/test/modules/test-before/index.js +4 -0
- package/test/modules/with-layout-page/views/page.html +6 -0
- package/test/modules-order.js +167 -0
- package/test/moog.js +45 -45
- package/test/postcss.js +64 -0
- package/test/relationships.js +224 -0
- package/test/templates.js +46 -2
- package/test/utils.js +11 -2
- package/test-lib/test.js +49 -45
package/.eslintrc
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"apos": true
|
|
11
11
|
},
|
|
12
12
|
"rules": {
|
|
13
|
+
"max-len": "off",
|
|
13
14
|
"no-var": "error",
|
|
14
15
|
"no-console": 0,
|
|
15
16
|
"vue/no-deprecated-v-on-native-modifier": 0,
|
|
@@ -56,12 +57,12 @@
|
|
|
56
57
|
{
|
|
57
58
|
"files": "*.vue",
|
|
58
59
|
"globals": {
|
|
59
|
-
"defineProps": "
|
|
60
|
-
"defineEmits": "
|
|
61
|
-
"defineExpose": "
|
|
62
|
-
"defineOptions": "
|
|
63
|
-
"defineModel": "
|
|
64
|
-
"defineSlots": "
|
|
60
|
+
"defineProps": "readonly",
|
|
61
|
+
"defineEmits": "readonly",
|
|
62
|
+
"defineExpose": "readonly",
|
|
63
|
+
"defineOptions": "readonly",
|
|
64
|
+
"defineModel": "readonly",
|
|
65
|
+
"defineSlots": "readonly"
|
|
65
66
|
}
|
|
66
67
|
},
|
|
67
68
|
{
|
|
@@ -18,21 +18,21 @@ jobs:
|
|
|
18
18
|
runs-on: ubuntu-latest
|
|
19
19
|
strategy:
|
|
20
20
|
matrix:
|
|
21
|
-
node-version: [18, 20]
|
|
22
|
-
mongodb-version: [
|
|
21
|
+
node-version: [18, 20, 22]
|
|
22
|
+
mongodb-version: [6.0, 7.0, 8.0]
|
|
23
23
|
|
|
24
24
|
# Steps represent a sequence of tasks that will be executed as part of the job
|
|
25
25
|
steps:
|
|
26
26
|
- name: Git checkout
|
|
27
|
-
uses: actions/checkout@
|
|
27
|
+
uses: actions/checkout@v4
|
|
28
28
|
|
|
29
29
|
- name: Use Node.js ${{ matrix.node-version }}
|
|
30
|
-
uses: actions/setup-node@
|
|
30
|
+
uses: actions/setup-node@v4
|
|
31
31
|
with:
|
|
32
32
|
node-version: ${{ matrix.node-version }}
|
|
33
33
|
|
|
34
34
|
- name: Start MongoDB
|
|
35
|
-
uses: supercharge/mongodb-github-action@1.
|
|
35
|
+
uses: supercharge/mongodb-github-action@1.11.0
|
|
36
36
|
with:
|
|
37
37
|
mongodb-version: ${{ matrix.mongodb-version }}
|
|
38
38
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,60 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 4.
|
|
3
|
+
## 4.9.0 (2024-10-31)
|
|
4
|
+
|
|
5
|
+
### Adds
|
|
6
|
+
|
|
7
|
+
* Relationship inputs have aria accessibility tags and autocomplete suggestions can be controlled by keyboard.
|
|
8
|
+
* Elements inside modals can have a `data-apos-focus-priority` attribute that prioritizes them inside the focusable elements list.
|
|
9
|
+
* Modals will continute trying to find focusable elements until an element marked `data-apos-focus-priority` appears or the max retry threshold is reached.
|
|
10
|
+
* Takes care of an edge case where Media Manager would duplicate search results.
|
|
11
|
+
* Add support for ESM projects.
|
|
12
|
+
* Modules can now have a `before: "module-name"` property in their configuration to initialize them before another module, bypassing the normal
|
|
13
|
+
order implied by `defaults.js` and `app.js`.
|
|
14
|
+
* `select` and `checkboxes` fields that implement dynamic choices can now take into account the value of other fields on the fly, by specifying
|
|
15
|
+
a `following` property with an array of other field names. Array and object subfields can access properties of the parent document
|
|
16
|
+
by adding a `<` prefix (or more than one) to field names in `following` to look upwards a level. Your custom method on the server side will
|
|
17
|
+
now receive a `following` object as an additional argument. One limitation: for now, a field with dynamic choices cannot depend on another field
|
|
18
|
+
with dynamic choices in this way.
|
|
19
|
+
* Adds AI-generated missing translations
|
|
20
|
+
* Adds the mobile preview dropdown for non visibles breakpoints. Uses the new `shortcut` property to display breakpoints out of the dropdown.
|
|
21
|
+
* Adds possibility to have two icons in a button.
|
|
22
|
+
* Breakpoint preview only targets `[data-apos-refreshable]`.
|
|
23
|
+
* Adds a `isActive` state to context menu items. Also adds possibility to add icons to context menu items.
|
|
24
|
+
* Add a postcss plugin to handle `vh` and `vw` values on breakpoint preview mode.
|
|
25
|
+
* Adds inject component `when` condition with possible values `hmr`, `prod`, and `dev`. Modules should explicitely register their components with the same `when` value and the condition should be met to inject the component.
|
|
26
|
+
* Adds inject `bundler` registration condition. It's in use only when registering a component and will be evaluated on runtime. The value should match the current build module (`webpack` or the external build module alias).
|
|
27
|
+
* Adds new development task `@apostrophecms/asset:reset` to reset the asset build cache and all build artifacts.
|
|
28
|
+
* Revamps the `@apostrophecms/asset` module to enable bundling via build modules.
|
|
29
|
+
* Adds `apos.asset.devServerUrl()` nunjucks helper to get the (bundle) dev server URL when available.
|
|
30
|
+
* The asset module has a new option, `options.hmr` that accepts `public` (default), `apos` or `false` to enable HMR for the public bundle or the admin UI bundle or disable it respectively. This configuration works only with external build modules that support HMR.
|
|
31
|
+
* The asset module has a new option, `options.hmrPort` that accepts an integer (default `null`) to specify the HMR WS port. If not specified, the default express port is used. This configuration works only with external build modules that support HMR WS.
|
|
32
|
+
* The asset module has a new option, `options.productionSourceMaps` that accepts a boolean (default `false`) to enable source maps in production. This configuration works only with external build modules that support source maps.
|
|
4
33
|
|
|
5
|
-
|
|
34
|
+
### Changes
|
|
35
|
+
|
|
36
|
+
* Silence deprecation warnings from Sass 1.80+ regarding the use of `@import`. The Sass team [has stated there will be a two-year transition period](https://sass-lang.com/documentation/breaking-changes/import/#transition-period) before the feature is actually removed. The use of `@import` is common practice in the Apostrophe codebase and in many project codebases. We will arrange for an orderly migration to the new `@use` directive before Sass 3.x appears.
|
|
37
|
+
* Move saving indicator after breakpoint preview.
|
|
38
|
+
* Internal methods `mergeConfiguration`, `autodetectBundles`, `lintModules`, `nestedModuleSubdirs` and `testDir` are now async.
|
|
39
|
+
* `express.getSessionOptions` is now async.
|
|
40
|
+
|
|
41
|
+
### Fixes
|
|
42
|
+
|
|
43
|
+
* Modifies the `AposAreaMenu.vue` component to set the `disabled` attribute to `true` if the max number of widgets have been added in an area with `expanded: true`.
|
|
44
|
+
* `pnpm: true` option in `app.js` is no longer breaking the application.
|
|
45
|
+
* Remove unused `vue-template-compiler` dependency.
|
|
46
|
+
* Prevent un-publishing the `@apostrophecms/global` doc and more generally all singletons.
|
|
47
|
+
* When opening a context menu while another is already opened, prevent from focusing the button of the first one instead of the newly opened menu.
|
|
48
|
+
* Updates `isEqual` method of `area` field type to avoid comparing an area having temporary properties with one having none.
|
|
49
|
+
* In a relationship field, when asking for sub relationships using `withRelationships` an dot notion.
|
|
50
|
+
If this is done in combination with a projection, this projection is updated to add the id storage fields of the needed relationships for the whole `withRelationships` path.
|
|
51
|
+
* The admin UI no longer fails to function when the HTML page is rendered with a direct `sendPage` call and there is no current "in context" page or piece.
|
|
52
|
+
|
|
53
|
+
## 4.7.2 and 4.8.1 (2024-10-09)
|
|
54
|
+
|
|
55
|
+
### Fixes
|
|
56
|
+
|
|
57
|
+
* Correct a race condition that can cause a crash at startup when custom `uploadfs` options are present in some specific cloud environments e.g. when using Azure Blob Storage.
|
|
6
58
|
|
|
7
59
|
## 4.8.0 (2024-10-03)
|
|
8
60
|
|
package/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// this should be loaded first
|
|
2
2
|
const opentelemetry = require('./lib/opentelemetry');
|
|
3
3
|
const path = require('path');
|
|
4
|
+
const url = require('url');
|
|
4
5
|
const _ = require('lodash');
|
|
5
6
|
const argv = require('boring')({ end: true });
|
|
6
7
|
const fs = require('fs');
|
|
@@ -10,6 +11,7 @@ const { cpus } = require('os');
|
|
|
10
11
|
const process = require('process');
|
|
11
12
|
const npmResolve = require('resolve');
|
|
12
13
|
const glob = require('./lib/glob.js');
|
|
14
|
+
const moogRequire = require('./lib/moog-require');
|
|
13
15
|
let defaults = require('./defaults.js');
|
|
14
16
|
|
|
15
17
|
// ## Top-level options
|
|
@@ -212,8 +214,8 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
212
214
|
try {
|
|
213
215
|
const matches = process.version.match(/^v(\d+)/);
|
|
214
216
|
const version = parseInt(matches[1]);
|
|
215
|
-
if (version <
|
|
216
|
-
throw new Error('Apostrophe
|
|
217
|
+
if (version < 18) {
|
|
218
|
+
throw new Error('Apostrophe requires at least Node.js 18.x.');
|
|
217
219
|
}
|
|
218
220
|
// The core must have a reference to itself in order to use the
|
|
219
221
|
// promise event emitter code
|
|
@@ -225,10 +227,27 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
225
227
|
Object.assign(self, require('./modules/@apostrophecms/module/lib/events.js')(self));
|
|
226
228
|
|
|
227
229
|
// Determine root module and root directory
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
|
|
231
|
+
const {
|
|
232
|
+
root,
|
|
233
|
+
rootDir,
|
|
234
|
+
npmRootDir,
|
|
235
|
+
selfDir
|
|
236
|
+
} = buildRoot(options);
|
|
237
|
+
self.root = root;
|
|
238
|
+
self.rootDir = rootDir;
|
|
239
|
+
self.npmRootDir = npmRootDir;
|
|
240
|
+
self.selfDir = selfDir;
|
|
241
|
+
self.getNpmPath = (name) => {
|
|
242
|
+
try {
|
|
243
|
+
return getNpmPath(name, self.npmRootDir);
|
|
244
|
+
} catch (e) {
|
|
245
|
+
// Not found via npm. This does not mean it doesn't
|
|
246
|
+
// exist as a project-level thing
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
232
251
|
// Signals to various (build related) places that we are running a pnpm installation.
|
|
233
252
|
// The relevant option, if set, has a higher precedence over the automated check.
|
|
234
253
|
self.isPnpm = options.pnpm ??
|
|
@@ -236,8 +255,8 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
236
255
|
|
|
237
256
|
testModule();
|
|
238
257
|
|
|
239
|
-
self.options = mergeConfiguration(options, defaults);
|
|
240
|
-
autodetectBundles();
|
|
258
|
+
self.options = await mergeConfiguration(options, defaults);
|
|
259
|
+
await autodetectBundles();
|
|
241
260
|
acceptGlobalOptions();
|
|
242
261
|
|
|
243
262
|
// Module-based async events (self.on and self.emit of each module,
|
|
@@ -271,8 +290,8 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
271
290
|
// your own piece types
|
|
272
291
|
|
|
273
292
|
self.instancesOf = function(name) {
|
|
274
|
-
return _.filter(self.modules, function(
|
|
275
|
-
return self.synth.instanceOf(
|
|
293
|
+
return _.filter(self.modules, function(apostropheModule) {
|
|
294
|
+
return self.synth.instanceOf(apostropheModule, name);
|
|
276
295
|
});
|
|
277
296
|
};
|
|
278
297
|
|
|
@@ -292,10 +311,10 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
292
311
|
self.aliasEvent('modulesReady', 'modulesRegistered');
|
|
293
312
|
self.aliasEvent('afterInit', 'ready');
|
|
294
313
|
|
|
295
|
-
defineModules();
|
|
314
|
+
await defineModules();
|
|
296
315
|
|
|
297
316
|
await instantiateModules();
|
|
298
|
-
lintModules();
|
|
317
|
+
await lintModules();
|
|
299
318
|
await self.emit('modulesRegistered'); // formerly modulesReady
|
|
300
319
|
self.apos.schema.validateAllSchemas();
|
|
301
320
|
self.apos.schema.registerAllSchemas();
|
|
@@ -303,9 +322,9 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
303
322
|
await self.apos.migration.migrate(self.argv);
|
|
304
323
|
// Inserts the global doc in the default locale if it does not exist; same for other
|
|
305
324
|
// singleton piece types registered by other modules
|
|
306
|
-
for (const
|
|
307
|
-
if (self.instanceOf(
|
|
308
|
-
await
|
|
325
|
+
for (const apostropheModule of Object.values(self.modules)) {
|
|
326
|
+
if (self.instanceOf(apostropheModule, '@apostrophecms/piece-type') && apostropheModule.options.singletonAuto) {
|
|
327
|
+
await apostropheModule.insertIfMissing();
|
|
309
328
|
}
|
|
310
329
|
}
|
|
311
330
|
await self.apos.page.implementParkAllInDefaultLocale();
|
|
@@ -337,14 +356,14 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
337
356
|
// SUPPORTING FUNCTIONS BEGIN HERE
|
|
338
357
|
|
|
339
358
|
// Merge configuration from defaults, data/local.js and app.js
|
|
340
|
-
function mergeConfiguration(options, defaults) {
|
|
359
|
+
async function mergeConfiguration(options, defaults) {
|
|
341
360
|
let config = {};
|
|
342
361
|
let local = {};
|
|
343
362
|
const localPath = options.__localPath || '/data/local.js';
|
|
344
363
|
const reallyLocalPath = self.rootDir + localPath;
|
|
345
364
|
|
|
346
365
|
if (fs.existsSync(reallyLocalPath)) {
|
|
347
|
-
local =
|
|
366
|
+
local = await self.root.import(reallyLocalPath);
|
|
348
367
|
}
|
|
349
368
|
|
|
350
369
|
// Otherwise making a second apos instance
|
|
@@ -369,29 +388,14 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
369
388
|
return config;
|
|
370
389
|
}
|
|
371
390
|
|
|
372
|
-
function
|
|
373
|
-
let _module = module;
|
|
374
|
-
let m = _module;
|
|
375
|
-
while (m.parent && m.parent.filename) {
|
|
376
|
-
// The test file is the root as far as we are concerned,
|
|
377
|
-
// not mocha itself
|
|
378
|
-
if (m.parent.filename.match(/\/node_modules\/mocha\//)) {
|
|
379
|
-
return m;
|
|
380
|
-
}
|
|
381
|
-
m = m.parent;
|
|
382
|
-
_module = m;
|
|
383
|
-
}
|
|
384
|
-
return _module;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function nestedModuleSubdirs() {
|
|
391
|
+
async function nestedModuleSubdirs() {
|
|
388
392
|
if (!options.nestedModuleSubdirs) {
|
|
389
393
|
return;
|
|
390
394
|
}
|
|
391
395
|
const configs = glob(self.localModules + '/**/modules.js', { follow: true });
|
|
392
|
-
|
|
396
|
+
for (const config of configs) {
|
|
393
397
|
try {
|
|
394
|
-
_.merge(self.options.modules,
|
|
398
|
+
_.merge(self.options.modules, await self.root.import(config));
|
|
395
399
|
} catch (e) {
|
|
396
400
|
console.error(stripIndent`
|
|
397
401
|
When nestedModuleSubdirs is active, any modules.js file beneath:
|
|
@@ -408,39 +412,37 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
408
412
|
`);
|
|
409
413
|
throw e;
|
|
410
414
|
}
|
|
411
|
-
}
|
|
415
|
+
}
|
|
412
416
|
}
|
|
413
417
|
|
|
414
|
-
function autodetectBundles() {
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
const
|
|
418
|
-
if (!
|
|
419
|
-
|
|
418
|
+
async function autodetectBundles() {
|
|
419
|
+
const apostropheModules = Object.keys(self.options.modules);
|
|
420
|
+
for (const apostropheModuleName of apostropheModules) {
|
|
421
|
+
const npmPath = self.getNpmPath(apostropheModuleName);
|
|
422
|
+
if (!npmPath) {
|
|
423
|
+
continue;
|
|
420
424
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
425
|
+
|
|
426
|
+
const apostropheModule = await self.root.import(npmPath);
|
|
427
|
+
if (apostropheModule.bundle) {
|
|
428
|
+
self.options.bundles = (self.options.bundles || []).concat(apostropheModuleName);
|
|
429
|
+
const bundleModules = apostropheModule.bundle.modules;
|
|
430
|
+
for (const bundleModuleName of bundleModules) {
|
|
431
|
+
if (!apostropheModules.includes(bundleModuleName)) {
|
|
432
|
+
const bundledModule = await self.root.import(
|
|
433
|
+
path.resolve(
|
|
434
|
+
path.dirname(npmPath),
|
|
435
|
+
apostropheModule.bundle.directory,
|
|
436
|
+
bundleModuleName,
|
|
437
|
+
'index.js'
|
|
438
|
+
)
|
|
439
|
+
);
|
|
427
440
|
if (bundledModule.improve) {
|
|
428
|
-
self.options.modules[
|
|
441
|
+
self.options.modules[bundleModuleName] = {};
|
|
429
442
|
}
|
|
430
443
|
}
|
|
431
|
-
}
|
|
444
|
+
}
|
|
432
445
|
}
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function getNpmPath(name) {
|
|
437
|
-
const parentPath = path.resolve(self.npmRootDir);
|
|
438
|
-
try {
|
|
439
|
-
return npmResolve.sync(name, { basedir: parentPath });
|
|
440
|
-
} catch (e) {
|
|
441
|
-
// Not found via npm. This does not mean it doesn't
|
|
442
|
-
// exist as a project-level thing
|
|
443
|
-
return null;
|
|
444
446
|
}
|
|
445
447
|
}
|
|
446
448
|
|
|
@@ -499,9 +501,10 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
499
501
|
port: 7900,
|
|
500
502
|
secret: 'irrelevant'
|
|
501
503
|
});
|
|
502
|
-
const m =
|
|
504
|
+
const m = self.root;
|
|
505
|
+
checkTestModule();
|
|
503
506
|
// Allow tests to be in test/ or in tests/
|
|
504
|
-
const testDir =
|
|
507
|
+
const testDir = path.dirname(m.filename);
|
|
505
508
|
const moduleDir = testDir.replace(/\/tests?$/, '');
|
|
506
509
|
if (testDir === moduleDir) {
|
|
507
510
|
throw new Error('Test file must be in test/ or tests/ subdirectory of module');
|
|
@@ -518,33 +521,21 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
518
521
|
fs.mkdirSync(testDir + '/node_modules' + pkgNamespace, { recursive: true });
|
|
519
522
|
fs.symlinkSync(moduleDir, testDir + '/node_modules/' + pkgName, 'dir');
|
|
520
523
|
}
|
|
521
|
-
|
|
522
|
-
// Not quite superfluous: it'll return self.root, but
|
|
523
|
-
// it also makes sure we encounter mocha along the way
|
|
524
|
+
// Makes sure we encounter mocha along the way
|
|
524
525
|
// and throws an exception if we don't
|
|
525
|
-
function
|
|
526
|
-
let m = module;
|
|
526
|
+
function checkTestModule() {
|
|
527
527
|
const testFor = `node_modules${path.sep}mocha`;
|
|
528
528
|
if (!require.main.filename.includes(testFor)) {
|
|
529
529
|
throw new Error('mocha does not seem to be running, is this really a test?');
|
|
530
530
|
}
|
|
531
|
-
while (m) {
|
|
532
|
-
if (m.parent && m.parent.filename.includes(testFor)) {
|
|
533
|
-
return m;
|
|
534
|
-
} else if (!m.parent) {
|
|
535
|
-
// Mocha v10 doesn't inject mocha paths inside `module`, therefore, we only detect the parent until the last parent. But we can get Mocha running using `require.main` - Amin
|
|
536
|
-
return m;
|
|
537
|
-
}
|
|
538
|
-
m = m.parent;
|
|
539
|
-
}
|
|
540
531
|
}
|
|
541
532
|
}
|
|
542
533
|
|
|
543
|
-
function defineModules() {
|
|
534
|
+
async function defineModules() {
|
|
544
535
|
// Set moog-require up to create our module manager objects
|
|
545
536
|
|
|
546
537
|
self.localModules = self.options.modulesSubdir || self.options.__testLocalModules || (self.rootDir + '/modules');
|
|
547
|
-
const synth =
|
|
538
|
+
const synth = await moogRequire({
|
|
548
539
|
root: self.root,
|
|
549
540
|
bundles: [ 'apostrophe' ].concat(self.options.bundles || []),
|
|
550
541
|
localModules: self.localModules,
|
|
@@ -557,7 +548,9 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
557
548
|
'icons',
|
|
558
549
|
'i18n',
|
|
559
550
|
'webpack',
|
|
560
|
-
'
|
|
551
|
+
'build',
|
|
552
|
+
'commands',
|
|
553
|
+
'before'
|
|
561
554
|
]
|
|
562
555
|
});
|
|
563
556
|
|
|
@@ -569,11 +562,11 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
569
562
|
self.redefine = self.synth.redefine;
|
|
570
563
|
self.create = self.synth.create;
|
|
571
564
|
|
|
572
|
-
nestedModuleSubdirs();
|
|
565
|
+
await nestedModuleSubdirs();
|
|
573
566
|
|
|
574
|
-
|
|
575
|
-
synth.define(name, options);
|
|
576
|
-
}
|
|
567
|
+
for (const [ name, options ] of Object.entries(self.options.modules)) {
|
|
568
|
+
await synth.define(name, options);
|
|
569
|
+
}
|
|
577
570
|
|
|
578
571
|
// Apostrophe prefers that any improvements to @apostrophecms/global
|
|
579
572
|
// be applied before any project level version of @apostrophecms/global
|
|
@@ -582,12 +575,95 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
582
575
|
return synth;
|
|
583
576
|
}
|
|
584
577
|
|
|
578
|
+
// Reorder modules based on their `before` property.
|
|
579
|
+
async function sortModules(moduleNames) {
|
|
580
|
+
// The module names that have a `before` property
|
|
581
|
+
const beforeModules = [];
|
|
582
|
+
// The metadata quick access of all modules
|
|
583
|
+
const modules = {};
|
|
584
|
+
// Recursion guard
|
|
585
|
+
const recursionGuard = {};
|
|
586
|
+
// The sorted modules result
|
|
587
|
+
const sorted = [];
|
|
588
|
+
|
|
589
|
+
// The base module sort metadata
|
|
590
|
+
for (const name of moduleNames) {
|
|
591
|
+
const metadata = await self.synth.getMetadata(name);
|
|
592
|
+
const before = Object.values(metadata.before).reverse().find(name => typeof name === 'string');
|
|
593
|
+
if (before) {
|
|
594
|
+
beforeModules.push(name);
|
|
595
|
+
}
|
|
596
|
+
modules[name] = {
|
|
597
|
+
before,
|
|
598
|
+
beforeSelf: []
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Loop through the modules that have a `before` property,
|
|
603
|
+
// validate and fill the initial `beforeSelf` metadata (first pass).
|
|
604
|
+
for (const name of beforeModules) {
|
|
605
|
+
const m = modules[name];
|
|
606
|
+
const before = m.before;
|
|
607
|
+
if (m.before === name) {
|
|
608
|
+
throw new Error(`Module "${name}" has a 'before' property that references itself.`);
|
|
609
|
+
}
|
|
610
|
+
if (!modules[before]) {
|
|
611
|
+
throw new Error(`Module "${name}" has a 'before' property that references a non-existent module: "${before}".`);
|
|
612
|
+
}
|
|
613
|
+
// Add the current module name to the target's beforeSelf.
|
|
614
|
+
modules[before].beforeSelf.push(name);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Loop through the modules that have a `before` properties
|
|
618
|
+
// now that we have the initial metadata (second pass).
|
|
619
|
+
// This takes care of edge cases like `before` that points to another module
|
|
620
|
+
// that has a `before` property itself, circular `before` references, etc.
|
|
621
|
+
// in a very predictable way.
|
|
622
|
+
for (const name of beforeModules) {
|
|
623
|
+
const m = modules[name];
|
|
624
|
+
const target = modules[m.before];
|
|
625
|
+
if (!target) {
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
// Add all the modules that want to be before this one to the target's beforeSelf.
|
|
629
|
+
// Do this recursively for every module from the beforeSelf array that has own `beforeSelf` members.
|
|
630
|
+
addBeforeSelfRecursive(name, m.beforeSelf, target.beforeSelf);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Fill in the sorted array, first wins when uniquefy-ing.
|
|
634
|
+
for (const name of moduleNames) {
|
|
635
|
+
sorted.push(...modules[name].beforeSelf, name);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// A unique array of sorted module names.
|
|
639
|
+
return [ ...new Set(sorted) ];
|
|
640
|
+
|
|
641
|
+
function addBeforeSelfRecursive(moduleName, beforeSelf, target) {
|
|
642
|
+
if (beforeSelf.length === 0) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
if (recursionGuard[moduleName]) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
recursionGuard[moduleName] = true;
|
|
649
|
+
|
|
650
|
+
beforeSelf.forEach((name) => {
|
|
651
|
+
if (recursionGuard[name]) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
target.unshift(name);
|
|
655
|
+
addBeforeSelfRecursive(name, modules[name].beforeSelf, target);
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
585
660
|
async function instantiateModules() {
|
|
586
661
|
self.modules = {};
|
|
587
|
-
|
|
662
|
+
const sorted = await sortModules(modulesToBeInstantiated());
|
|
663
|
+
for (const item of sorted) {
|
|
588
664
|
// module registers itself in self.modules
|
|
589
|
-
const
|
|
590
|
-
await
|
|
665
|
+
const apostropheModule = await self.synth.create(item, { apos: self });
|
|
666
|
+
await apostropheModule.emit('moduleReady');
|
|
591
667
|
}
|
|
592
668
|
}
|
|
593
669
|
|
|
@@ -598,10 +674,10 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
598
674
|
});
|
|
599
675
|
}
|
|
600
676
|
|
|
601
|
-
function lintModules() {
|
|
677
|
+
async function lintModules() {
|
|
602
678
|
const validSteps = [];
|
|
603
|
-
for (const
|
|
604
|
-
for (const step of
|
|
679
|
+
for (const apostropheModule of Object.values(self.modules)) {
|
|
680
|
+
for (const step of apostropheModule.__meta.chain) {
|
|
605
681
|
validSteps.push(step.name);
|
|
606
682
|
}
|
|
607
683
|
}
|
|
@@ -616,19 +692,19 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
616
692
|
const nsDirs = fs.readdirSync(`${self.localModules}/${dir}`);
|
|
617
693
|
for (let nsDir of nsDirs) {
|
|
618
694
|
nsDir = `${dir}/${nsDir}`;
|
|
619
|
-
testDir(nsDir);
|
|
695
|
+
await testDir(nsDir);
|
|
620
696
|
}
|
|
621
697
|
} else {
|
|
622
698
|
testDir(dir);
|
|
623
699
|
}
|
|
624
700
|
}
|
|
625
|
-
function testDir(name) {
|
|
701
|
+
async function testDir(name) {
|
|
626
702
|
// Projects that have different theme modules activated at different times
|
|
627
703
|
// are a frequent source of false positives for this warning, so ignore
|
|
628
704
|
// seemingly unused modules with "theme" in the name
|
|
629
705
|
if (!validSteps.includes(name)) {
|
|
630
706
|
try {
|
|
631
|
-
const submodule =
|
|
707
|
+
const submodule = await self.root.import(path.resolve(self.localModules, name, 'index.js'));
|
|
632
708
|
if (submodule && submodule.options && submodule.options.ignoreUnusedFolderWarning) {
|
|
633
709
|
return;
|
|
634
710
|
}
|
|
@@ -666,7 +742,7 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
666
742
|
}
|
|
667
743
|
}
|
|
668
744
|
|
|
669
|
-
for (const [ name,
|
|
745
|
+
for (const [ name, apostropheModule ] of Object.entries(self.modules)) {
|
|
670
746
|
if (name.match(/^apostrophe-/)) {
|
|
671
747
|
self.util.warnDevOnce(
|
|
672
748
|
'namespace-apostrophe-modules',
|
|
@@ -691,17 +767,17 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
691
767
|
);
|
|
692
768
|
}
|
|
693
769
|
|
|
694
|
-
if (
|
|
770
|
+
if (apostropheModule.options.extends && ((typeof apostropheModule.options.extends) === 'string')) {
|
|
695
771
|
lint(`The module ${name} contains an "extends" option. This is probably a\nmistake. In Apostrophe "extend" is used to extend other modules.`);
|
|
696
772
|
}
|
|
697
|
-
if (
|
|
698
|
-
lint(`The module ${name} extends ${
|
|
773
|
+
if (apostropheModule.options.singletonWarningIfNot && (name !== apostropheModule.options.singletonWarningIfNot)) {
|
|
774
|
+
lint(`The module ${name} extends ${apostropheModule.options.singletonWarningIfNot}, which is normally\na singleton (Apostrophe creates only one instance of it). Two competing\ninstances will lead to problems. If you are adding project-level code to it,\njust use modules/${apostropheModule.options.singletonWarningIfNot}/index.js and do not use "extend".\nIf you are improving it via an npm module, use "improve" rather than "extend".\nIf neither situation applies you should probably just make a new module that does\nnot extend anything.\n\nIf you are sure you know what you are doing, you can set the\nsingletonWarningIfNot: false option for this module.`);
|
|
699
775
|
}
|
|
700
|
-
if (name.match(/-widget$/) && (!extending(
|
|
776
|
+
if (name.match(/-widget$/) && (!extending(apostropheModule)) && (!apostropheModule.options.ignoreNoExtendWarning)) {
|
|
701
777
|
lint(`The module ${name} does not extend anything.\n\nA -widget module usually extends @apostrophecms/widget-type or another widget type.\nOr possibly you forgot to npm install something.\n\nIf you are sure you are doing the right thing, set the\nignoreNoExtendWarning option to true for this module.`);
|
|
702
|
-
} else if (name.match(/-page$/) && (name !== '@apostrophecms/page') && (!extending(
|
|
778
|
+
} else if (name.match(/-page$/) && (name !== '@apostrophecms/page') && (!extending(apostropheModule)) && (!apostropheModule.options.ignoreNoExtendWarning)) {
|
|
703
779
|
lint(`The module ${name} does not extend anything.\n\nA -page module usually extends @apostrophecms/page-type or\n@apostrophecms/piece-page-type or another page type.\nOr possibly you forgot to npm install something.\n\nIf you are sure you are doing the right thing, set the\nignoreNoExtendWarning option to true for this module.`);
|
|
704
|
-
} else if ((!extending(
|
|
780
|
+
} else if ((!extending(apostropheModule)) && (!hasCode(name)) && (!isBundle(name)) && (!apostropheModule.options.ignoreNoCodeWarning)) {
|
|
705
781
|
lint(`The module ${name} does not extend anything and does not have any code.\n\nThis usually means that you:\n\n1. Forgot to "extend" another module\n2. Configured a module that comes from npm without npm installing it\n3. Simply haven't written your "index.js" yet\n\nIf you really want a module with no code, set the ignoreNoCodeWarning option\nto true for this module.`);
|
|
706
782
|
}
|
|
707
783
|
}
|
|
@@ -738,12 +814,12 @@ async function apostrophe(options, telemetry, rootSpan) {
|
|
|
738
814
|
const d = self.synth.definitions[name];
|
|
739
815
|
return d.bundle || (d.extend && d.extend.bundle);
|
|
740
816
|
}
|
|
741
|
-
function extending(
|
|
817
|
+
function extending(apostropheModule) {
|
|
742
818
|
// If the module extends no other module, then it will
|
|
743
819
|
// have up to four entries in its inheritance chain:
|
|
744
820
|
// project level self, npm level self, `apostrophe-modules`
|
|
745
821
|
// project-level and `apostrophe-modules` npm level.
|
|
746
|
-
return
|
|
822
|
+
return apostropheModule.__meta.chain.length > 4;
|
|
747
823
|
}
|
|
748
824
|
|
|
749
825
|
function lint(s) {
|
|
@@ -775,3 +851,77 @@ function respawn(worker) {
|
|
|
775
851
|
console.error(`Respawning worker process ${worker.process.pid}`);
|
|
776
852
|
clusterFork();
|
|
777
853
|
}
|
|
854
|
+
|
|
855
|
+
module.exports.buildRoot = buildRoot;
|
|
856
|
+
|
|
857
|
+
function buildRoot(options) {
|
|
858
|
+
const root = getRoot(options);
|
|
859
|
+
const rootDir = options.rootDir || path.dirname(root.filename);
|
|
860
|
+
const npmRootDir = options.npmRootDir || rootDir;
|
|
861
|
+
const selfDir = __dirname;
|
|
862
|
+
|
|
863
|
+
return {
|
|
864
|
+
root,
|
|
865
|
+
rootDir,
|
|
866
|
+
npmRootDir,
|
|
867
|
+
selfDir
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
function getRoot(options) {
|
|
871
|
+
const root = options.root;
|
|
872
|
+
if (root?.filename && root?.require) {
|
|
873
|
+
return {
|
|
874
|
+
filename: root.filename,
|
|
875
|
+
import: async (id) => root.require(id),
|
|
876
|
+
require: (id) => root.require(id)
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
if (root?.url) {
|
|
881
|
+
// Apostrophe was started from an ESM project
|
|
882
|
+
const filename = url.fileURLToPath(root.url);
|
|
883
|
+
const dynamicImport = async (id) => {
|
|
884
|
+
const { default: defaultExport, ...rest } = await import(id);
|
|
885
|
+
|
|
886
|
+
return defaultExport || rest;
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
return {
|
|
890
|
+
filename,
|
|
891
|
+
import: dynamicImport,
|
|
892
|
+
require: (id) => {
|
|
893
|
+
console.warn(`self.apos.root.require is now async, please verify that you await the promise (${id})`);
|
|
894
|
+
|
|
895
|
+
return dynamicImport(id);
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Legacy commonjs logic
|
|
901
|
+
function getLegacyRoot() {
|
|
902
|
+
let _module = module;
|
|
903
|
+
let m = _module;
|
|
904
|
+
while (m.parent && m.parent.filename) {
|
|
905
|
+
// The test file is the root as far as we are concerned,
|
|
906
|
+
// not mocha itself
|
|
907
|
+
if (m.parent.filename.match(/\/node_modules\/mocha\//)) {
|
|
908
|
+
return m;
|
|
909
|
+
}
|
|
910
|
+
m = m.parent;
|
|
911
|
+
_module = m;
|
|
912
|
+
}
|
|
913
|
+
return _module;
|
|
914
|
+
}
|
|
915
|
+
const legacyRoot = getLegacyRoot();
|
|
916
|
+
return {
|
|
917
|
+
filename: legacyRoot.filename,
|
|
918
|
+
import: async (id) => legacyRoot.require(id),
|
|
919
|
+
require: (id) => legacyRoot.require(id)
|
|
920
|
+
};
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
module.exports.getNpmPath = getNpmPath;
|
|
924
|
+
|
|
925
|
+
function getNpmPath(name, baseDir) {
|
|
926
|
+
return npmResolve.sync(name, { basedir: path.resolve(baseDir) });
|
|
927
|
+
}
|