emailengine-app 2.69.0 → 2.71.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/.github/workflows/deploy.yml +6 -3
- package/.github/workflows/release.yaml +2 -0
- package/.github/workflows/test.yml +73 -12
- package/.ncurc.js +3 -3
- package/CHANGELOG.md +37 -0
- package/Gruntfile.js +21 -23
- package/bin/emailengine.js +8 -1
- package/config/default.toml +5 -0
- package/config/test.toml +5 -0
- package/data/google-crawlers.json +1 -1
- package/getswagger.sh +44 -4
- package/gettext-extract.js +163 -0
- package/lib/account.js +104 -72
- package/lib/api-routes/account-routes.js +231 -71
- package/lib/api-routes/blocklist-routes.js +25 -18
- package/lib/api-routes/chat-routes.js +32 -14
- package/lib/api-routes/delivery-test-routes.js +30 -5
- package/lib/api-routes/export-routes.js +27 -2
- package/lib/api-routes/gateway-routes.js +63 -12
- package/lib/api-routes/license-routes.js +18 -4
- package/lib/api-routes/mailbox-routes.js +33 -7
- package/lib/api-routes/message-routes.js +291 -145
- package/lib/api-routes/oauth2-app-routes.js +90 -24
- package/lib/api-routes/outbox-routes.js +16 -4
- package/lib/api-routes/pubsub-routes.js +8 -4
- package/lib/api-routes/route-helpers.js +14 -1
- package/lib/api-routes/settings-routes.js +51 -25
- package/lib/api-routes/stats-routes.js +37 -3
- package/lib/api-routes/submit-routes.js +31 -42
- package/lib/api-routes/template-routes.js +54 -21
- package/lib/api-routes/token-routes.js +67 -67
- package/lib/api-routes/webhook-route-routes.js +37 -8
- package/lib/autodetect-imap-settings.js +0 -2
- package/lib/consts.js +5 -0
- package/lib/document-store.js +22 -1
- package/lib/email-client/base-client.js +31 -8
- package/lib/email-client/gmail-client.js +119 -112
- package/lib/email-client/imap/mailbox.js +2 -2
- package/lib/email-client/imap/subconnection.js +0 -1
- package/lib/email-client/imap/sync-operations.js +1 -1
- package/lib/email-client/imap-client.js +36 -17
- package/lib/email-client/notification-handler.js +3 -6
- package/lib/email-client/outlook-client.js +49 -62
- package/lib/export.js +49 -1
- package/lib/feature-flags.js +8 -2
- package/lib/gateway.js +4 -9
- package/lib/get-raw-email.js +5 -5
- package/lib/imapproxy/imap-core/lib/imap-connection.js +0 -1
- package/lib/license-beacon.js +367 -0
- package/lib/logger.js +35 -22
- package/lib/metrics-collector.js +0 -2
- package/lib/oauth2-apps.js +13 -4
- package/lib/outbox.js +24 -40
- package/lib/redis-operations.js +1 -1
- package/lib/routes-ui.js +2 -1
- package/lib/schemas.js +403 -83
- package/lib/sentry.js +139 -0
- package/lib/settings.js +9 -3
- package/lib/stream-encrypt.js +1 -1
- package/lib/templates.js +1 -1
- package/lib/tokens.js +5 -3
- package/lib/tools.js +28 -6
- package/lib/ui-routes/account-routes.js +7 -4
- package/lib/ui-routes/admin-config-routes.js +20 -6
- package/lib/ui-routes/document-store-routes.js +7 -1
- package/lib/ui-routes/oauth-config-routes.js +0 -2
- package/lib/ui-routes/route-helpers.js +0 -2
- package/lib/ui-routes/unsubscribe-routes.js +0 -2
- package/lib/webhooks.js +8 -4
- package/package.json +23 -19
- package/sbom.json +1 -1
- package/server.js +38 -31
- package/static/licenses.html +171 -391
- package/translations/de.mo +0 -0
- package/translations/de.po +154 -142
- package/translations/et.mo +0 -0
- package/translations/et.po +129 -131
- package/translations/fr.mo +0 -0
- package/translations/fr.po +133 -136
- package/translations/ja.mo +0 -0
- package/translations/ja.po +126 -129
- package/translations/messages.pot +107 -107
- package/translations/nl.mo +0 -0
- package/translations/nl.po +128 -130
- package/translations/pl.mo +0 -0
- package/translations/pl.po +125 -128
- package/update-info.sh +19 -1
- package/views/config/logging.hbs +48 -0
- package/views/dashboard.hbs +22 -0
- package/workers/api.js +33 -37
- package/workers/documents.js +2 -22
- package/workers/export.js +73 -92
- package/workers/imap-proxy.js +3 -23
- package/workers/imap.js +2 -22
- package/workers/smtp.js +2 -22
- package/workers/submit.js +6 -24
- package/workers/webhooks.js +2 -22
|
@@ -41,7 +41,7 @@ jobs:
|
|
|
41
41
|
id: deploy
|
|
42
42
|
run: |
|
|
43
43
|
echo $GITHUB_SHA > commit.txt
|
|
44
|
-
./update-info.sh
|
|
44
|
+
EE_COMMIT_HASH=$GITHUB_SHA ./update-info.sh
|
|
45
45
|
npm install --omit=dev
|
|
46
46
|
tar czf /tmp/${SERVICE_NAME}.tar.gz --exclude .git .
|
|
47
47
|
scp -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking=no" /tmp/${SERVICE_NAME}.tar.gz deploy@${TARGET_HOST_02}:
|
|
@@ -55,7 +55,7 @@ jobs:
|
|
|
55
55
|
id: deploy-demo
|
|
56
56
|
run: |
|
|
57
57
|
echo $GITHUB_SHA > commit.txt
|
|
58
|
-
./update-info.sh
|
|
58
|
+
EE_COMMIT_HASH=$GITHUB_SHA ./update-info.sh
|
|
59
59
|
npm install --omit=dev
|
|
60
60
|
tar czf /tmp/${SERVICE_NAME}.tar.gz --exclude .git .
|
|
61
61
|
scp -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking=no" /tmp/${SERVICE_NAME}.tar.gz deploy@${TARGET_HOST_02}:
|
|
@@ -104,8 +104,11 @@ jobs:
|
|
|
104
104
|
images: |
|
|
105
105
|
${{ github.repository }}
|
|
106
106
|
ghcr.io/${{ github.repository }}
|
|
107
|
+
# :latest is owned by the release workflow (release.yaml) and
|
|
108
|
+
# always points at the newest release; per-push dev builds from
|
|
109
|
+
# master are published as :edge instead
|
|
107
110
|
tags: |
|
|
108
|
-
type=raw,value=
|
|
111
|
+
type=raw,value=edge,enable=true
|
|
109
112
|
|
|
110
113
|
- name: Build and push
|
|
111
114
|
uses: docker/build-push-action@v7
|
|
@@ -103,9 +103,11 @@ jobs:
|
|
|
103
103
|
platforms: ${{ steps.buildx.outputs.platforms }}
|
|
104
104
|
push: true
|
|
105
105
|
tags: |
|
|
106
|
+
${{ github.repository }}:latest
|
|
106
107
|
${{ github.repository }}:v${{needs.release_please.outputs.major}}.${{needs.release_please.outputs.minor}}.${{needs.release_please.outputs.patch}}
|
|
107
108
|
${{ github.repository }}:v${{needs.release_please.outputs.major}}.${{needs.release_please.outputs.minor}}
|
|
108
109
|
${{ github.repository }}:v${{needs.release_please.outputs.major}}
|
|
110
|
+
ghcr.io/${{ github.repository }}:latest
|
|
109
111
|
ghcr.io/${{ github.repository }}:v${{needs.release_please.outputs.major}}.${{needs.release_please.outputs.minor}}.${{needs.release_please.outputs.patch}}
|
|
110
112
|
ghcr.io/${{ github.repository }}:v${{needs.release_please.outputs.major}}.${{needs.release_please.outputs.minor}}
|
|
111
113
|
ghcr.io/${{ github.repository }}:v${{needs.release_please.outputs.major}}
|
|
@@ -23,20 +23,80 @@ jobs:
|
|
|
23
23
|
uses: actions/setup-node@v6
|
|
24
24
|
with:
|
|
25
25
|
node-version: 24
|
|
26
|
+
cache: npm
|
|
26
27
|
- run: npm install
|
|
27
28
|
|
|
28
29
|
- name: Run License Checks
|
|
29
30
|
run: |
|
|
30
31
|
npm run licenses
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
name:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
lint:
|
|
34
|
+
name: Lint
|
|
35
|
+
runs-on: ubuntu-24.04
|
|
36
|
+
timeout-minutes: 10
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v6
|
|
39
|
+
- name: Use Node.js 24
|
|
40
|
+
uses: actions/setup-node@v6
|
|
41
|
+
with:
|
|
42
|
+
node-version: 24
|
|
43
|
+
cache: npm
|
|
44
|
+
- run: npm install
|
|
45
|
+
- name: Run ESLint
|
|
46
|
+
run: |
|
|
47
|
+
npm run lint
|
|
48
|
+
|
|
49
|
+
unit:
|
|
50
|
+
name: Unit Tests
|
|
51
|
+
timeout-minutes: 10
|
|
52
|
+
runs-on: ubuntu-24.04
|
|
53
|
+
# Service containers to run with `container-job`
|
|
54
|
+
services:
|
|
55
|
+
# Label used to access the service container
|
|
56
|
+
redis:
|
|
57
|
+
# Docker Hub image
|
|
58
|
+
image: redis
|
|
59
|
+
# Set health checks to wait until redis has started
|
|
60
|
+
options: >-
|
|
61
|
+
--health-cmd "redis-cli ping"
|
|
62
|
+
--health-interval 10s
|
|
63
|
+
--health-timeout 5s
|
|
64
|
+
--health-retries 5
|
|
65
|
+
ports:
|
|
66
|
+
- 6379:6379
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/checkout@v6
|
|
69
|
+
- name: Use Node.js 24
|
|
70
|
+
uses: actions/setup-node@v6
|
|
71
|
+
with:
|
|
72
|
+
node-version: 24
|
|
73
|
+
cache: npm
|
|
74
|
+
- name: Setup Redis CLI
|
|
75
|
+
uses: shogo82148/actions-setup-redis@v1
|
|
76
|
+
with:
|
|
77
|
+
redis-version: '7.x'
|
|
78
|
+
auto-start: 'false'
|
|
79
|
+
- run: npm install
|
|
80
|
+
- name: Run unit tests
|
|
81
|
+
run: |
|
|
82
|
+
npm run test:unit
|
|
83
|
+
env:
|
|
84
|
+
NODE_ENV: test
|
|
85
|
+
# account-revoke-on-delete-test.js runs against live Gmail when
|
|
86
|
+
# these are present and skips itself when they are not
|
|
87
|
+
GMAIL_API_PROJECT_ID: ${{ secrets.TEST_GMAIL_API_PROJECT_ID }}
|
|
88
|
+
GMAIL_API_CLIENT_ID: ${{ secrets.TEST_GMAIL_API_CLIENT_ID }}
|
|
89
|
+
GMAIL_API_CLIENT_SECRET: ${{ secrets.TEST_GMAIL_API_CLIENT_SECRET }}
|
|
90
|
+
GMAIL_API_SERVICE_EMAIL: ${{ secrets.TEST_GMAIL_API_SERVICE_EMAIL }}
|
|
91
|
+
GMAIL_API_SERVICE_CLIENT: ${{ secrets.TEST_GMAIL_API_SERVICE_CLIENT }}
|
|
92
|
+
GMAIL_API_SERVICE_KEY: ${{ secrets.TEST_GMAIL_API_SERVICE_KEY }}
|
|
93
|
+
GMAIL_API_ACCOUNT_EMAIL_1: ${{ secrets.TEST_GMAIL_API_ACCOUNT_EMAIL_1 }}
|
|
94
|
+
GMAIL_API_ACCOUNT_REFRESH_1: ${{ secrets.TEST_GMAIL_API_ACCOUNT_REFRESH_1 }}
|
|
95
|
+
|
|
96
|
+
integration:
|
|
97
|
+
name: Integration Tests
|
|
98
|
+
timeout-minutes: 15 # Increased timeout for Gmail API tests
|
|
99
|
+
runs-on: ubuntu-24.04
|
|
40
100
|
# Service containers to run with `container-job`
|
|
41
101
|
services:
|
|
42
102
|
# Label used to access the service container
|
|
@@ -53,19 +113,20 @@ jobs:
|
|
|
53
113
|
- 6379:6379
|
|
54
114
|
steps:
|
|
55
115
|
- uses: actions/checkout@v6
|
|
56
|
-
- name: Use Node.js
|
|
116
|
+
- name: Use Node.js 24
|
|
57
117
|
uses: actions/setup-node@v6
|
|
58
118
|
with:
|
|
59
|
-
node-version:
|
|
119
|
+
node-version: 24
|
|
120
|
+
cache: npm
|
|
60
121
|
- name: Setup Redis CLI
|
|
61
122
|
uses: shogo82148/actions-setup-redis@v1
|
|
62
123
|
with:
|
|
63
124
|
redis-version: '7.x'
|
|
64
125
|
auto-start: 'false'
|
|
65
126
|
- run: npm install
|
|
66
|
-
- name: Run tests
|
|
127
|
+
- name: Run integration tests
|
|
67
128
|
run: |
|
|
68
|
-
npm test
|
|
129
|
+
npm run test:integration
|
|
69
130
|
env:
|
|
70
131
|
NODE_ENV: test
|
|
71
132
|
GMAIL_API_PROJECT_ID: ${{ secrets.TEST_GMAIL_API_PROJECT_ID }}
|
package/.ncurc.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
upgrade: true,
|
|
3
|
+
// Keep joi within the 17.x major (hapi-swagger's peer dependency requires joi 17.x).
|
|
4
|
+
// Using a target function instead of a blanket reject so joi still receives 17.x security patches.
|
|
5
|
+
target: name => (name === 'joi' ? 'minor' : 'latest'),
|
|
3
6
|
reject: [
|
|
4
7
|
// Block package upgrades that moved to ESM
|
|
5
8
|
'nanoid',
|
|
@@ -18,9 +21,6 @@ module.exports = {
|
|
|
18
21
|
// some kind of CVE in later versions. Only needed for license reference, so the actual version does not matter anyway
|
|
19
22
|
'startbootstrap-sb-admin-2',
|
|
20
23
|
|
|
21
|
-
// Keep joi at version 17.x for hapi-swagger compatibility
|
|
22
|
-
'joi',
|
|
23
|
-
|
|
24
24
|
// @asamuzakjp/css-color >=4.1.2 pulls in @csstools/* v4 which are pure ESM and break pkg bundling
|
|
25
25
|
'@asamuzakjp/css-color',
|
|
26
26
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.71.0](https://github.com/postalsys/emailengine/compare/v2.70.0...v2.71.0) (2026-06-15)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add anonymized feature beacon to license validation ([954d92a](https://github.com/postalsys/emailengine/commit/954d92a9d8e7c5fff6f1b8c711dfd228f556387a))
|
|
9
|
+
* disable deprecated Document Store by default behind a feature gate ([8790f75](https://github.com/postalsys/emailengine/commit/8790f7538ba2ee8d6ec73d1f3bbc221c2f54f0fe))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* defer export worker job consumption until startup recovery completes ([a21b0a5](https://github.com/postalsys/emailengine/commit/a21b0a50fdb98d13a2227a020069129d23fe225b))
|
|
15
|
+
* fail export when a folder cannot be indexed ([36c9c9d](https://github.com/postalsys/emailengine/commit/36c9c9d536540cd5a61b16b69af5e8e84c194ee6))
|
|
16
|
+
* filter transient fetch failures from Sentry and retry DNS errors ([883b9b4](https://github.com/postalsys/emailengine/commit/883b9b487e3580c5b1240d28cd519d190c402b47))
|
|
17
|
+
* retry transient errors in API-account batch export path ([6cba7c7](https://github.com/postalsys/emailengine/commit/6cba7c72658a037f68867cf2464f7d3420a79507))
|
|
18
|
+
* translate OAuth scope error page across 6 languages ([4ae1919](https://github.com/postalsys/emailengine/commit/4ae19199fe628f8a4f7177d53db1f05167077e3e))
|
|
19
|
+
* upgrade joi to 17.13.4 and @postalsys/certs to 1.0.15 (GHSA-q7cg-457f-vx79) ([6ec77e5](https://github.com/postalsys/emailengine/commit/6ec77e50b288b14550cd09b724f5aab59d17ced4))
|
|
20
|
+
|
|
21
|
+
## [2.70.0](https://github.com/postalsys/emailengine/compare/v2.69.0...v2.70.0) (2026-06-11)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Features
|
|
25
|
+
|
|
26
|
+
* allow enabling Sentry error reporting from the admin UI ([a4076a4](https://github.com/postalsys/emailengine/commit/a4076a4fa6658ccc998f9bfe35dddf015cd12b09))
|
|
27
|
+
* replace Bugsnag with self-hosted Sentry for error tracking ([62de831](https://github.com/postalsys/emailengine/commit/62de831a2911da9b649fe693547ba09d70e2e481))
|
|
28
|
+
* tag Sentry error reports with instance id and license key ([2e9683f](https://github.com/postalsys/emailengine/commit/2e9683f27d54ff8fa7ba3f6f4453866fd135f173))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### Bug Fixes
|
|
32
|
+
|
|
33
|
+
* align API response schemas with actual responses and fix uncovered API bugs ([abacbf6](https://github.com/postalsys/emailengine/commit/abacbf66f048a77d00d180a831a97594380d8ed9))
|
|
34
|
+
* align remaining API response schemas with actual responses ([06136ab](https://github.com/postalsys/emailengine/commit/06136abd20a97c45b25aac7f93653387eb55928a))
|
|
35
|
+
* assign unassigned accounts after license activation ([5677977](https://github.com/postalsys/emailengine/commit/56779778eba27f2d1604ff06f17e5736a5ddee61))
|
|
36
|
+
* do not crash at startup when a feature flag env variable is set to a falsy value ([98ad979](https://github.com/postalsys/emailengine/commit/98ad97988e752fad9e2ae11eff63f35ffcf84976))
|
|
37
|
+
* report correct nextAttempt time for queued messages ([d5ebdfb](https://github.com/postalsys/emailengine/commit/d5ebdfb029b3ed331a8239ace7426f4f0704ae2e))
|
|
38
|
+
* resolve code review findings in Gmail bulk ops, exports, webhooks, and schemas ([f0ddb46](https://github.com/postalsys/emailengine/commit/f0ddb4631fb924ddb93e0a99d0e20bc9f0cc8ee2))
|
|
39
|
+
|
|
3
40
|
## [2.69.0](https://github.com/postalsys/emailengine/compare/v2.68.1...v2.69.0) (2026-06-09)
|
|
4
41
|
|
|
5
42
|
|
package/Gruntfile.js
CHANGED
|
@@ -5,20 +5,11 @@ const config = require('@zone-eu/wild-config');
|
|
|
5
5
|
module.exports = function (grunt) {
|
|
6
6
|
// Project configuration.
|
|
7
7
|
grunt.initConfig({
|
|
8
|
-
wait: {
|
|
9
|
-
server: {
|
|
10
|
-
options: {
|
|
11
|
-
delay: 20 * 1000 // Increased from 12s to 20s for Gmail API operations
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
|
|
16
8
|
shell: {
|
|
17
9
|
eslint: {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
10
|
+
// Globs are quoted so eslint expands them itself - unquoted, sh (no globstar)
|
|
11
|
+
// expands lib/**/*.js to depth-2 files only and skips most of lib/
|
|
12
|
+
command: "npx eslint 'lib/**/*.js' 'workers/**/*.js' server.js Gruntfile.js"
|
|
22
13
|
},
|
|
23
14
|
server: {
|
|
24
15
|
command: 'node server.js',
|
|
@@ -27,16 +18,22 @@ module.exports = function (grunt) {
|
|
|
27
18
|
}
|
|
28
19
|
},
|
|
29
20
|
flush: {
|
|
30
|
-
command: `redis-cli -u "${config.dbs.redis}" flushdb
|
|
31
|
-
options: {
|
|
32
|
-
async: false
|
|
33
|
-
}
|
|
21
|
+
command: `redis-cli -u "${config.dbs.redis}" flushdb`
|
|
34
22
|
},
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
23
|
+
waitServer: {
|
|
24
|
+
// Polls /health until the server reports ready (all IMAP workers up,
|
|
25
|
+
// Redis responding) instead of sleeping for a fixed delay
|
|
26
|
+
command: 'node test/helpers/wait-for-server.js'
|
|
27
|
+
},
|
|
28
|
+
testUnit: {
|
|
29
|
+
// Self-contained tests - need Redis but not the live server.
|
|
30
|
+
// Run with default --test concurrency; the suite is verified to pass in parallel.
|
|
31
|
+
// The *-test.js pattern keeps helper modules out of the test runner
|
|
32
|
+
command: 'node --test --test-timeout=180000 test/*-test.js'
|
|
33
|
+
},
|
|
34
|
+
testIntegration: {
|
|
35
|
+
// Tests that run against the live server started by shell:server
|
|
36
|
+
command: 'node --test --test-concurrency=1 --test-timeout=180000 test/integration/*-test.js'
|
|
40
37
|
},
|
|
41
38
|
options: {
|
|
42
39
|
stdout: data => console.log(data.toString().trim()),
|
|
@@ -48,10 +45,11 @@ module.exports = function (grunt) {
|
|
|
48
45
|
|
|
49
46
|
// Load the plugin(s)
|
|
50
47
|
grunt.loadNpmTasks('grunt-shell-spawn');
|
|
51
|
-
grunt.loadNpmTasks('grunt-wait');
|
|
52
48
|
|
|
53
49
|
// Tasks
|
|
54
|
-
grunt.registerTask('test', ['shell:flush', 'shell:
|
|
50
|
+
grunt.registerTask('test-unit', ['shell:flush', 'shell:testUnit']);
|
|
51
|
+
grunt.registerTask('test-integration', ['shell:flush', 'shell:server', 'shell:waitServer', 'shell:testIntegration', 'shell:server:kill']);
|
|
52
|
+
grunt.registerTask('test', ['test-unit', 'test-integration']);
|
|
55
53
|
|
|
56
54
|
grunt.registerTask('default', ['shell:eslint', 'test']);
|
|
57
55
|
};
|
package/bin/emailengine.js
CHANGED
|
@@ -105,7 +105,14 @@ const GLOBAL_OPTIONS = [
|
|
|
105
105
|
{ name: '--smtp.host', description: 'SMTP server bind address', type: 'string', default: '127.0.0.1', group: 'SMTP server' },
|
|
106
106
|
{ name: '--smtp.port', description: 'SMTP server port', type: 'number', default: 2525, group: 'SMTP server' },
|
|
107
107
|
{ name: '--smtp.proxy', description: 'Enable HAProxy PROXY protocol', type: 'boolean', default: false, group: 'SMTP server' },
|
|
108
|
-
{ name: '--smtp.maxMessageSize', description: 'Maximum email size', type: 'number/string', default: '25M', group: 'SMTP server' }
|
|
108
|
+
{ name: '--smtp.maxMessageSize', description: 'Maximum email size', type: 'number/string', default: '25M', group: 'SMTP server' },
|
|
109
|
+
{
|
|
110
|
+
name: '--documentStore.enabled',
|
|
111
|
+
description: 'Enable the deprecated Document Store (ElasticSearch) feature',
|
|
112
|
+
type: 'boolean',
|
|
113
|
+
default: false,
|
|
114
|
+
group: 'Document Store (deprecated)'
|
|
115
|
+
}
|
|
109
116
|
];
|
|
110
117
|
|
|
111
118
|
// Help formatting functions
|
package/config/default.toml
CHANGED
|
@@ -31,6 +31,11 @@ secret = "" # client password, if not set allows any password
|
|
|
31
31
|
proxy = false # Set to true if using HAProxy with send-proxy option
|
|
32
32
|
#maxMessageSize = "25M" # maximum message size accepted by SMTP server (default: 25MB)
|
|
33
33
|
|
|
34
|
+
[documentStore]
|
|
35
|
+
# Deprecated Document Store (ElasticSearch) feature. Disabled by default; the worker and
|
|
36
|
+
# all document-store API/UI endpoints are only available when this is enabled.
|
|
37
|
+
enabled = false
|
|
38
|
+
|
|
34
39
|
[dbs]
|
|
35
40
|
# redis connection
|
|
36
41
|
redis = "redis://127.0.0.1:6379/8"
|
package/config/test.toml
CHANGED
package/getswagger.sh
CHANGED
|
@@ -1,8 +1,48 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
|
+
set -e
|
|
4
|
+
|
|
3
5
|
export EENGINE_PORT=5678
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
# Keep the deprecated Document Store endpoints in the generated spec; they are only
|
|
8
|
+
# registered when the feature is enabled.
|
|
9
|
+
export EENGINE_DOCUMENT_STORE_ENABLED=true
|
|
10
|
+
|
|
11
|
+
# refuse to run if something is already listening on the port, otherwise the
|
|
12
|
+
# polling loop below would silently fetch swagger.json from a stale instance
|
|
13
|
+
if (exec 3<>"/dev/tcp/127.0.0.1/${EENGINE_PORT}") 2>/dev/null; then
|
|
14
|
+
echo "Port ${EENGINE_PORT} is already in use" >&2
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
node server.js > /dev/null 2>&1 &
|
|
19
|
+
SERVER_PID=$!
|
|
20
|
+
|
|
21
|
+
cleanup() {
|
|
22
|
+
kill "$SERVER_PID" 2>/dev/null || true
|
|
23
|
+
wait "$SERVER_PID" 2>/dev/null || true
|
|
24
|
+
}
|
|
25
|
+
trap cleanup EXIT
|
|
26
|
+
|
|
27
|
+
# poll until the API is up instead of relying on a fixed sleep
|
|
28
|
+
rm -f swagger.json.tmp
|
|
29
|
+
for i in $(seq 1 60); do
|
|
30
|
+
if curl -fs --max-time 5 "http://127.0.0.1:${EENGINE_PORT}/swagger.json" -o swagger.json.tmp; then
|
|
31
|
+
break
|
|
32
|
+
fi
|
|
33
|
+
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
|
|
34
|
+
echo "Server exited before swagger.json could be fetched" >&2
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
sleep 1
|
|
38
|
+
done
|
|
39
|
+
|
|
40
|
+
if [ ! -s swagger.json.tmp ]; then
|
|
41
|
+
echo "Timed out waiting for swagger.json" >&2
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# only replace swagger.json if the download parses as JSON
|
|
46
|
+
node -e 'JSON.parse(require("fs").readFileSync("swagger.json.tmp", "utf-8"))'
|
|
47
|
+
|
|
48
|
+
mv swagger.json.tmp swagger.json
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Extracts translatable strings from JS sources into translations/messages.pot.
|
|
4
|
+
// Run via `npm run gettext` after xgettext-template has generated the POT from the
|
|
5
|
+
// Handlebars views - this script joins the strings found in JS files into that file.
|
|
6
|
+
//
|
|
7
|
+
// Replaces jsxgettext, which bundled acorn 5 and crashed on post-ES2018 syntax
|
|
8
|
+
// (optional chaining, nullish coalescing, etc.). Files are discovered dynamically,
|
|
9
|
+
// so new modules with gettext()/ngettext() calls are picked up automatically.
|
|
10
|
+
|
|
11
|
+
const { parse } = require('acorn');
|
|
12
|
+
const { full: walkFull } = require('acorn-walk');
|
|
13
|
+
const { po } = require('gettext-parser');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const Path = require('path');
|
|
16
|
+
|
|
17
|
+
const ROOT_DIR = __dirname;
|
|
18
|
+
const POT_PATH = Path.join(ROOT_DIR, 'translations', 'messages.pot');
|
|
19
|
+
|
|
20
|
+
// All server-side code that may contain gettext()/ngettext() calls
|
|
21
|
+
const SCAN_DIRS = ['bin', 'lib', 'workers'];
|
|
22
|
+
const SCAN_FILES = ['server.js'];
|
|
23
|
+
|
|
24
|
+
function listJsFiles(dir) {
|
|
25
|
+
return fs
|
|
26
|
+
.readdirSync(dir, { recursive: true })
|
|
27
|
+
.filter(entryPath => entryPath.endsWith('.js'))
|
|
28
|
+
.map(entryPath => Path.join(dir, entryPath));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// The full sorted scan set (sorted to keep POT reference output deterministic)
|
|
32
|
+
function listScanFiles() {
|
|
33
|
+
let files = SCAN_FILES.map(file => Path.join(ROOT_DIR, file));
|
|
34
|
+
for (let dir of SCAN_DIRS) {
|
|
35
|
+
files = files.concat(listJsFiles(Path.join(ROOT_DIR, dir)));
|
|
36
|
+
}
|
|
37
|
+
return files.sort();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseFile(filePath) {
|
|
41
|
+
const source = fs.readFileSync(filePath, 'utf-8');
|
|
42
|
+
try {
|
|
43
|
+
return parse(source, { ecmaVersion: 'latest', sourceType: 'script', locations: true, allowHashBang: true });
|
|
44
|
+
} catch (err) {
|
|
45
|
+
err.message = `Failed to parse ${filePath}: ${err.message}`;
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Resolves a call argument into a string value. Handles string literals and
|
|
51
|
+
// concatenation of string literals ('foo' + 'bar'). Returns false for anything
|
|
52
|
+
// dynamic (identifiers, template literals with expressions, etc.) - such calls
|
|
53
|
+
// forward runtime values and do not define new translatable strings.
|
|
54
|
+
function resolveString(node) {
|
|
55
|
+
switch (node.type) {
|
|
56
|
+
case 'Literal':
|
|
57
|
+
return typeof node.value === 'string' ? node.value : false;
|
|
58
|
+
case 'TemplateLiteral':
|
|
59
|
+
return node.expressions.length === 0 ? node.quasis[0].value.cooked : false;
|
|
60
|
+
case 'BinaryExpression': {
|
|
61
|
+
if (node.operator !== '+') {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
let left = resolveString(node.left);
|
|
65
|
+
let right = resolveString(node.right);
|
|
66
|
+
return left !== false && right !== false ? left + right : false;
|
|
67
|
+
}
|
|
68
|
+
default:
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function calleeName(callee) {
|
|
74
|
+
if (callee.type === 'Identifier') {
|
|
75
|
+
return callee.name;
|
|
76
|
+
}
|
|
77
|
+
if (callee.type === 'MemberExpression' && !callee.computed && callee.property.type === 'Identifier') {
|
|
78
|
+
return callee.property.name;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function extractFromFile(filePath) {
|
|
84
|
+
const ast = parseFile(filePath);
|
|
85
|
+
|
|
86
|
+
let entries = [];
|
|
87
|
+
let reference = node => `${Path.relative(ROOT_DIR, filePath)}:${node.loc.start.line}`;
|
|
88
|
+
|
|
89
|
+
walkFull(ast, node => {
|
|
90
|
+
if (node.type !== 'CallExpression') {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let name = calleeName(node.callee);
|
|
95
|
+
|
|
96
|
+
// An empty msgid is skipped (like xgettext does): translations[''] is the POT header
|
|
97
|
+
// entry in gettext-parser, so an empty key would corrupt the header block
|
|
98
|
+
if (name === 'gettext' && node.arguments.length >= 1) {
|
|
99
|
+
let msgid = resolveString(node.arguments[0]);
|
|
100
|
+
if (msgid !== false && msgid !== '') {
|
|
101
|
+
entries.push({ msgid, reference: reference(node) });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (name === 'ngettext' && node.arguments.length >= 2) {
|
|
106
|
+
let msgid = resolveString(node.arguments[0]);
|
|
107
|
+
let msgidPlural = resolveString(node.arguments[1]);
|
|
108
|
+
if (msgid !== false && msgid !== '' && msgidPlural !== false) {
|
|
109
|
+
entries.push({ msgid, msgidPlural, reference: reference(node) });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return entries;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function main() {
|
|
118
|
+
let files = listScanFiles();
|
|
119
|
+
|
|
120
|
+
let extracted = [];
|
|
121
|
+
for (let filePath of files) {
|
|
122
|
+
extracted = extracted.concat(extractFromFile(filePath));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let pot = po.parse(fs.readFileSync(POT_PATH));
|
|
126
|
+
let translations = (pot.translations[''] = pot.translations[''] || {});
|
|
127
|
+
|
|
128
|
+
// xgettext-template does not stamp a creation date, so set it here (jsxgettext used to)
|
|
129
|
+
pot.headers = pot.headers || {};
|
|
130
|
+
pot.headers['POT-Creation-Date'] = new Date()
|
|
131
|
+
.toISOString()
|
|
132
|
+
.replace(/T/, ' ')
|
|
133
|
+
.replace(/:\d+\.\d+Z$/, '+0000');
|
|
134
|
+
|
|
135
|
+
for (let { msgid, msgidPlural, reference } of extracted) {
|
|
136
|
+
let entry = translations[msgid];
|
|
137
|
+
if (!entry) {
|
|
138
|
+
entry = translations[msgid] = { msgid, msgstr: [''] };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (msgidPlural && !entry.msgid_plural) {
|
|
142
|
+
entry.msgid_plural = msgidPlural;
|
|
143
|
+
entry.msgstr = ['', ''];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
entry.comments = entry.comments || {};
|
|
147
|
+
let references = entry.comments.reference ? entry.comments.reference.split('\n') : [];
|
|
148
|
+
if (!references.includes(reference)) {
|
|
149
|
+
references.push(reference);
|
|
150
|
+
}
|
|
151
|
+
entry.comments.reference = references.join('\n');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fs.writeFileSync(POT_PATH, po.compile(pot));
|
|
155
|
+
console.log(`Extracted ${extracted.length} gettext strings from ${files.length} JS files into ${Path.relative(ROOT_DIR, POT_PATH)}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (require.main === module) {
|
|
159
|
+
main();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Exported for the gettext coverage test, which walks the same scan set with the same helpers
|
|
163
|
+
module.exports = { listScanFiles, parseFile, calleeName, extractFromFile };
|