apostrophe 3.26.0 → 3.27.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/CHANGELOG.md CHANGED
@@ -1,11 +1,31 @@
1
1
  # Changelog
2
2
 
3
- ## 3.26.0
3
+ ## 3.27.0 (2022-08-18)
4
+
5
+ ### Adds
6
+
7
+ * Add `/grid` `POST` route in permission module, in addition to the existing `GET` one.
8
+ * New utility script to help find excessively heavy npm dependencies of apostrophe core.
9
+
10
+ ### Changes
11
+
12
+ * Extract permission grid into `AposPermissionGrid` vue component.
13
+ * Moved `stylelint` from `dependencies` to `devDependencies`. The benefit may be small because many projects will depend on `stylelint` at project level, but every little bit helps install speed, and it may make a bigger difference if different major versions are in use.
14
+
15
+ ## 3.26.1 (2022-08-06)
16
+
17
+ ### Fixes
18
+
19
+ Hotfix: always waits for the DOM to be ready before initializing the Apostrophe Admin UI. `setTimeout` alone might not guarantee that every time. This issue has apparently become more frequent in the latest versions of Chrome.
20
+ * Modifies the `login` module to return an empty object in the API session cookie response body to avoid potential invalid JSON error if `response.json()` is retrieved.
21
+
22
+ ## 3.26.0 (2022-08-03)
4
23
 
5
24
  ### Adds
6
25
 
7
26
  * Tasks can now be registered with the `afterModuleReady` flag, which is more useful than `afterModuleInit` because it waits for the module to be more fully initialized, including all "improvements" loaded via npm. The original `afterModuleInit` flag is still supported in case someone was counting on its behavior.
8
27
  * Add `/grid` `POST` route in permission module, in addition to the existing `GET` one, to improve extensibility.
28
+ * `@apostrophecms/express:list-routes` command line task added, to facilitate debugging.
9
29
 
10
30
  ### Changes
11
31
 
