@directus/api 14.1.2 → 15.0.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/dist/app.js +3 -3
- package/dist/auth/drivers/oauth2.js +2 -3
- package/dist/auth/drivers/openid.js +2 -3
- package/dist/cli/commands/schema/apply.js +44 -33
- package/dist/cli/index.js +2 -2
- package/dist/database/index.d.ts +2 -1
- package/dist/database/index.js +2 -1
- package/dist/database/system-data/relations/relations.yaml +4 -0
- package/dist/emitter.d.ts +1 -0
- package/dist/emitter.js +2 -1
- package/dist/env.d.ts +1 -0
- package/dist/env.js +6 -0
- package/dist/extensions/lib/get-shared-deps-mapping.js +1 -1
- package/dist/extensions/lib/sandbox/register/route.d.ts +1 -0
- package/dist/extensions/manager.d.ts +5 -0
- package/dist/extensions/manager.js +42 -16
- package/dist/logger.d.ts +2 -1
- package/dist/logger.js +1 -0
- package/dist/middleware/rate-limiter-ip.js +14 -11
- package/dist/redis/create-redis.d.ts +7 -0
- package/dist/redis/create-redis.js +12 -0
- package/dist/redis/index.d.ts +2 -0
- package/dist/redis/index.js +2 -0
- package/dist/redis/use-redis.d.ts +16 -0
- package/dist/redis/use-redis.js +22 -0
- package/dist/server.d.ts +2 -0
- package/dist/services/extensions.js +1 -1
- package/dist/services/graphql/index.js +50 -15
- package/dist/services/payload.js +3 -3
- package/dist/services/server.js +2 -3
- package/dist/services/specifications.js +3 -3
- package/dist/services/users.js +6 -6
- package/dist/telemetry/index.d.ts +4 -0
- package/dist/telemetry/index.js +4 -0
- package/dist/telemetry/lib/get-report.d.ts +5 -0
- package/dist/telemetry/lib/get-report.js +42 -0
- package/dist/telemetry/lib/init-telemetry.d.ts +11 -0
- package/dist/telemetry/lib/init-telemetry.js +30 -0
- package/dist/telemetry/lib/send-report.d.ts +5 -0
- package/dist/telemetry/lib/send-report.js +23 -0
- package/dist/telemetry/lib/track.d.ts +10 -0
- package/dist/telemetry/lib/track.js +31 -0
- package/dist/telemetry/types/report.d.ts +58 -0
- package/dist/telemetry/types/report.js +1 -0
- package/dist/telemetry/utils/get-item-count.d.ts +26 -0
- package/dist/telemetry/utils/get-item-count.js +36 -0
- package/dist/telemetry/utils/get-random-wait-time.d.ts +5 -0
- package/dist/telemetry/utils/get-random-wait-time.js +5 -0
- package/dist/telemetry/utils/get-user-count.d.ts +7 -0
- package/dist/telemetry/utils/get-user-count.js +30 -0
- package/dist/telemetry/utils/get-user-item-count.d.ts +13 -0
- package/dist/telemetry/utils/get-user-item-count.js +18 -0
- package/dist/utils/apply-query.js +13 -2
- package/dist/utils/get-cache-key.js +1 -1
- package/dist/utils/get-ip-from-req.d.ts +1 -1
- package/dist/utils/get-ip-from-req.js +1 -1
- package/dist/utils/get-snapshot.js +1 -1
- package/dist/utils/get-versioned-hash.js +1 -1
- package/dist/utils/md.d.ts +1 -1
- package/dist/utils/md.js +3 -2
- package/dist/utils/validate-query.js +1 -0
- package/dist/utils/validate-snapshot.js +3 -3
- package/dist/websocket/controllers/base.d.ts +2 -0
- package/dist/websocket/controllers/graphql.d.ts +2 -0
- package/dist/websocket/controllers/graphql.js +1 -1
- package/dist/websocket/controllers/index.d.ts +2 -0
- package/dist/websocket/controllers/rest.d.ts +2 -0
- package/dist/websocket/types.d.ts +3 -1
- package/package.json +108 -109
- package/dist/utils/package.d.ts +0 -2
- package/dist/utils/package.js +0 -6
- package/dist/utils/telemetry.d.ts +0 -1
- package/dist/utils/telemetry.js +0 -23
package/dist/app.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { InvalidPayloadError, ServiceUnavailableError } from '@directus/errors';
|
|
1
2
|
import { handlePressure } from '@directus/pressure';
|
|
2
3
|
import cookieParser from 'cookie-parser';
|
|
3
4
|
import express from 'express';
|
|
@@ -40,7 +41,6 @@ import webhooksRouter from './controllers/webhooks.js';
|
|
|
40
41
|
import { isInstalled, validateDatabaseConnection, validateDatabaseExtensions, validateMigrations, } from './database/index.js';
|
|
41
42
|
import emitter from './emitter.js';
|
|
42
43
|
import env from './env.js';
|
|
43
|
-
import { InvalidPayloadError, ServiceUnavailableError } from '@directus/errors';
|
|
44
44
|
import { getExtensionManager } from './extensions/index.js';
|
|
45
45
|
import { getFlowManager } from './flows.js';
|
|
46
46
|
import logger, { expressLogger } from './logger.js';
|
|
@@ -56,11 +56,11 @@ import rateLimiter from './middleware/rate-limiter-ip.js';
|
|
|
56
56
|
import sanitizeQuery from './middleware/sanitize-query.js';
|
|
57
57
|
import schema from './middleware/schema.js';
|
|
58
58
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
59
|
-
import { collectTelemetry } from './utils/telemetry.js';
|
|
60
59
|
import { Url } from './utils/url.js';
|
|
61
60
|
import { validateEnv } from './utils/validate-env.js';
|
|
62
61
|
import { validateStorage } from './utils/validate-storage.js';
|
|
63
62
|
import { init as initWebhooks } from './webhooks.js';
|
|
63
|
+
import { initTelemetry } from './telemetry/index.js';
|
|
64
64
|
const require = createRequire(import.meta.url);
|
|
65
65
|
export default async function createApp() {
|
|
66
66
|
const helmet = await import('helmet');
|
|
@@ -234,7 +234,7 @@ export default async function createApp() {
|
|
|
234
234
|
await emitter.emitInit('routes.after', { app });
|
|
235
235
|
// Register all webhooks
|
|
236
236
|
await initWebhooks();
|
|
237
|
-
|
|
237
|
+
initTelemetry();
|
|
238
238
|
await emitter.emitInit('app.after', { app });
|
|
239
239
|
return app;
|
|
240
240
|
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { isDirectusError } from '@directus/errors';
|
|
1
|
+
import { ErrorCode, InvalidCredentialsError, InvalidProviderConfigError, InvalidProviderError, InvalidTokenError, isDirectusError, ServiceUnavailableError, } from '@directus/errors';
|
|
2
2
|
import { parseJSON } from '@directus/utils';
|
|
3
3
|
import express, { Router } from 'express';
|
|
4
|
-
import flatten from 'flat';
|
|
4
|
+
import { flatten } from 'flat';
|
|
5
5
|
import jwt from 'jsonwebtoken';
|
|
6
6
|
import { errors, generators, Issuer } from 'openid-client';
|
|
7
7
|
import { getAuthProvider } from '../../auth.js';
|
|
8
8
|
import getDatabase from '../../database/index.js';
|
|
9
9
|
import emitter from '../../emitter.js';
|
|
10
10
|
import env from '../../env.js';
|
|
11
|
-
import { ErrorCode, InvalidCredentialsError, InvalidProviderError, InvalidProviderConfigError, InvalidTokenError, ServiceUnavailableError, } from '@directus/errors';
|
|
12
11
|
import logger from '../../logger.js';
|
|
13
12
|
import { respond } from '../../middleware/respond.js';
|
|
14
13
|
import { AuthenticationService } from '../../services/authentication.js';
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { isDirectusError } from '@directus/errors';
|
|
1
|
+
import { ErrorCode, InvalidCredentialsError, InvalidProviderConfigError, InvalidProviderError, InvalidTokenError, isDirectusError, ServiceUnavailableError, } from '@directus/errors';
|
|
2
2
|
import { parseJSON } from '@directus/utils';
|
|
3
3
|
import express, { Router } from 'express';
|
|
4
|
-
import flatten from 'flat';
|
|
4
|
+
import { flatten } from 'flat';
|
|
5
5
|
import jwt from 'jsonwebtoken';
|
|
6
6
|
import { errors, generators, Issuer } from 'openid-client';
|
|
7
7
|
import { getAuthProvider } from '../../auth.js';
|
|
8
8
|
import getDatabase from '../../database/index.js';
|
|
9
9
|
import emitter from '../../emitter.js';
|
|
10
10
|
import env from '../../env.js';
|
|
11
|
-
import { ErrorCode, InvalidCredentialsError, InvalidProviderError, InvalidProviderConfigError, InvalidTokenError, ServiceUnavailableError, } from '@directus/errors';
|
|
12
11
|
import logger from '../../logger.js';
|
|
13
12
|
import { respond } from '../../middleware/respond.js';
|
|
14
13
|
import { AuthenticationService } from '../../services/authentication.js';
|
|
@@ -41,92 +41,90 @@ export async function apply(snapshotPath, options) {
|
|
|
41
41
|
const dryRun = options?.dryRun === true;
|
|
42
42
|
const promptForChanges = !dryRun && options?.yes !== true;
|
|
43
43
|
if (dryRun || promptForChanges) {
|
|
44
|
-
|
|
44
|
+
const sections = [];
|
|
45
45
|
if (snapshotDiff.collections.length > 0) {
|
|
46
|
-
|
|
46
|
+
const lines = [chalk.underline.bold('Collections:')];
|
|
47
47
|
for (const { collection, diff } of snapshotDiff.collections) {
|
|
48
48
|
if (diff[0]?.kind === DiffKind.EDIT) {
|
|
49
|
-
|
|
49
|
+
lines.push(` - ${chalk.magenta('Update')} ${collection}`);
|
|
50
50
|
for (const change of diff) {
|
|
51
51
|
if (change.kind === DiffKind.EDIT) {
|
|
52
|
-
const path = change.path
|
|
53
|
-
|
|
52
|
+
const path = formatPath(change.path);
|
|
53
|
+
lines.push(` - Set ${path} to ${change.rhs}`);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
else if (diff[0]?.kind === DiffKind.DELETE) {
|
|
58
|
-
|
|
58
|
+
lines.push(` - ${chalk.red('Delete')} ${collection}`);
|
|
59
59
|
}
|
|
60
60
|
else if (diff[0]?.kind === DiffKind.NEW) {
|
|
61
|
-
|
|
61
|
+
lines.push(` - ${chalk.green('Create')} ${collection}`);
|
|
62
62
|
}
|
|
63
63
|
else if (diff[0]?.kind === DiffKind.ARRAY) {
|
|
64
|
-
|
|
64
|
+
lines.push(` - ${chalk.magenta('Update')} ${collection}`);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
sections.push(lines.join('\n'));
|
|
67
68
|
}
|
|
68
69
|
if (snapshotDiff.fields.length > 0) {
|
|
69
|
-
|
|
70
|
+
const lines = [chalk.underline.bold('Fields:')];
|
|
70
71
|
for (const { collection, field, diff } of snapshotDiff.fields) {
|
|
71
72
|
if (diff[0]?.kind === DiffKind.EDIT || isNestedMetaUpdate(diff[0])) {
|
|
72
|
-
|
|
73
|
+
lines.push(` - ${chalk.magenta('Update')} ${collection}.${field}`);
|
|
73
74
|
for (const change of diff) {
|
|
74
|
-
const path = change.path
|
|
75
|
+
const path = formatPath(change.path);
|
|
75
76
|
if (change.kind === DiffKind.EDIT) {
|
|
76
|
-
|
|
77
|
+
lines.push(` - Set ${path} to ${change.rhs}`);
|
|
77
78
|
}
|
|
78
79
|
else if (change.kind === DiffKind.DELETE) {
|
|
79
|
-
|
|
80
|
+
lines.push(` - Remove ${path}`);
|
|
80
81
|
}
|
|
81
82
|
else if (change.kind === DiffKind.NEW) {
|
|
82
|
-
|
|
83
|
+
lines.push(` - Add ${path} and set it to ${change.rhs}`);
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
else if (diff[0]?.kind === DiffKind.DELETE) {
|
|
87
|
-
|
|
88
|
+
lines.push(` - ${chalk.red('Delete')} ${collection}.${field}`);
|
|
88
89
|
}
|
|
89
90
|
else if (diff[0]?.kind === DiffKind.NEW) {
|
|
90
|
-
|
|
91
|
+
lines.push(` - ${chalk.green('Create')} ${collection}.${field}`);
|
|
91
92
|
}
|
|
92
93
|
else if (diff[0]?.kind === DiffKind.ARRAY) {
|
|
93
|
-
|
|
94
|
+
lines.push(` - ${chalk.magenta('Update')} ${collection}.${field}`);
|
|
94
95
|
}
|
|
95
96
|
}
|
|
97
|
+
sections.push(lines.join('\n'));
|
|
96
98
|
}
|
|
97
99
|
if (snapshotDiff.relations.length > 0) {
|
|
98
|
-
|
|
100
|
+
const lines = [chalk.underline.bold('Relations:')];
|
|
99
101
|
for (const { collection, field, related_collection, diff } of snapshotDiff.relations) {
|
|
102
|
+
const relatedCollection = formatRelatedCollection(related_collection);
|
|
100
103
|
if (diff[0]?.kind === DiffKind.EDIT) {
|
|
101
|
-
|
|
104
|
+
lines.push(` - ${chalk.magenta('Update')} ${collection}.${field}${relatedCollection}`);
|
|
102
105
|
for (const change of diff) {
|
|
103
106
|
if (change.kind === DiffKind.EDIT) {
|
|
104
|
-
const path = change.path
|
|
105
|
-
|
|
107
|
+
const path = formatPath(change.path);
|
|
108
|
+
lines.push(` - Set ${path} to ${change.rhs}`);
|
|
106
109
|
}
|
|
107
110
|
}
|
|
108
111
|
}
|
|
109
112
|
else if (diff[0]?.kind === DiffKind.DELETE) {
|
|
110
|
-
|
|
113
|
+
lines.push(` - ${chalk.red('Delete')} ${collection}.${field}${relatedCollection}`);
|
|
111
114
|
}
|
|
112
115
|
else if (diff[0]?.kind === DiffKind.NEW) {
|
|
113
|
-
|
|
116
|
+
lines.push(` - ${chalk.green('Create')} ${collection}.${field}${relatedCollection}`);
|
|
114
117
|
}
|
|
115
118
|
else if (diff[0]?.kind === DiffKind.ARRAY) {
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
// Related collection doesn't exist for a2o relationship types
|
|
122
|
-
if (related_collection) {
|
|
123
|
-
message += `-> ${related_collection}`;
|
|
119
|
+
lines.push(` - ${chalk.magenta('Update')} ${collection}.${field}${relatedCollection}`);
|
|
124
120
|
}
|
|
125
121
|
}
|
|
122
|
+
sections.push(lines.join('\n'));
|
|
126
123
|
}
|
|
127
|
-
message = 'The following changes will be applied:\n\n' +
|
|
124
|
+
const message = 'The following changes will be applied:\n\n' + sections.join('\n\n');
|
|
128
125
|
if (dryRun) {
|
|
129
|
-
|
|
126
|
+
// eslint-disable-next-line no-console
|
|
127
|
+
console.log(message);
|
|
130
128
|
process.exit(0);
|
|
131
129
|
}
|
|
132
130
|
const { proceed } = await inquirer.prompt([
|
|
@@ -151,3 +149,16 @@ export async function apply(snapshotPath, options) {
|
|
|
151
149
|
process.exit(1);
|
|
152
150
|
}
|
|
153
151
|
}
|
|
152
|
+
function formatPath(path) {
|
|
153
|
+
if (path.length === 1) {
|
|
154
|
+
return path.toString();
|
|
155
|
+
}
|
|
156
|
+
return path.slice(1).join('.');
|
|
157
|
+
}
|
|
158
|
+
function formatRelatedCollection(relatedCollection) {
|
|
159
|
+
// Related collection doesn't exist for a2o relationship types
|
|
160
|
+
if (relatedCollection) {
|
|
161
|
+
return ` → ${relatedCollection}`;
|
|
162
|
+
}
|
|
163
|
+
return '';
|
|
164
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command, Option } from 'commander';
|
|
2
|
+
import { version } from 'directus/version';
|
|
2
3
|
import emitter from '../emitter.js';
|
|
3
4
|
import { startServer } from '../server.js';
|
|
4
|
-
import * as pkg from '../utils/package.js';
|
|
5
5
|
import bootstrap from './commands/bootstrap/index.js';
|
|
6
6
|
import count from './commands/count/index.js';
|
|
7
7
|
import dbInstall from './commands/database/install.js';
|
|
@@ -20,7 +20,7 @@ export async function createCli() {
|
|
|
20
20
|
await loadExtensions();
|
|
21
21
|
await emitter.emitInit('cli.before', { program });
|
|
22
22
|
program.name('directus').usage('[command] [options]');
|
|
23
|
-
program.version(
|
|
23
|
+
program.version(version, '-v, --version');
|
|
24
24
|
program.command('start').description('Start the Directus API').action(startServer);
|
|
25
25
|
program.command('init').description('Create a new Directus Project').action(init);
|
|
26
26
|
// Security
|
package/dist/database/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { SchemaInspector } from '@directus/schema';
|
|
2
2
|
import type { Knex } from 'knex';
|
|
3
3
|
import type { DatabaseClient } from '../types/index.js';
|
|
4
|
-
export default
|
|
4
|
+
export default getDatabase;
|
|
5
|
+
export declare function getDatabase(): Knex;
|
|
5
6
|
export declare function getSchemaInspector(): SchemaInspector;
|
|
6
7
|
/**
|
|
7
8
|
* Get database version. Value currently exists for MySQL only.
|
package/dist/database/index.js
CHANGED
|
@@ -17,7 +17,8 @@ let database = null;
|
|
|
17
17
|
let inspector = null;
|
|
18
18
|
let databaseVersion = null;
|
|
19
19
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
-
export default
|
|
20
|
+
export default getDatabase;
|
|
21
|
+
export function getDatabase() {
|
|
21
22
|
if (database) {
|
|
22
23
|
return database;
|
|
23
24
|
}
|
|
@@ -96,6 +96,10 @@ data:
|
|
|
96
96
|
many_field: public_background
|
|
97
97
|
one_collection: directus_files
|
|
98
98
|
|
|
99
|
+
- many_collection: directus_settings
|
|
100
|
+
many_field: public_favicon
|
|
101
|
+
one_collection: directus_files
|
|
102
|
+
|
|
99
103
|
- many_collection: directus_settings
|
|
100
104
|
many_field: storage_default_folder
|
|
101
105
|
one_collection: directus_folders
|
package/dist/emitter.d.ts
CHANGED
package/dist/emitter.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ee2 from 'eventemitter2';
|
|
2
|
-
import logger from './logger.js';
|
|
3
2
|
import getDatabase from './database/index.js';
|
|
3
|
+
import logger from './logger.js';
|
|
4
4
|
export class Emitter {
|
|
5
5
|
filterEmitter;
|
|
6
6
|
actionEmitter;
|
|
@@ -84,4 +84,5 @@ export class Emitter {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
const emitter = new Emitter();
|
|
87
|
+
export const useEmitter = () => emitter;
|
|
87
88
|
export default emitter;
|
package/dist/env.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
export declare const defaults: Record<string, any>;
|
|
6
6
|
declare let env: Record<string, any>;
|
|
7
7
|
export default env;
|
|
8
|
+
export declare const useEnv: () => Record<string, any>;
|
|
8
9
|
/**
|
|
9
10
|
* When changes have been made during runtime, like in the CLI, we can refresh the env object with
|
|
10
11
|
* the newly created variables
|
package/dist/env.js
CHANGED
|
@@ -160,6 +160,7 @@ const allowedEnvironmentVars = [
|
|
|
160
160
|
'PACKAGE_FILE_LOCATION',
|
|
161
161
|
'EXTENSIONS_LOCATION',
|
|
162
162
|
'EXTENSIONS_PATH',
|
|
163
|
+
'EXTENSIONS_MUST_LOAD',
|
|
163
164
|
'EXTENSIONS_AUTO_RELOAD',
|
|
164
165
|
'EXTENSIONS_CACHE_TTL',
|
|
165
166
|
'EXTENSIONS_SANDBOX_MEMORY',
|
|
@@ -196,6 +197,8 @@ const allowedEnvironmentVars = [
|
|
|
196
197
|
'ADMIN_PASSWORD',
|
|
197
198
|
// telemetry
|
|
198
199
|
'TELEMETRY',
|
|
200
|
+
'TELEMETRY_URL',
|
|
201
|
+
'TELEMETRY_AUTHORIZATION',
|
|
199
202
|
// limits & optimization
|
|
200
203
|
'RELATIONAL_BATCH_SIZE',
|
|
201
204
|
'EXPORT_BATCH_SIZE',
|
|
@@ -260,6 +263,7 @@ export const defaults = {
|
|
|
260
263
|
AUTH_DISABLE_DEFAULT: false,
|
|
261
264
|
PACKAGE_FILE_LOCATION: '.',
|
|
262
265
|
EXTENSIONS_PATH: './extensions',
|
|
266
|
+
EXTENSIONS_MUST_LOAD: false,
|
|
263
267
|
EXTENSIONS_AUTO_RELOAD: false,
|
|
264
268
|
EXTENSIONS_SANDBOX_MEMORY: 100,
|
|
265
269
|
EXTENSIONS_SANDBOX_TIMEOUT: 1000,
|
|
@@ -269,6 +273,7 @@ export const defaults = {
|
|
|
269
273
|
EMAIL_SENDMAIL_NEW_LINE: 'unix',
|
|
270
274
|
EMAIL_SENDMAIL_PATH: '/usr/sbin/sendmail',
|
|
271
275
|
TELEMETRY: true,
|
|
276
|
+
TELEMETRY_URL: 'https://telemetry.directus.io',
|
|
272
277
|
ASSETS_CACHE_TTL: '30d',
|
|
273
278
|
ASSETS_TRANSFORM_MAX_CONCURRENT: 25,
|
|
274
279
|
ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION: 6000,
|
|
@@ -336,6 +341,7 @@ let env = {
|
|
|
336
341
|
process.env = env;
|
|
337
342
|
env = processValues(env);
|
|
338
343
|
export default env;
|
|
344
|
+
export const useEnv = () => env;
|
|
339
345
|
/**
|
|
340
346
|
* When changes have been made during runtime, like in the CLI, we can refresh the env object with
|
|
341
347
|
* the newly created variables
|
|
@@ -12,7 +12,7 @@ export const getSharedDepsMapping = async (deps) => {
|
|
|
12
12
|
const appDir = await readdir(path.join(resolvePackage('@directus/app', __dirname), 'dist', 'assets'));
|
|
13
13
|
const depsMapping = {};
|
|
14
14
|
for (const dep of deps) {
|
|
15
|
-
const depRegex = new RegExp(`${escapeRegExp(dep.replace(/\//g, '_'))}\\.[
|
|
15
|
+
const depRegex = new RegExp(`${escapeRegExp(dep.replace(/\//g, '_'))}\\.[a-zA-Z0-9_-]{8}\\.entry\\.js`);
|
|
16
16
|
const depName = appDir.find((file) => depRegex.test(file));
|
|
17
17
|
if (depName) {
|
|
18
18
|
const depUrl = new Url(env['PUBLIC_URL']).addPath('admin', 'assets', depName);
|
|
@@ -155,4 +155,9 @@ export declare class ExtensionManager {
|
|
|
155
155
|
* Remove the registration for all API extensions
|
|
156
156
|
*/
|
|
157
157
|
private unregisterApiExtensions;
|
|
158
|
+
/**
|
|
159
|
+
* If extensions must load successfully, any errors will cause the process to exit.
|
|
160
|
+
* Otherwise, the error will only be logged as a warning.
|
|
161
|
+
*/
|
|
162
|
+
private handleExtensionError;
|
|
158
163
|
}
|
|
@@ -27,6 +27,7 @@ import { getSchema } from '../utils/get-schema.js';
|
|
|
27
27
|
import { importFileUrl } from '../utils/import-file-url.js';
|
|
28
28
|
import { JobQueue } from '../utils/job-queue.js';
|
|
29
29
|
import { scheduleSynchronizedJob, validateCron } from '../utils/schedule.js';
|
|
30
|
+
import { toBoolean } from '../utils/to-boolean.js';
|
|
30
31
|
import { getExtensionsPath } from './lib/get-extensions-path.js';
|
|
31
32
|
import { getExtensionsSettings } from './lib/get-extensions-settings.js';
|
|
32
33
|
import { getExtensions } from './lib/get-extensions.js';
|
|
@@ -146,8 +147,7 @@ export class ExtensionManager {
|
|
|
146
147
|
this.extensionsSettings = await getExtensionsSettings(this.extensions);
|
|
147
148
|
}
|
|
148
149
|
catch (error) {
|
|
149
|
-
|
|
150
|
-
logger.warn(error);
|
|
150
|
+
this.handleExtensionError({ error, reason: `Couldn't load extensions` });
|
|
151
151
|
}
|
|
152
152
|
await this.registerHooks();
|
|
153
153
|
await this.registerEndpoints();
|
|
@@ -292,7 +292,7 @@ export class ExtensionManager {
|
|
|
292
292
|
find: name,
|
|
293
293
|
replacement: path,
|
|
294
294
|
}));
|
|
295
|
-
const entrypoint = generateExtensionsEntrypoint(this.extensions);
|
|
295
|
+
const entrypoint = generateExtensionsEntrypoint(this.extensions, this.extensionsSettings);
|
|
296
296
|
try {
|
|
297
297
|
const bundle = await rollup({
|
|
298
298
|
input: 'entry',
|
|
@@ -322,9 +322,9 @@ export class ExtensionManager {
|
|
|
322
322
|
const extensionCode = await readFile(entrypointPath, 'utf-8');
|
|
323
323
|
const isolate = new ivm.Isolate({
|
|
324
324
|
memoryLimit: sandboxMemory,
|
|
325
|
-
onCatastrophicError: (
|
|
325
|
+
onCatastrophicError: (error) => {
|
|
326
326
|
logger.error(`Error in API extension sandbox of ${extension.type} "${extension.name}"`);
|
|
327
|
-
logger.error(
|
|
327
|
+
logger.error(error);
|
|
328
328
|
process.abort();
|
|
329
329
|
},
|
|
330
330
|
});
|
|
@@ -377,8 +377,7 @@ export class ExtensionManager {
|
|
|
377
377
|
}
|
|
378
378
|
}
|
|
379
379
|
catch (error) {
|
|
380
|
-
|
|
381
|
-
logger.warn(error);
|
|
380
|
+
this.handleExtensionError({ error, reason: `Couldn't register hook "${hook.name}"` });
|
|
382
381
|
}
|
|
383
382
|
}
|
|
384
383
|
}
|
|
@@ -410,8 +409,7 @@ export class ExtensionManager {
|
|
|
410
409
|
}
|
|
411
410
|
}
|
|
412
411
|
catch (error) {
|
|
413
|
-
|
|
414
|
-
logger.warn(error);
|
|
412
|
+
this.handleExtensionError({ error, reason: `Couldn't register endpoint "${endpoint.name}"` });
|
|
415
413
|
}
|
|
416
414
|
}
|
|
417
415
|
}
|
|
@@ -449,8 +447,7 @@ export class ExtensionManager {
|
|
|
449
447
|
}
|
|
450
448
|
}
|
|
451
449
|
catch (error) {
|
|
452
|
-
|
|
453
|
-
logger.warn(error);
|
|
450
|
+
this.handleExtensionError({ error, reason: `Couldn't register operation "${operation.name}"` });
|
|
454
451
|
}
|
|
455
452
|
}
|
|
456
453
|
}
|
|
@@ -460,6 +457,12 @@ export class ExtensionManager {
|
|
|
460
457
|
*/
|
|
461
458
|
async registerBundles() {
|
|
462
459
|
const bundles = this.extensions.filter((extension) => extension.type === 'bundle');
|
|
460
|
+
const extensionEnabled = (extensionName) => {
|
|
461
|
+
const settings = this.extensionsSettings.find(({ name }) => name === extensionName);
|
|
462
|
+
if (!settings)
|
|
463
|
+
return false;
|
|
464
|
+
return settings.enabled;
|
|
465
|
+
};
|
|
463
466
|
for (const bundle of bundles) {
|
|
464
467
|
try {
|
|
465
468
|
const bundlePath = path.resolve(bundle.path, bundle.entrypoint.api);
|
|
@@ -469,14 +472,20 @@ export class ExtensionManager {
|
|
|
469
472
|
const configs = getModuleDefault(bundleInstances);
|
|
470
473
|
const unregisterFunctions = [];
|
|
471
474
|
for (const { config, name } of configs.hooks) {
|
|
475
|
+
if (!extensionEnabled(`${bundle.name}/${name}`))
|
|
476
|
+
continue;
|
|
472
477
|
const unregisters = this.registerHook(config, name);
|
|
473
478
|
unregisterFunctions.push(...unregisters);
|
|
474
479
|
}
|
|
475
480
|
for (const { config, name } of configs.endpoints) {
|
|
481
|
+
if (!extensionEnabled(`${bundle.name}/${name}`))
|
|
482
|
+
continue;
|
|
476
483
|
const unregister = this.registerEndpoint(config, name);
|
|
477
484
|
unregisterFunctions.push(unregister);
|
|
478
485
|
}
|
|
479
|
-
for (const { config } of configs.operations) {
|
|
486
|
+
for (const { config, name } of configs.operations) {
|
|
487
|
+
if (!extensionEnabled(`${bundle.name}/${name}`))
|
|
488
|
+
continue;
|
|
480
489
|
const unregister = this.registerOperation(config);
|
|
481
490
|
unregisterFunctions.push(unregister);
|
|
482
491
|
}
|
|
@@ -486,8 +495,7 @@ export class ExtensionManager {
|
|
|
486
495
|
});
|
|
487
496
|
}
|
|
488
497
|
catch (error) {
|
|
489
|
-
|
|
490
|
-
logger.warn(error);
|
|
498
|
+
this.handleExtensionError({ error, reason: `Couldn't register bundle "${bundle.name}"` });
|
|
491
499
|
}
|
|
492
500
|
}
|
|
493
501
|
}
|
|
@@ -534,7 +542,7 @@ export class ExtensionManager {
|
|
|
534
542
|
});
|
|
535
543
|
}
|
|
536
544
|
else {
|
|
537
|
-
|
|
545
|
+
this.handleExtensionError({ reason: `Couldn't register cron hook. Provided cron is invalid: ${cron}` });
|
|
538
546
|
}
|
|
539
547
|
},
|
|
540
548
|
embed: (position, code) => {
|
|
@@ -556,7 +564,7 @@ export class ExtensionManager {
|
|
|
556
564
|
}
|
|
557
565
|
}
|
|
558
566
|
else {
|
|
559
|
-
|
|
567
|
+
this.handleExtensionError({ reason: `Couldn't register embed hook. Provided code is empty!` });
|
|
560
568
|
}
|
|
561
569
|
},
|
|
562
570
|
};
|
|
@@ -610,4 +618,22 @@ export class ExtensionManager {
|
|
|
610
618
|
const unregisterFunctions = Array.from(this.unregisterFunctionMap.values());
|
|
611
619
|
await Promise.all(unregisterFunctions.map((fn) => fn()));
|
|
612
620
|
}
|
|
621
|
+
/**
|
|
622
|
+
* If extensions must load successfully, any errors will cause the process to exit.
|
|
623
|
+
* Otherwise, the error will only be logged as a warning.
|
|
624
|
+
*/
|
|
625
|
+
handleExtensionError({ error, reason }) {
|
|
626
|
+
if (toBoolean(env['EXTENSIONS_MUST_LOAD'])) {
|
|
627
|
+
logger.error('EXTENSION_MUST_LOAD is enabled and an extension failed to load.');
|
|
628
|
+
logger.error(reason);
|
|
629
|
+
if (error)
|
|
630
|
+
logger.error(error);
|
|
631
|
+
process.exit(1);
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
logger.warn(reason);
|
|
635
|
+
if (error)
|
|
636
|
+
logger.warn(error);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
613
639
|
}
|
package/dist/logger.d.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import type { RequestHandler } from 'express';
|
|
3
3
|
import { type LoggerOptions } from 'pino';
|
|
4
4
|
export declare const httpLoggerOptions: LoggerOptions;
|
|
5
|
-
declare const logger: import("pino").Logger<
|
|
5
|
+
declare const logger: import("pino").Logger<never>;
|
|
6
6
|
export declare const httpLoggerEnvConfig: Record<string, any>;
|
|
7
7
|
export declare const expressLogger: RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
8
|
+
export declare const useLogger: () => import("pino").Logger<never>;
|
|
8
9
|
export default logger;
|
package/dist/logger.js
CHANGED
|
@@ -10,17 +10,20 @@ if (env['RATE_LIMITER_ENABLED'] === true) {
|
|
|
10
10
|
validateEnv(['RATE_LIMITER_STORE', 'RATE_LIMITER_DURATION', 'RATE_LIMITER_POINTS']);
|
|
11
11
|
rateLimiter = createRateLimiter('RATE_LIMITER');
|
|
12
12
|
checkRateLimit = asyncHandler(async (req, res, next) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
const ip = getIPFromReq(req);
|
|
14
|
+
if (ip) {
|
|
15
|
+
try {
|
|
16
|
+
await rateLimiter.consume(ip, 1);
|
|
17
|
+
}
|
|
18
|
+
catch (rateLimiterRes) {
|
|
19
|
+
if (rateLimiterRes instanceof Error)
|
|
20
|
+
throw rateLimiterRes;
|
|
21
|
+
res.set('Retry-After', String(Math.round(rateLimiterRes.msBeforeNext / 1000)));
|
|
22
|
+
throw new HitRateLimitError({
|
|
23
|
+
limit: +env['RATE_LIMITER_POINTS'],
|
|
24
|
+
reset: new Date(Date.now() + rateLimiterRes.msBeforeNext),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
24
27
|
}
|
|
25
28
|
next();
|
|
26
29
|
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Redis } from 'ioredis';
|
|
2
|
+
import { useEnv } from '../env.js';
|
|
3
|
+
import { getConfigFromEnv } from '../utils/get-config-from-env.js';
|
|
4
|
+
/**
|
|
5
|
+
* Create a new Redis instance based on the global env configuration
|
|
6
|
+
*
|
|
7
|
+
* @returns New Redis instance based on global configuration
|
|
8
|
+
*/
|
|
9
|
+
export const createRedis = () => {
|
|
10
|
+
const env = useEnv();
|
|
11
|
+
return new Redis(env['REDIS'] ?? getConfigFromEnv('REDIS'));
|
|
12
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Redis } from 'ioredis';
|
|
2
|
+
/**
|
|
3
|
+
* Memoization cache for useRedis
|
|
4
|
+
*
|
|
5
|
+
* @see {@link useRedis}
|
|
6
|
+
*/
|
|
7
|
+
export declare const _cache: {
|
|
8
|
+
redis: Redis | undefined;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Access the globally shared Redis instance
|
|
12
|
+
* Creates new Redis instance on first invocation
|
|
13
|
+
*
|
|
14
|
+
* @returns Globally shared Redis instance
|
|
15
|
+
*/
|
|
16
|
+
export declare const useRedis: () => Redis;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Redis } from 'ioredis';
|
|
2
|
+
import { createRedis } from './create-redis.js';
|
|
3
|
+
/**
|
|
4
|
+
* Memoization cache for useRedis
|
|
5
|
+
*
|
|
6
|
+
* @see {@link useRedis}
|
|
7
|
+
*/
|
|
8
|
+
export const _cache = {
|
|
9
|
+
redis: undefined,
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Access the globally shared Redis instance
|
|
13
|
+
* Creates new Redis instance on first invocation
|
|
14
|
+
*
|
|
15
|
+
* @returns Globally shared Redis instance
|
|
16
|
+
*/
|
|
17
|
+
export const useRedis = () => {
|
|
18
|
+
if (_cache.redis)
|
|
19
|
+
return _cache.redis;
|
|
20
|
+
_cache.redis = createRedis();
|
|
21
|
+
return _cache.redis;
|
|
22
|
+
};
|