@@ -41,8 +41,8 @@ module.exports = {
41
41
  // on the case, such as `@apostrophecms/global:editor` or
42
42
  // `@apostrophecms/page:manager`.
43
43
  //
44
- // Alternatively, an `href` option may be set to an ordinary URL in
45
- // `options`. This creates a basic link in the admin menu.
44
+ // TODO: Alternatively, an `href` option may be set to an ordinary
45
+ // URL in `options`. This creates a basic link in the admin menu.
46
46
  //
47
47
  // `permission` should be an object with `action` and `type`
48
48
  // properties. This determines visibility of the option, securing
@@ -59,13 +59,13 @@ module.exports = {
59
59
  // wish to implement a custom admin bar item not powered by
60
60
  // the `AposModals` app.
61
61
  //
62
- // If `options.contextUtility` is true the item will be displayed in a tray of
63
- // icons just to the left of the page settings gear. If `options.toggle` is also true,
62
+ // If `options.contextUtility` is true, the item will be displayed in a tray of
63
+ // icons just to the right of the login and/or locales menu. If `options.toggle` is also true,
64
64
  // then the button will have the `active` state until toggled
65
- // off again. `options.openTooltip` and `options.closeTooltip` may be
66
- // provided to offer a different tooltip during the active state. Otherwise
67
- // `options.tooltip` is used. The regular label is also present for
68
- // screenreaders only. The contextUtility functionality is typically used for
65
+ // off again. `options.tooltip.deactivate` and `options.tooltip.activate` may be
66
+ // provided to offer a different tooltip during the active versus inactive states,
67
+ // respectively. Otherwise, `options.tooltip` is used. The regular label is also present
68
+ // for screenreaders only. The contextUtility functionality is typically used for
69
69
  // experiences that temporarily change the current editing context.
70
70
  //
71
71
  // If an `options.when` function is provided, it will be invoked with
@@ -460,9 +460,14 @@ module.exports = {
460
460
  ${(tiptap && tiptap.registerCode) || ''}
461
461
  ` +
462
462
  (app ? stripIndent`
463
- setTimeout(() => {
464
- ${app.invokeCode}
465
- }, 0);
463
+ if (document.readyState !== 'loading') {
464
+ setTimeout(invoke, 0);
465
+ } else {
466
+ window.addEventListener('DOMContentLoaded', invoke);
467
+ }
468
+ function invoke() {
469
+ ${app.invokeCode}
470
+ }
466
471
  ` : '') +
467
472
  // No delay on these, they expect to run early like ui/public code
468
473
  // and the first ones invoked set up expected stuff like apos.http
@@ -1,5 +1,3 @@
1
- const StyleLintPlugin = require('stylelint-webpack-plugin');
2
-
3
1
  module.exports = (options, apos) => {
4
2
  return {
5
3
  module: {
@@ -58,11 +56,6 @@ module.exports = (options, apos) => {
58
56
  ]
59
57
  }
60
58
  ]
61
- },
62
- plugins: [
63
- new StyleLintPlugin({
64
- files: [ './node_modules/apostrophe/modules/**/*.{scss,vue}' ]
65
- })
66
- ]
59
+ }
67
60
  };
68
61
  };
@@ -168,6 +168,20 @@ module.exports = {
168
168
  self.apos.util.error('When you do so other modules will also pick up on it and make URLs absolute.');
169
169
  }
170
170
  },
171
+ tasks(self) {
172
+ return {
173
+ 'list-routes': {
174
+ help: 'List all Express routes registered via routes(), apiRoutes(), etc. (not directly via apos.app)',
175
+ async task(argv) {
176
+ for (const info of self.finalModuleMiddlewareAndRoutes) {
177
+ if (info.route) {
178
+ console.log(`${info.method.toUpperCase()} ${info.url}`);
179
+ }
180
+ }
181
+ }
182
+ }
183
+ };
184
+ },
171
185
  handlers(self) {
172
186
  return {
173
187
  'apostrophe:run': {
@@ -648,6 +648,7 @@ module.exports = {
648
648
  if (session) {
649
649
  await self.passportLogin(req, user);
650
650
  await self.clearLoginAttempts(user.username);
651
+ return {};
651
652
  } else {
652
653
  const token = cuid();
653
654
  await self.bearerTokens.insert({
@@ -16,48 +16,7 @@
16
16
  :wrapper-classes="[ 'apos-input__role' ]"
17
17
  @change="change"
18
18
  />
19
- <div class="apos-input__role__permission-grid">
20
- <div
21
- v-for="permissionSet in permissionSets"
22
- :key="permissionSet.name"
23
- class="apos-input__role__permission-grid__set"
24
- >
25
- <h4 class="apos-input__role__permission-grid__set-name">
26
- {{ $t(permissionSet.label) }}
27
- <AposIndicator
28
- v-if="permissionSet.includes"
29
- icon="help-circle-icon"
30
- class="apos-input__role__permission-grid__help"
31
- :tooltip="getTooltip(permissionSet.includes)"
32
- :icon-size="11"
33
- icon-color="var(--a-base-4)"
34
- />
35
- </h4>
36
- <dl class="apos-input__role__permission-grid__list">
37
- <div
38
- v-for="permission in permissionSet.permissions"
39
- :key="permission.name"
40
- class="apos-input__role__permission-grid__row"
41
- >
42
- <dd class="apos-input__role__permission-grid__value">
43
- <AposIndicator
44
- :icon="permission.value ? 'check-bold-icon' : 'close-icon'"
45
- :icon-color="permission.value ? 'var(--a-success)' : 'var(--a-base-5)'"
46
- />
47
- <span v-if="permission.value" class="apos-sr-only">
48
- {{ $t('apostrophe:enabled') }}
49
- </span>
50
- <span v-else class="apos-sr-only">
51
- {{ $t('apostrophe:disabled') }}
52
- </span>
53
- </dd>
54
- <dt class="apos-input__role__permission-grid__label">
55
- {{ $t(permission.label) }}
56
- </dt>
57
- </div>
58
- </dl>
59
- </div>
60
- </div>
19
+ <AposPermissionGrid :api-params="{ role: next }" />
61
20
  </template>
62
21
  </AposInputWrapper>
63
22
  </template>
@@ -68,24 +27,12 @@ import AposInputMixin from 'Modules/@apostrophecms/schema/mixins/AposInputMixin'
68
27
  export default {
69
28
  name: 'AposInputRole',
70
29
  mixins: [ AposInputMixin ],
71
- props: {
72
- icon: {
73
- type: String,
74
- default: 'menu-down-icon'
75
- }
76
- },
77
30
  data() {
78
31
  return {
79
32
  next: (this.value.data == null) ? null : this.value.data,
80
- choices: [],
81
- permissionSets: []
33
+ choices: []
82
34
  };
83
35
  },
84
- watch: {
85
- async next() {
86
- this.permissionSets = await this.getPermissionSets(this.next);
87
- }
88
- },
89
36
  async mounted() {
90
37
  // Add an null option if there isn't one already
91
38
  if (!this.field.required && !this.field.choices.find(choice => {
@@ -104,32 +51,8 @@ export default {
104
51
  this.next = this.field.choices[0].value;
105
52
  }
106
53
  });
107
- if (this.next) {
108
- this.permissionSets = await this.getPermissionSets(this.next);
109
- }
110
54
  },
111
55
  methods: {
112
- getTooltip(includes) {
113
- const html = document.createElement('div');
114
- html.setAttribute('class', 'apos-info');
115
- const list = document.createElement('ul');
116
- const intro = document.createElement('p');
117
- const followUp = document.createElement('p');
118
- intro.appendChild(document.createTextNode(this.$t('apostrophe:piecePermissionsIntro')));
119
- followUp.appendChild(document.createTextNode(this.$t('apostrophe:piecePermissionsPieceTypeList')));
120
- html.appendChild(intro);
121
- html.appendChild(followUp);
122
- includes.forEach(item => {
123
- const li = document.createElement('li');
124
- li.appendChild(document.createTextNode(this.$t(item)));
125
- list.appendChild(li);
126
- });
127
- html.appendChild(list);
128
- return {
129
- content: html,
130
- localize: false
131
- };
132
- },
133
56
  validate(value) {
134
57
  if (this.field.required && !value.length) {
135
58
  return 'required';
@@ -142,60 +65,7 @@ export default {
142
65
  change(value) {
143
66
  // Allows expression of non-string values
144
67
  this.next = this.choices.find(choice => choice.value === value).value;
145
- },
146
- async getPermissionSets(role) {
147
- const { permissionSets } = await apos.http.post(`${apos.permission.action}/grid`, {
148
- body: {
149
- role
150
- },
151
- busy: true
152
- });
153
-
154
- return permissionSets;
155
68
  }
156
69
  }
157
70
  };
158
71
  </script>
159
-
160
- <style lang="scss" scoped>
161
- .apos-input-icon {
162
- @include apos-transition();
163
- }
164
-
165
- .apos-input__role__permission-grid {
166
- @include type-base;
167
- display: grid;
168
- margin-top: $spacing-triple;
169
- grid-template-columns: repeat(auto-fit, minmax(50%, 1fr));
170
- }
171
-
172
- .apos-input__role__permission-grid__row {
173
- display: flex;
174
- align-items: center;
175
- padding-bottom: $spacing-three-quarters;
176
- margin-bottom: $spacing-three-quarters;
177
- border-bottom: 1px solid var(--a-base-9);
178
- }
179
- .apos-input__role__permission-grid__list {
180
- margin-top: 0;
181
- }
182
- .apos-input__role__permission-grid__set {
183
- padding: 0 $spacing-base;
184
- margin-bottom: $spacing-double;
185
- }
186
-
187
- .apos-input__role__permission-grid__set-name {
188
- @include type-title;
189
- display: inline-flex;
190
- margin: 0 0 $spacing-double;
191
- }
192
-
193
- .apos-input__role__permission-grid__value {
194
- display: inline-flex;
195
- margin: 0 $spacing-half 0 0;
196
- }
197
-
198
- .apos-input__role__permission-grid__help {
199
- margin-left: $spacing-half;
200
- }
201
- </style>
@@ -0,0 +1,169 @@
1
+ <template>
2
+ <div class="apos-input__role__permission-grid">
3
+ <div
4
+ v-for="permissionSet in permissionSets"
5
+ :key="permissionSet.name"
6
+ class="apos-input__role__permission-grid__set"
7
+ >
8
+ <h4 class="apos-input__role__permission-grid__set-name">
9
+ {{ $t(permissionSet.label) }}
10
+ <AposIndicator
11
+ v-if="permissionSet.includes"
12
+ icon="help-circle-icon"
13
+ class="apos-input__role__permission-grid__help"
14
+ :tooltip="getTooltip(permissionSet.includes)"
15
+ :icon-size="11"
16
+ icon-color="var(--a-base-4)"
17
+ />
18
+ </h4>
19
+ <dl class="apos-input__role__permission-grid__list">
20
+ <div
21
+ v-for="permission in permissionSet.permissions"
22
+ :key="permission.name"
23
+ class="apos-input__role__permission-grid__row"
24
+ >
25
+ <dd class="apos-input__role__permission-grid__value">
26
+ <AposIndicator
27
+ :icon="permission.value ? 'check-bold-icon' : 'close-icon'"
28
+ :icon-color="permission.value ? 'var(--a-success)' : 'var(--a-base-5)'"
29
+ />
30
+ <span v-if="permission.value" class="apos-sr-only">
31
+ {{ $t('apostrophe:enabled') }}
32
+ </span>
33
+ <span v-else class="apos-sr-only">
34
+ {{ $t('apostrophe:disabled') }}
35
+ </span>
36
+ </dd>
37
+ <dt class="apos-input__role__permission-grid__label">
38
+ {{ $t(permission.label) }}
39
+ </dt>
40
+ </div>
41
+ </dl>
42
+ </div>
43
+ </div>
44
+ </template>
45
+
46
+ <script>
47
+ export default {
48
+ name: 'AposPermissionGrid',
49
+ props: {
50
+ apiParams: {
51
+ type: Object,
52
+ required: true,
53
+ validator(value) {
54
+ if (typeof value !== 'object') {
55
+ return false;
56
+ }
57
+
58
+ const ROLE = 'role';
59
+ const ROLES_BY_TYPE = 'rolesByType';
60
+ const GROUPS = '_groups';
61
+
62
+ const keys = Object.keys(value);
63
+ const [ key ] = keys;
64
+
65
+ if (keys.length > 1 || ![ ROLE, ROLES_BY_TYPE, GROUPS ].includes(key)) {
66
+ return false;
67
+ }
68
+ if (value[ROLE] && typeof value[ROLE] !== 'string') {
69
+ return false;
70
+ }
71
+ if (value[ROLES_BY_TYPE] && !Array.isArray(value[ROLES_BY_TYPE])) {
72
+ return false;
73
+ }
74
+ if (value[GROUPS] && !Array.isArray(value[GROUPS])) {
75
+ return false;
76
+ }
77
+ return true;
78
+ }
79
+ }
80
+ },
81
+ data() {
82
+ return {
83
+ permissionSets: []
84
+ };
85
+ },
86
+ watch: {
87
+ apiParams: {
88
+ async handler() {
89
+ this.permissionSets = await this.getPermissionSets();
90
+ },
91
+ deep: true
92
+ }
93
+ },
94
+ async mounted() {
95
+ this.permissionSets = await this.getPermissionSets();
96
+ },
97
+ methods: {
98
+ getTooltip(includes) {
99
+ const html = document.createElement('div');
100
+ html.setAttribute('class', 'apos-info');
101
+ const list = document.createElement('ul');
102
+ const intro = document.createElement('p');
103
+ const followUp = document.createElement('p');
104
+ intro.appendChild(document.createTextNode(this.$t('apostrophe:piecePermissionsIntro')));
105
+ followUp.appendChild(document.createTextNode(this.$t('apostrophe:piecePermissionsPieceTypeList')));
106
+ html.appendChild(intro);
107
+ html.appendChild(followUp);
108
+ includes.forEach(item => {
109
+ const li = document.createElement('li');
110
+ li.appendChild(document.createTextNode(this.$t(item)));
111
+ list.appendChild(li);
112
+ });
113
+ html.appendChild(list);
114
+
115
+ return {
116
+ content: html,
117
+ localize: false
118
+ };
119
+ },
120
+ async getPermissionSets() {
121
+ const { permissionSets } = await apos.http.post(`${apos.permission.action}/grid`, {
122
+ body: this.apiParams,
123
+ busy: true
124
+ });
125
+
126
+ return permissionSets;
127
+ }
128
+ }
129
+ };
130
+ </script>
131
+
132
+ <style lang="scss" scoped>
133
+ .apos-input__role__permission-grid {
134
+ @include type-base;
135
+ display: grid;
136
+ margin-top: $spacing-triple;
137
+ grid-template-columns: repeat(auto-fit, minmax(50%, 1fr));
138
+ }
139
+
140
+ .apos-input__role__permission-grid__row {
141
+ display: flex;
142
+ align-items: center;
143
+ padding-bottom: $spacing-three-quarters;
144
+ margin-bottom: $spacing-three-quarters;
145
+ border-bottom: 1px solid var(--a-base-9);
146
+ }
147
+ .apos-input__role__permission-grid__list {
148
+ margin-top: 0;
149
+ }
150
+ .apos-input__role__permission-grid__set {
151
+ padding: 0 $spacing-base;
152
+ margin-bottom: $spacing-double;
153
+ }
154
+
155
+ .apos-input__role__permission-grid__set-name {
156
+ @include type-title;
157
+ display: inline-flex;
158
+ margin: 0 0 $spacing-double;
159
+ }
160
+
161
+ .apos-input__role__permission-grid__value {
162
+ display: inline-flex;
163
+ margin: 0 $spacing-half 0 0;
164
+ }
165
+
166
+ .apos-input__role__permission-grid__help {
167
+ margin-left: $spacing-half;
168
+ }
169
+ </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apostrophe",
3
- "version": "3.26.0",
3
+ "version": "3.27.0",
4
4
  "description": "The Apostrophe Content Management System.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -59,7 +59,6 @@
59
59
  "debounce-async": "0.0.2",
60
60
  "deep-get-set": "^1.1.1",
61
61
  "dompurify": "^2.3.1",
62
- "eslint-plugin-promise": "^5.1.0",
63
62
  "express": "^4.16.4",
64
63
  "express-bearer-token": "^2.4.0",
65
64
  "express-cache-on-demand": "^1.0.3",
@@ -102,10 +101,6 @@
102
101
  "sass-loader": "^10.1.1",
103
102
  "server-destroy": "^1.0.1",
104
103
  "sluggo": "^0.3.0",
105
- "stylelint": "^14.6.1",
106
- "stylelint-declaration-strict-value": "^1.8.0",
107
- "stylelint-order": "^5.0.0",
108
- "stylelint-webpack-plugin": "^3.2.0",
109
104
  "tinycolor2": "^1.4.2",
110
105
  "tough-cookie": "^4.0.0",
111
106
  "underscore.string": "^3.3.4",
@@ -134,7 +129,11 @@
134
129
  "nyc": "^15.1.0",
135
130
  "replace-in-file": "^6.1.0",
136
131
  "vue-eslint-parser": "^7.1.1",
137
- "webpack-bundle-analyzer": "^3.9.0"
132
+ "webpack-bundle-analyzer": "^3.9.0",
133
+ "eslint-plugin-promise": "^5.1.0",
134
+ "stylelint": "^14.6.1",
135
+ "stylelint-declaration-strict-value": "^1.8.0",
136
+ "stylelint-order": "^5.0.0"
138
137
  },
139
138
  "browserslist": [
140
139
  "ie >= 10"
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable node/no-path-concat */
3
+
4
+ const fs = require('fs');
5
+
6
+ const trueDeps = JSON.parse(fs.readFileSync(`${__dirname}/../package-lock.json`)).packages;
7
+ const deps = {};
8
+ for (let [ name, props ] of Object.entries(trueDeps)) {
9
+ if (props.dev) {
10
+ continue;
11
+ }
12
+ const lastIndex = name.lastIndexOf('node_modules/');
13
+ if (lastIndex !== -1) {
14
+ name = name.substring(lastIndex + 13);
15
+ }
16
+ deps[name] = props;
17
+ }
18
+ const costs = new Map();
19
+ for (const name of Object.keys(deps[''].dependencies)) {
20
+ costs.set(name, countWeight(name));
21
+ }
22
+
23
+ function countWeight(name) {
24
+ const subDeps = deps[name].dependencies || {};
25
+ let weight = 0;
26
+ for (const name of Object.keys(subDeps)) {
27
+ weight += countWeight(name);
28
+ }
29
+ return weight + 1;
30
+ }
31
+
32
+ const sorted = [ ...costs.entries() ].sort((a, b) => a[1] - b[1]);
33
+ for (const [ name, cost ] of sorted) {
34
+ console.log(`${name} has ${cost} sub-dependencies`);
35
+ }
36
+
37
+ const nonDevDeps = Object.keys(trueDeps).filter(name => !trueDeps[name].dev).length;
38
+ console.log(`Total dependencies: ${nonDevDeps}`);