mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2025-07-27 08:00:06 +02:00
Compare commits
194 commits
ffbb09e1d4
...
52f1e46343
Author | SHA1 | Date | |
---|---|---|---|
![]() |
52f1e46343 | ||
![]() |
841e32bb64 | ||
![]() |
88d3186dc1 | ||
![]() |
f86eb6208f | ||
![]() |
c18b3f95b2 | ||
![]() |
23e24627d5 | ||
![]() |
d6cd041704 | ||
![]() |
e0f3bea9ad | ||
![]() |
436dcc977f | ||
![]() |
b667cef262 | ||
![]() |
8e6d1d5f07 | ||
![]() |
9b27ed4798 | ||
![]() |
b8e391c005 | ||
![]() |
827fb19df7 | ||
![]() |
80897001a5 | ||
![]() |
8a1cdab27e | ||
![]() |
e98addf33a | ||
![]() |
0b9cb185fa | ||
![]() |
061f10a059 | ||
![]() |
c45bca6ce9 | ||
![]() |
17d1b0b8d6 | ||
![]() |
5a0f53654f | ||
![]() |
0e54e650e3 | ||
![]() |
6a73d3f8f3 | ||
![]() |
47b76a49d8 | ||
![]() |
6bc4e40773 | ||
![]() |
26a7a7d7b8 | ||
![]() |
bb24aa46d1 | ||
![]() |
efd55ffe97 | ||
![]() |
1354465562 | ||
![]() |
1e6b13f9d5 | ||
![]() |
f56645ecfe | ||
![]() |
efc42481da | ||
![]() |
26f27d32a1 | ||
![]() |
14d6600fb5 | ||
![]() |
d8c5b74e09 | ||
![]() |
d680544b69 | ||
![]() |
ab37e18bc3 | ||
![]() |
dbb528762e | ||
![]() |
ecdd0c54bd | ||
![]() |
f7d37a49d6 | ||
![]() |
ed006b707c | ||
![]() |
bb3f1aa998 | ||
![]() |
d280d4ecad | ||
![]() |
b375b13950 | ||
![]() |
60e588440f | ||
![]() |
5ee59a4f4a | ||
![]() |
a8b443fe5f | ||
![]() |
996c407393 | ||
![]() |
85533a36e9 | ||
![]() |
d7ef7f0399 | ||
![]() |
50a5c7984d | ||
![]() |
cc3b020d88 | ||
![]() |
957462b61c | ||
![]() |
c4c8f521ff | ||
![]() |
7a0a6077cf | ||
![]() |
586820c34d | ||
![]() |
18cd52cfa1 | ||
![]() |
0e13796882 | ||
![]() |
daa52d62fa | ||
![]() |
e3e8d944b2 | ||
![]() |
14cbd44d9b | ||
![]() |
cb7d75202b | ||
![]() |
bc78432e62 | ||
![]() |
efa39ee664 | ||
![]() |
1440a47d53 | ||
![]() |
81dd3c10a7 | ||
![]() |
8017918063 | ||
![]() |
75ce58d0c6 | ||
![]() |
bb66f75027 | ||
![]() |
3ef62472bd | ||
![]() |
e282a74f7a | ||
![]() |
10091811f7 | ||
![]() |
67ab5a749a | ||
![]() |
2dd054d602 | ||
![]() |
966cea3d8b | ||
![]() |
b56556f37b | ||
![]() |
899f6d18d6 | ||
![]() |
449a5d6339 | ||
![]() |
4847c83cb8 | ||
![]() |
69d99079b1 | ||
![]() |
fee5ea8411 | ||
![]() |
5c4cb50628 | ||
![]() |
2ebc3a982a | ||
![]() |
59f614a41b | ||
![]() |
ec1bd69605 | ||
![]() |
40193752c4 | ||
![]() |
eebda2427e | ||
![]() |
e25a69936e | ||
![]() |
443fb3f152 | ||
![]() |
fb0cfbe0bb | ||
![]() |
bd67b4ca13 | ||
![]() |
6c96c70b28 | ||
![]() |
b70e0166bd | ||
![]() |
60cdd252ef | ||
![]() |
c942b490ab | ||
![]() |
732b365683 | ||
![]() |
dd351f64fb | ||
![]() |
5730cd3dde | ||
![]() |
b52c0bf08e | ||
![]() |
587390d066 | ||
![]() |
21c035b8d5 | ||
![]() |
6d35305d7d | ||
![]() |
271635491a | ||
![]() |
aa002369cb | ||
![]() |
7c79c3b4c3 | ||
![]() |
ac51878186 | ||
![]() |
3cb7ef05d9 | ||
![]() |
7356c3b863 | ||
![]() |
11d964649c | ||
![]() |
8d940ad841 | ||
![]() |
8e31c30ec7 | ||
![]() |
82e5b2c5d7 | ||
![]() |
2f3166aa54 | ||
![]() |
8234e80931 | ||
![]() |
3bfc3ee7ae | ||
![]() |
732b1d146e | ||
![]() |
bf22684e2d | ||
![]() |
1ec12e3d88 | ||
![]() |
50c2d8f32f | ||
![]() |
b99a81cb25 | ||
![]() |
c1f3758aa2 | ||
![]() |
67a436b639 | ||
![]() |
70c26b6ed2 | ||
![]() |
0d70223a48 | ||
![]() |
caf8b5c3c5 | ||
![]() |
5506e0d58e | ||
![]() |
ddfadbc474 | ||
![]() |
3c3414a7d3 | ||
![]() |
9762cf95e3 | ||
![]() |
bc4550c1f9 | ||
![]() |
fba8f776a1 | ||
![]() |
9e07549ecb | ||
![]() |
0a0dc13030 | ||
![]() |
5ba31ab14f | ||
![]() |
c50bd6af89 | ||
![]() |
391fca9e83 | ||
![]() |
5717ea7f5c | ||
![]() |
7ea1b690f2 | ||
![]() |
d489e35782 | ||
![]() |
8d4f258494 | ||
![]() |
dc73a18ca4 | ||
![]() |
9cf284aefa | ||
![]() |
3242376d19 | ||
![]() |
2ea03af559 | ||
![]() |
1b5d31941e | ||
![]() |
9cb2b48c1e | ||
![]() |
6eada92966 | ||
![]() |
2ccdb74d20 | ||
![]() |
d39d92cce8 | ||
![]() |
0335367c75 | ||
![]() |
fa553128a4 | ||
![]() |
b3a1341545 | ||
![]() |
4b5f31ac95 | ||
![]() |
ad94c29659 | ||
![]() |
ec91c120b1 | ||
![]() |
9f0f910a83 | ||
![]() |
fbbba7a3df | ||
![]() |
6973ba4244 | ||
![]() |
e8b5ee2ff9 | ||
![]() |
4d93187e58 | ||
![]() |
efb66b4d2f | ||
![]() |
e4156e19b8 | ||
![]() |
f9b36cd5be | ||
![]() |
5cc0895c56 | ||
![]() |
07b37abcb3 | ||
![]() |
e7329b9660 | ||
![]() |
eddeca2942 | ||
![]() |
661aa20c09 | ||
![]() |
6c3cc794a4 | ||
![]() |
12939b91b3 | ||
![]() |
f72a34f25b | ||
![]() |
c060358cd8 | ||
![]() |
f29bd47911 | ||
![]() |
59275eeb84 | ||
![]() |
bc4c3c4ef8 | ||
![]() |
e3dd47ba6e | ||
![]() |
81941ff335 | ||
![]() |
9a2847dbee | ||
![]() |
f0856c862f | ||
![]() |
6881ba956a | ||
![]() |
bfe42734bc | ||
![]() |
c3368167d0 | ||
![]() |
5ce1685b5b | ||
![]() |
aa97dbdbb6 | ||
![]() |
42eb4b2779 | ||
![]() |
0261d701a7 | ||
![]() |
08c5b11689 | ||
![]() |
1cd8eba098 | ||
![]() |
07b2255426 | ||
![]() |
d95340edbc | ||
![]() |
a5e2708eae | ||
![]() |
c8a410d358 | ||
![]() |
473f767465 |
695 changed files with 15400 additions and 9919 deletions
|
@ -1,6 +1,5 @@
|
||||||
volumes:
|
volumes:
|
||||||
clsi-cache:
|
clsi-cache:
|
||||||
clsi-output:
|
|
||||||
filestore-public-files:
|
filestore-public-files:
|
||||||
filestore-template-files:
|
filestore-template-files:
|
||||||
filestore-uploads:
|
filestore-uploads:
|
||||||
|
@ -33,9 +32,9 @@ services:
|
||||||
user: root
|
user: root
|
||||||
volumes:
|
volumes:
|
||||||
- ${PWD}/compiles:/overleaf/services/clsi/compiles
|
- ${PWD}/compiles:/overleaf/services/clsi/compiles
|
||||||
|
- ${PWD}/output:/overleaf/services/clsi/output
|
||||||
- ${DOCKER_SOCKET_PATH:-/var/run/docker.sock}:/var/run/docker.sock
|
- ${DOCKER_SOCKET_PATH:-/var/run/docker.sock}:/var/run/docker.sock
|
||||||
- clsi-cache:/overleaf/services/clsi/cache
|
- clsi-cache:/overleaf/services/clsi/cache
|
||||||
- clsi-output:/overleaf/services/clsi/output
|
|
||||||
|
|
||||||
contacts:
|
contacts:
|
||||||
build:
|
build:
|
||||||
|
|
|
@ -73,7 +73,11 @@ services:
|
||||||
## Server Pro ##
|
## Server Pro ##
|
||||||
################
|
################
|
||||||
|
|
||||||
## Sandboxed Compiles: https://github.com/overleaf/overleaf/wiki/Server-Pro:-Sandboxed-Compiles
|
## The Community Edition is intended for use in environments where all users are trusted and is not appropriate for
|
||||||
|
## scenarios where isolation of users is required. Sandboxed Compiles are not available in the Community Edition,
|
||||||
|
## so the following environment variables must be commented out to avoid compile issues.
|
||||||
|
##
|
||||||
|
## Sandboxed Compiles: https://docs.overleaf.com/on-premises/configuration/overleaf-toolkit/server-pro-only-configuration/sandboxed-compiles
|
||||||
SANDBOXED_COMPILES: 'true'
|
SANDBOXED_COMPILES: 'true'
|
||||||
### Bind-mount source for /var/lib/overleaf/data/compiles inside the container.
|
### Bind-mount source for /var/lib/overleaf/data/compiles inside the container.
|
||||||
SANDBOXED_COMPILES_HOST_DIR_COMPILES: '/home/user/sharelatex_data/data/compiles'
|
SANDBOXED_COMPILES_HOST_DIR_COMPILES: '/home/user/sharelatex_data/data/compiles'
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
node_modules/
|
|
46
libraries/access-token-encryptor/.gitignore
vendored
46
libraries/access-token-encryptor/.gitignore
vendored
|
@ -1,46 +0,0 @@
|
||||||
compileFolder
|
|
||||||
|
|
||||||
Compiled source #
|
|
||||||
###################
|
|
||||||
*.com
|
|
||||||
*.class
|
|
||||||
*.dll
|
|
||||||
*.exe
|
|
||||||
*.o
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Packages #
|
|
||||||
############
|
|
||||||
# it's better to unpack these files and commit the raw source
|
|
||||||
# git has its own built in compression methods
|
|
||||||
*.7z
|
|
||||||
*.dmg
|
|
||||||
*.gz
|
|
||||||
*.iso
|
|
||||||
*.jar
|
|
||||||
*.rar
|
|
||||||
*.tar
|
|
||||||
*.zip
|
|
||||||
|
|
||||||
# Logs and databases #
|
|
||||||
######################
|
|
||||||
*.log
|
|
||||||
*.sql
|
|
||||||
*.sqlite
|
|
||||||
|
|
||||||
# OS generated files #
|
|
||||||
######################
|
|
||||||
.DS_Store?
|
|
||||||
ehthumbs.db
|
|
||||||
Icon?
|
|
||||||
Thumbs.db
|
|
||||||
|
|
||||||
/node_modules/*
|
|
||||||
data/*/*
|
|
||||||
|
|
||||||
**.swp
|
|
||||||
|
|
||||||
/log.json
|
|
||||||
hash_folder
|
|
||||||
|
|
||||||
.npmrc
|
|
|
@ -1 +1 @@
|
||||||
20.18.2
|
22.15.1
|
||||||
|
|
|
@ -5,6 +5,6 @@ access-token-encryptor
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=20.18.2
|
--node-version=22.15.1
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
node_modules/
|
|
3
libraries/fetch-utils/.gitignore
vendored
3
libraries/fetch-utils/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
||||||
|
|
||||||
# managed by monorepo$ bin/update_build_scripts
|
|
||||||
.npmrc
|
|
|
@ -1 +1 @@
|
||||||
20.18.2
|
22.15.1
|
||||||
|
|
|
@ -5,6 +5,6 @@ fetch-utils
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=20.18.2
|
--node-version=22.15.1
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
node_modules/
|
|
3
libraries/logger/.gitignore
vendored
3
libraries/logger/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
||||||
node_modules
|
|
||||||
|
|
||||||
.npmrc
|
|
|
@ -1 +1 @@
|
||||||
20.18.2
|
22.15.1
|
||||||
|
|
|
@ -5,6 +5,6 @@ logger
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=20.18.2
|
--node-version=22.15.1
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
node_modules/
|
|
3
libraries/metrics/.gitignore
vendored
3
libraries/metrics/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
||||||
node_modules
|
|
||||||
|
|
||||||
.npmrc
|
|
|
@ -1 +1 @@
|
||||||
20.18.2
|
22.15.1
|
||||||
|
|
|
@ -5,6 +5,6 @@ metrics
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=20.18.2
|
--node-version=22.15.1
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
* before any other module to support code instrumentation.
|
* before any other module to support code instrumentation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const metricsModuleImportStartTime = performance.now()
|
||||||
|
|
||||||
const APP_NAME = process.env.METRICS_APP_NAME || 'unknown'
|
const APP_NAME = process.env.METRICS_APP_NAME || 'unknown'
|
||||||
const BUILD_VERSION = process.env.BUILD_VERSION
|
const BUILD_VERSION = process.env.BUILD_VERSION
|
||||||
const ENABLE_PROFILE_AGENT = process.env.ENABLE_PROFILE_AGENT === 'true'
|
const ENABLE_PROFILE_AGENT = process.env.ENABLE_PROFILE_AGENT === 'true'
|
||||||
|
@ -103,3 +105,5 @@ function recordProcessStart() {
|
||||||
const metrics = require('.')
|
const metrics = require('.')
|
||||||
metrics.inc('process_startup')
|
metrics.inc('process_startup')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = { metricsModuleImportStartTime }
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google-cloud/opentelemetry-cloud-trace-exporter": "^2.1.0",
|
"@google-cloud/opentelemetry-cloud-trace-exporter": "^2.1.0",
|
||||||
"@google-cloud/profiler": "^6.0.0",
|
"@google-cloud/profiler": "^6.0.3",
|
||||||
"@opentelemetry/api": "^1.4.1",
|
"@opentelemetry/api": "^1.4.1",
|
||||||
"@opentelemetry/auto-instrumentations-node": "^0.39.1",
|
"@opentelemetry/auto-instrumentations-node": "^0.39.1",
|
||||||
"@opentelemetry/exporter-trace-otlp-http": "^0.41.2",
|
"@opentelemetry/exporter-trace-otlp-http": "^0.41.2",
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
node_modules/
|
|
3
libraries/mongo-utils/.gitignore
vendored
3
libraries/mongo-utils/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
||||||
|
|
||||||
# managed by monorepo$ bin/update_build_scripts
|
|
||||||
.npmrc
|
|
|
@ -1 +1 @@
|
||||||
20.18.2
|
22.15.1
|
||||||
|
|
|
@ -5,6 +5,6 @@ mongo-utils
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=20.18.2
|
--node-version=22.15.1
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
node_modules/
|
|
5
libraries/o-error/.gitignore
vendored
5
libraries/o-error/.gitignore
vendored
|
@ -1,5 +0,0 @@
|
||||||
.nyc_output
|
|
||||||
coverage
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
.npmrc
|
|
|
@ -1 +1 @@
|
||||||
20.18.2
|
22.15.1
|
||||||
|
|
|
@ -5,6 +5,6 @@ o-error
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=20.18.2
|
--node-version=22.15.1
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1,20 +1,34 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Light-weight helpers for handling JavaScript Errors in node.js and the
|
* Light-weight helpers for handling JavaScript Errors in node.js and the
|
||||||
* browser.
|
* browser.
|
||||||
*/
|
*/
|
||||||
class OError extends Error {
|
class OError extends Error {
|
||||||
|
/**
|
||||||
|
* The error that is the underlying cause of this error
|
||||||
|
*
|
||||||
|
* @type {unknown}
|
||||||
|
*/
|
||||||
|
cause
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of errors encountered as the callback chain is unwound
|
||||||
|
*
|
||||||
|
* @type {TaggedError[] | undefined}
|
||||||
|
*/
|
||||||
|
_oErrorTags
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} message as for built-in Error
|
* @param {string} message as for built-in Error
|
||||||
* @param {Object} [info] extra data to attach to the error
|
* @param {Object} [info] extra data to attach to the error
|
||||||
* @param {Error} [cause] the internal error that caused this error
|
* @param {unknown} [cause] the internal error that caused this error
|
||||||
*/
|
*/
|
||||||
constructor(message, info, cause) {
|
constructor(message, info, cause) {
|
||||||
super(message)
|
super(message)
|
||||||
this.name = this.constructor.name
|
this.name = this.constructor.name
|
||||||
if (info) this.info = info
|
if (info) this.info = info
|
||||||
if (cause) this.cause = cause
|
if (cause) this.cause = cause
|
||||||
/** @private @type {Array<TaggedError> | undefined} */
|
|
||||||
this._oErrorTags // eslint-disable-line
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,7 +45,7 @@ class OError extends Error {
|
||||||
/**
|
/**
|
||||||
* Wrap the given error, which caused this error.
|
* Wrap the given error, which caused this error.
|
||||||
*
|
*
|
||||||
* @param {Error} cause the internal error that caused this error
|
* @param {unknown} cause the internal error that caused this error
|
||||||
* @return {this}
|
* @return {this}
|
||||||
*/
|
*/
|
||||||
withCause(cause) {
|
withCause(cause) {
|
||||||
|
@ -65,13 +79,16 @@ class OError extends Error {
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* @param {Error} error the error to tag
|
* @template {unknown} E
|
||||||
|
* @param {E} error the error to tag
|
||||||
* @param {string} [message] message with which to tag `error`
|
* @param {string} [message] message with which to tag `error`
|
||||||
* @param {Object} [info] extra data with wich to tag `error`
|
* @param {Object} [info] extra data with wich to tag `error`
|
||||||
* @return {Error} the modified `error` argument
|
* @return {E} the modified `error` argument
|
||||||
*/
|
*/
|
||||||
static tag(error, message, info) {
|
static tag(error, message, info) {
|
||||||
const oError = /** @type{OError} */ (error)
|
const oError = /** @type {{ _oErrorTags: TaggedError[] | undefined }} */ (
|
||||||
|
error
|
||||||
|
)
|
||||||
|
|
||||||
if (!oError._oErrorTags) oError._oErrorTags = []
|
if (!oError._oErrorTags) oError._oErrorTags = []
|
||||||
|
|
||||||
|
@ -102,7 +119,7 @@ class OError extends Error {
|
||||||
*
|
*
|
||||||
* If an info property is repeated, the last one wins.
|
* If an info property is repeated, the last one wins.
|
||||||
*
|
*
|
||||||
* @param {Error | null | undefined} error any error (may or may not be an `OError`)
|
* @param {unknown} error any error (may or may not be an `OError`)
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
static getFullInfo(error) {
|
static getFullInfo(error) {
|
||||||
|
@ -129,7 +146,7 @@ class OError extends Error {
|
||||||
* Return the `stack` property from `error`, including the `stack`s for any
|
* Return the `stack` property from `error`, including the `stack`s for any
|
||||||
* tagged errors added with `OError.tag` and for any `cause`s.
|
* tagged errors added with `OError.tag` and for any `cause`s.
|
||||||
*
|
*
|
||||||
* @param {Error | null | undefined} error any error (may or may not be an `OError`)
|
* @param {unknown} error any error (may or may not be an `OError`)
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
static getFullStack(error) {
|
static getFullStack(error) {
|
||||||
|
@ -143,7 +160,7 @@ class OError extends Error {
|
||||||
stack += `\n${oError._oErrorTags.map(tag => tag.stack).join('\n')}`
|
stack += `\n${oError._oErrorTags.map(tag => tag.stack).join('\n')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const causeStack = oError.cause && OError.getFullStack(oError.cause)
|
const causeStack = OError.getFullStack(oError.cause)
|
||||||
if (causeStack) {
|
if (causeStack) {
|
||||||
stack += '\ncaused by:\n' + indent(causeStack)
|
stack += '\ncaused by:\n' + indent(causeStack)
|
||||||
}
|
}
|
||||||
|
|
|
@ -268,6 +268,11 @@ describe('utils', function () {
|
||||||
expect(OError.getFullInfo(null)).to.deep.equal({})
|
expect(OError.getFullInfo(null)).to.deep.equal({})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('works when given a string', function () {
|
||||||
|
const err = 'not an error instance'
|
||||||
|
expect(OError.getFullInfo(err)).to.deep.equal({})
|
||||||
|
})
|
||||||
|
|
||||||
it('works on a normal error', function () {
|
it('works on a normal error', function () {
|
||||||
const err = new Error('foo')
|
const err = new Error('foo')
|
||||||
expect(OError.getFullInfo(err)).to.deep.equal({})
|
expect(OError.getFullInfo(err)).to.deep.equal({})
|
||||||
|
|
|
@ -35,6 +35,14 @@ describe('OError', function () {
|
||||||
expect(err2.cause.message).to.equal('cause 2')
|
expect(err2.cause.message).to.equal('cause 2')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('accepts non-Error causes', function () {
|
||||||
|
const err1 = new OError('foo', {}, 'not-an-error')
|
||||||
|
expect(err1.cause).to.equal('not-an-error')
|
||||||
|
|
||||||
|
const err2 = new OError('foo').withCause('not-an-error')
|
||||||
|
expect(err2.cause).to.equal('not-an-error')
|
||||||
|
})
|
||||||
|
|
||||||
it('handles a custom error type with a cause', function () {
|
it('handles a custom error type with a cause', function () {
|
||||||
function doSomethingBadInternally() {
|
function doSomethingBadInternally() {
|
||||||
throw new Error('internal error')
|
throw new Error('internal error')
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
node_modules/
|
|
4
libraries/object-persistor/.gitignore
vendored
4
libraries/object-persistor/.gitignore
vendored
|
@ -1,4 +0,0 @@
|
||||||
/node_modules
|
|
||||||
*.swp
|
|
||||||
|
|
||||||
.npmrc
|
|
|
@ -1 +1 @@
|
||||||
20.18.2
|
22.15.1
|
||||||
|
|
|
@ -5,6 +5,6 @@ object-persistor
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=20.18.2
|
--node-version=22.15.1
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -305,8 +305,10 @@ module.exports = class FSPersistor extends AbstractPersistor {
|
||||||
|
|
||||||
async _listDirectory(path) {
|
async _listDirectory(path) {
|
||||||
if (this.useSubdirectories) {
|
if (this.useSubdirectories) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/return-await
|
||||||
return await glob(Path.join(path, '**'))
|
return await glob(Path.join(path, '**'))
|
||||||
} else {
|
} else {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/return-await
|
||||||
return await glob(`${path}_*`)
|
return await glob(`${path}_*`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
node_modules/
|
|
5
libraries/overleaf-editor-core/.gitignore
vendored
5
libraries/overleaf-editor-core/.gitignore
vendored
|
@ -1,5 +0,0 @@
|
||||||
/coverage
|
|
||||||
/node_modules
|
|
||||||
|
|
||||||
# managed by monorepo$ bin/update_build_scripts
|
|
||||||
.npmrc
|
|
|
@ -1 +1 @@
|
||||||
20.18.2
|
22.15.1
|
||||||
|
|
|
@ -5,6 +5,6 @@ overleaf-editor-core
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=20.18.2
|
--node-version=22.15.1
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -18,6 +18,7 @@ const MoveFileOperation = require('./lib/operation/move_file_operation')
|
||||||
const SetCommentStateOperation = require('./lib/operation/set_comment_state_operation')
|
const SetCommentStateOperation = require('./lib/operation/set_comment_state_operation')
|
||||||
const EditFileOperation = require('./lib/operation/edit_file_operation')
|
const EditFileOperation = require('./lib/operation/edit_file_operation')
|
||||||
const EditNoOperation = require('./lib/operation/edit_no_operation')
|
const EditNoOperation = require('./lib/operation/edit_no_operation')
|
||||||
|
const EditOperationTransformer = require('./lib/operation/edit_operation_transformer')
|
||||||
const SetFileMetadataOperation = require('./lib/operation/set_file_metadata_operation')
|
const SetFileMetadataOperation = require('./lib/operation/set_file_metadata_operation')
|
||||||
const NoOperation = require('./lib/operation/no_operation')
|
const NoOperation = require('./lib/operation/no_operation')
|
||||||
const Operation = require('./lib/operation')
|
const Operation = require('./lib/operation')
|
||||||
|
@ -43,6 +44,8 @@ const TrackingProps = require('./lib/file_data/tracking_props')
|
||||||
const Range = require('./lib/range')
|
const Range = require('./lib/range')
|
||||||
const CommentList = require('./lib/file_data/comment_list')
|
const CommentList = require('./lib/file_data/comment_list')
|
||||||
const LazyStringFileData = require('./lib/file_data/lazy_string_file_data')
|
const LazyStringFileData = require('./lib/file_data/lazy_string_file_data')
|
||||||
|
const StringFileData = require('./lib/file_data/string_file_data')
|
||||||
|
const EditOperationBuilder = require('./lib/operation/edit_operation_builder')
|
||||||
|
|
||||||
exports.AddCommentOperation = AddCommentOperation
|
exports.AddCommentOperation = AddCommentOperation
|
||||||
exports.Author = Author
|
exports.Author = Author
|
||||||
|
@ -58,6 +61,7 @@ exports.DeleteCommentOperation = DeleteCommentOperation
|
||||||
exports.File = File
|
exports.File = File
|
||||||
exports.FileMap = FileMap
|
exports.FileMap = FileMap
|
||||||
exports.LazyStringFileData = LazyStringFileData
|
exports.LazyStringFileData = LazyStringFileData
|
||||||
|
exports.StringFileData = StringFileData
|
||||||
exports.History = History
|
exports.History = History
|
||||||
exports.Label = Label
|
exports.Label = Label
|
||||||
exports.AddFileOperation = AddFileOperation
|
exports.AddFileOperation = AddFileOperation
|
||||||
|
@ -65,6 +69,8 @@ exports.MoveFileOperation = MoveFileOperation
|
||||||
exports.SetCommentStateOperation = SetCommentStateOperation
|
exports.SetCommentStateOperation = SetCommentStateOperation
|
||||||
exports.EditFileOperation = EditFileOperation
|
exports.EditFileOperation = EditFileOperation
|
||||||
exports.EditNoOperation = EditNoOperation
|
exports.EditNoOperation = EditNoOperation
|
||||||
|
exports.EditOperationBuilder = EditOperationBuilder
|
||||||
|
exports.EditOperationTransformer = EditOperationTransformer
|
||||||
exports.SetFileMetadataOperation = SetFileMetadataOperation
|
exports.SetFileMetadataOperation = SetFileMetadataOperation
|
||||||
exports.NoOperation = NoOperation
|
exports.NoOperation = NoOperation
|
||||||
exports.Operation = Operation
|
exports.Operation = Operation
|
||||||
|
|
|
@ -13,7 +13,7 @@ const V2DocVersions = require('./v2_doc_versions')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @import Author from "./author"
|
* @import Author from "./author"
|
||||||
* @import { BlobStore } from "./types"
|
* @import { BlobStore, RawChange } from "./types"
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,7 +54,7 @@ class Change {
|
||||||
/**
|
/**
|
||||||
* For serialization.
|
* For serialization.
|
||||||
*
|
*
|
||||||
* @return {Object}
|
* @return {RawChange}
|
||||||
*/
|
*/
|
||||||
toRaw() {
|
toRaw() {
|
||||||
function toRaw(object) {
|
function toRaw(object) {
|
||||||
|
|
|
@ -88,6 +88,14 @@ class StringFileData extends FileData {
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return docstore view of a doc: each line separated
|
||||||
|
* @return {string[]}
|
||||||
|
*/
|
||||||
|
getLines() {
|
||||||
|
return this.getContent({ filterTrackedDeletes: true }).split('\n')
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
getByteLength() {
|
getByteLength() {
|
||||||
return Buffer.byteLength(this.content)
|
return Buffer.byteLength(this.content)
|
||||||
|
|
|
@ -36,6 +36,20 @@ class EditOperationBuilder {
|
||||||
}
|
}
|
||||||
throw new Error('Unsupported operation in EditOperationBuilder.fromJSON')
|
throw new Error('Unsupported operation in EditOperationBuilder.fromJSON')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {unknown} raw
|
||||||
|
* @return {raw is RawEditOperation}
|
||||||
|
*/
|
||||||
|
static isValid(raw) {
|
||||||
|
return (
|
||||||
|
isTextOperation(raw) ||
|
||||||
|
isRawAddCommentOperation(raw) ||
|
||||||
|
isRawDeleteCommentOperation(raw) ||
|
||||||
|
isRawSetCommentStateOperation(raw) ||
|
||||||
|
isRawEditNoOperation(raw)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
node_modules/
|
|
3
libraries/promise-utils/.gitignore
vendored
3
libraries/promise-utils/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
||||||
|
|
||||||
# managed by monorepo$ bin/update_build_scripts
|
|
||||||
.npmrc
|
|
|
@ -1 +1 @@
|
||||||
20.18.2
|
22.15.1
|
||||||
|
|
|
@ -5,6 +5,6 @@ promise-utils
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=20.18.2
|
--node-version=22.15.1
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
node_modules/
|
|
13
libraries/ranges-tracker/.gitignore
vendored
13
libraries/ranges-tracker/.gitignore
vendored
|
@ -1,13 +0,0 @@
|
||||||
**.swp
|
|
||||||
|
|
||||||
app.js
|
|
||||||
app/js/
|
|
||||||
test/unit/js/
|
|
||||||
public/build/
|
|
||||||
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
/public/js/chat.js
|
|
||||||
plato/
|
|
||||||
|
|
||||||
.npmrc
|
|
|
@ -1 +1 @@
|
||||||
20.18.2
|
22.15.1
|
||||||
|
|
|
@ -5,6 +5,6 @@ ranges-tracker
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=20.18.2
|
--node-version=22.15.1
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
node_modules/
|
|
13
libraries/redis-wrapper/.gitignore
vendored
13
libraries/redis-wrapper/.gitignore
vendored
|
@ -1,13 +0,0 @@
|
||||||
**.swp
|
|
||||||
|
|
||||||
app.js
|
|
||||||
app/js/
|
|
||||||
test/unit/js/
|
|
||||||
public/build/
|
|
||||||
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
/public/js/chat.js
|
|
||||||
plato/
|
|
||||||
|
|
||||||
.npmrc
|
|
|
@ -1 +1 @@
|
||||||
20.18.2
|
22.15.1
|
||||||
|
|
|
@ -97,7 +97,8 @@ module.exports = class RedisLocker {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Callback} callback
|
* @param {string} id
|
||||||
|
* @param {function(Error, boolean, string): void} callback
|
||||||
*/
|
*/
|
||||||
tryLock(id, callback) {
|
tryLock(id, callback) {
|
||||||
if (callback == null) {
|
if (callback == null) {
|
||||||
|
@ -106,7 +107,7 @@ module.exports = class RedisLocker {
|
||||||
const lockValue = this.randomLock()
|
const lockValue = this.randomLock()
|
||||||
const key = this.getKey(id)
|
const key = this.getKey(id)
|
||||||
const startTime = Date.now()
|
const startTime = Date.now()
|
||||||
return this.rclient.set(
|
this.rclient.set(
|
||||||
key,
|
key,
|
||||||
lockValue,
|
lockValue,
|
||||||
'EX',
|
'EX',
|
||||||
|
@ -121,7 +122,7 @@ module.exports = class RedisLocker {
|
||||||
const timeTaken = Date.now() - startTime
|
const timeTaken = Date.now() - startTime
|
||||||
if (timeTaken > MAX_REDIS_REQUEST_LENGTH) {
|
if (timeTaken > MAX_REDIS_REQUEST_LENGTH) {
|
||||||
// took too long, so try to free the lock
|
// took too long, so try to free the lock
|
||||||
return this.releaseLock(id, lockValue, function (err, result) {
|
this.releaseLock(id, lockValue, function (err, result) {
|
||||||
if (err != null) {
|
if (err != null) {
|
||||||
return callback(err)
|
return callback(err)
|
||||||
} // error freeing lock
|
} // error freeing lock
|
||||||
|
@ -139,7 +140,8 @@ module.exports = class RedisLocker {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Callback} callback
|
* @param {string} id
|
||||||
|
* @param {function(Error, string): void} callback
|
||||||
*/
|
*/
|
||||||
getLock(id, callback) {
|
getLock(id, callback) {
|
||||||
if (callback == null) {
|
if (callback == null) {
|
||||||
|
@ -153,7 +155,7 @@ module.exports = class RedisLocker {
|
||||||
return callback(e)
|
return callback(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.tryLock(id, (error, gotLock, lockValue) => {
|
this.tryLock(id, (error, gotLock, lockValue) => {
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
return callback(error)
|
return callback(error)
|
||||||
}
|
}
|
||||||
|
@ -173,14 +175,15 @@ module.exports = class RedisLocker {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Callback} callback
|
* @param {string} id
|
||||||
|
* @param {function(Error, boolean): void} callback
|
||||||
*/
|
*/
|
||||||
checkLock(id, callback) {
|
checkLock(id, callback) {
|
||||||
if (callback == null) {
|
if (callback == null) {
|
||||||
callback = function () {}
|
callback = function () {}
|
||||||
}
|
}
|
||||||
const key = this.getKey(id)
|
const key = this.getKey(id)
|
||||||
return this.rclient.exists(key, (err, exists) => {
|
this.rclient.exists(key, (err, exists) => {
|
||||||
if (err != null) {
|
if (err != null) {
|
||||||
return callback(err)
|
return callback(err)
|
||||||
}
|
}
|
||||||
|
@ -196,30 +199,26 @@ module.exports = class RedisLocker {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Callback} callback
|
* @param {string} id
|
||||||
|
* @param {string} lockValue
|
||||||
|
* @param {function(Error, boolean): void} callback
|
||||||
*/
|
*/
|
||||||
releaseLock(id, lockValue, callback) {
|
releaseLock(id, lockValue, callback) {
|
||||||
const key = this.getKey(id)
|
const key = this.getKey(id)
|
||||||
return this.rclient.eval(
|
this.rclient.eval(UNLOCK_SCRIPT, 1, key, lockValue, (err, result) => {
|
||||||
UNLOCK_SCRIPT,
|
if (err != null) {
|
||||||
1,
|
return callback(err)
|
||||||
key,
|
} else if (result != null && result !== 1) {
|
||||||
lockValue,
|
// successful unlock should release exactly one key
|
||||||
(err, result) => {
|
logger.error(
|
||||||
if (err != null) {
|
{ id, key, lockValue, redis_err: err, redis_result: result },
|
||||||
return callback(err)
|
'unlocking error'
|
||||||
} else if (result != null && result !== 1) {
|
)
|
||||||
// successful unlock should release exactly one key
|
metrics.inc(this.metricsPrefix + '-unlock-error')
|
||||||
logger.error(
|
return callback(new Error('tried to release timed out lock'))
|
||||||
{ id, key, lockValue, redis_err: err, redis_result: result },
|
} else {
|
||||||
'unlocking error'
|
return callback(null, result)
|
||||||
)
|
|
||||||
metrics.inc(this.metricsPrefix + '-unlock-error')
|
|
||||||
return callback(new Error('tried to release timed out lock'))
|
|
||||||
} else {
|
|
||||||
return callback(null, result)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,6 @@ redis-wrapper
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=20.18.2
|
--node-version=22.15.1
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
node_modules/
|
|
5
libraries/settings/.gitignore
vendored
5
libraries/settings/.gitignore
vendored
|
@ -1,5 +0,0 @@
|
||||||
/.npmrc
|
|
||||||
/node_modules
|
|
||||||
|
|
||||||
# managed by monorepo$ bin/update_build_scripts
|
|
||||||
.npmrc
|
|
|
@ -1 +1 @@
|
||||||
20.18.2
|
22.15.1
|
||||||
|
|
|
@ -5,6 +5,6 @@ settings
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=20.18.2
|
--node-version=22.15.1
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
node_modules/
|
|
3
libraries/stream-utils/.gitignore
vendored
3
libraries/stream-utils/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
||||||
|
|
||||||
# managed by monorepo$ bin/update_build_scripts
|
|
||||||
.npmrc
|
|
|
@ -1 +1 @@
|
||||||
20.18.2
|
22.15.1
|
||||||
|
|
|
@ -5,6 +5,6 @@ stream-utils
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--is-library=True
|
--is-library=True
|
||||||
--node-version=20.18.2
|
--node-version=22.15.1
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
1662
package-lock.json
generated
1662
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -37,7 +37,7 @@
|
||||||
},
|
},
|
||||||
"swagger-tools": {
|
"swagger-tools": {
|
||||||
"body-parser": "1.20.3",
|
"body-parser": "1.20.3",
|
||||||
"multer": "1.4.5-lts.1",
|
"multer": "2.0.0",
|
||||||
"path-to-regexp": "3.3.0",
|
"path-to-regexp": "3.3.0",
|
||||||
"qs": "6.13.0"
|
"qs": "6.13.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# Overleaf Base Image (sharelatex/sharelatex-base)
|
# Overleaf Base Image (sharelatex/sharelatex-base)
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
FROM phusion/baseimage:noble-1.0.0
|
FROM phusion/baseimage:noble-1.0.2
|
||||||
|
|
||||||
# Makes sure LuaTex cache is writable
|
# Makes sure LuaTex cache is writable
|
||||||
# -----------------------------------
|
# -----------------------------------
|
||||||
|
@ -10,7 +10,7 @@ ENV TEXMFVAR=/var/lib/overleaf/tmp/texmf-var
|
||||||
|
|
||||||
# Update to ensure dependencies are updated
|
# Update to ensure dependencies are updated
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
ENV REBUILT_AFTER="2025-03-27"
|
ENV REBUILT_AFTER="2025-05-19"
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
# --------------------
|
# --------------------
|
||||||
|
@ -30,7 +30,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||||
# install Node.js https://github.com/nodesource/distributions#nodejs
|
# install Node.js https://github.com/nodesource/distributions#nodejs
|
||||||
&& mkdir -p /etc/apt/keyrings \
|
&& mkdir -p /etc/apt/keyrings \
|
||||||
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
|
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
|
||||||
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \
|
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y nodejs \
|
&& apt-get install -y nodejs \
|
||||||
\
|
\
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
*/20 * * * * root /overleaf/cron/project-history-periodic-flush.sh >> /var/log/overleaf/cron-project-history-periodic-flush.log 2>&1
|
*/20 * * * * root /overleaf/cron/project-history-periodic-flush.sh >> /var/log/overleaf/cron-project-history-periodic-flush.log 2>&1
|
||||||
30 * * * * root /overleaf/cron/project-history-retry-soft.sh >> /var/log/overleaf/project-history-retry-soft.log 2>&1
|
30 * * * * root /overleaf/cron/project-history-retry-soft.sh >> /var/log/overleaf/project-history-retry-soft.log 2>&1
|
||||||
45 * * * * root /overleaf/cron/project-history-retry-hard.sh >> /var/log/overleaf/project-history-retry-hard.log 2>&1
|
45 * * * * root /overleaf/cron/project-history-retry-hard.sh >> /var/log/overleaf/project-history-retry-hard.log 2>&1
|
||||||
|
0 3 * * * root /overleaf/cron/project-history-flush-all.sh >> /var/log/overleaf/project-history-flush-all.log 2>&1
|
||||||
|
|
|
@ -79,6 +79,7 @@ const settings = {
|
||||||
host: process.env.OVERLEAF_REDIS_HOST || 'dockerhost',
|
host: process.env.OVERLEAF_REDIS_HOST || 'dockerhost',
|
||||||
port: process.env.OVERLEAF_REDIS_PORT || '6379',
|
port: process.env.OVERLEAF_REDIS_PORT || '6379',
|
||||||
password: process.env.OVERLEAF_REDIS_PASS || undefined,
|
password: process.env.OVERLEAF_REDIS_PASS || undefined,
|
||||||
|
tls: process.env.OVERLEAF_REDIS_TLS === 'true' ? {} : undefined,
|
||||||
key_schema: {
|
key_schema: {
|
||||||
// document-updater
|
// document-updater
|
||||||
blockingKey({ doc_id }) {
|
blockingKey({ doc_id }) {
|
||||||
|
|
14
server-ce/cron/project-history-flush-all.sh
Executable file
14
server-ce/cron/project-history-flush-all.sh
Executable file
|
@ -0,0 +1,14 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
echo "---------------------------------"
|
||||||
|
echo "Flush all project-history changes"
|
||||||
|
echo "---------------------------------"
|
||||||
|
date
|
||||||
|
|
||||||
|
source /etc/container_environment.sh
|
||||||
|
source /etc/overleaf/env.sh
|
||||||
|
cd /overleaf/services/project-history && node scripts/flush_all.js
|
||||||
|
|
||||||
|
echo "Done flushing all project-history changes"
|
14
server-ce/hotfix/5.4.1/Dockerfile
Normal file
14
server-ce/hotfix/5.4.1/Dockerfile
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
FROM sharelatex/sharelatex:5.4.0
|
||||||
|
|
||||||
|
RUN apt update && apt install -y linux-libc-dev \
|
||||||
|
&& unattended-upgrade --verbose --no-minimal-upgrade-steps \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
COPY package-lock.json.diff .
|
||||||
|
RUN patch package-lock.json < package-lock.json.diff
|
||||||
|
RUN npm install --omit=dev
|
||||||
|
|
||||||
|
|
||||||
|
# fix tls configuration in redis
|
||||||
|
COPY issue_24996.patch .
|
||||||
|
RUN patch -p0 /etc/overleaf/settings.js < issue_24996.patch \
|
||||||
|
&& rm issue_24996.patch
|
10
server-ce/hotfix/5.4.1/issue_24996.patch
Normal file
10
server-ce/hotfix/5.4.1/issue_24996.patch
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
--- settings.js
|
||||||
|
+++ settings.js
|
||||||
|
@@ -79,6 +79,7 @@ const settings = {
|
||||||
|
host: process.env.OVERLEAF_REDIS_HOST || 'dockerhost',
|
||||||
|
port: process.env.OVERLEAF_REDIS_PORT || '6379',
|
||||||
|
password: process.env.OVERLEAF_REDIS_PASS || undefined,
|
||||||
|
+ tls: process.env.OVERLEAF_REDIS_TLS === 'true' ? {} : undefined,
|
||||||
|
key_schema: {
|
||||||
|
// document-updater
|
||||||
|
blockingKey({ doc_id }) {
|
2024
server-ce/hotfix/5.4.1/package-lock.json.diff
Normal file
2024
server-ce/hotfix/5.4.1/package-lock.json.diff
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,4 @@
|
||||||
FROM node:20.18.2
|
FROM node:22.15.1
|
||||||
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - \
|
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - \
|
||||||
&& echo \
|
&& echo \
|
||||||
"deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
|
"deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
|
||||||
|
|
|
@ -294,7 +294,7 @@ describe('admin panel', function () {
|
||||||
|
|
||||||
cy.log('navigate to thrashed projects and delete the project')
|
cy.log('navigate to thrashed projects and delete the project')
|
||||||
cy.get('.project-list-sidebar-scroll').within(() => {
|
cy.get('.project-list-sidebar-scroll').within(() => {
|
||||||
cy.findByText('Trashed Projects').click()
|
cy.findByText('Trashed projects').click()
|
||||||
})
|
})
|
||||||
findProjectRow(deletedProjectName).within(() =>
|
findProjectRow(deletedProjectName).within(() =>
|
||||||
cy.findByRole('button', { name: 'Delete' }).click()
|
cy.findByRole('button', { name: 'Delete' }).click()
|
||||||
|
@ -319,7 +319,7 @@ describe('admin panel', function () {
|
||||||
login(user1)
|
login(user1)
|
||||||
cy.visit('/project')
|
cy.visit('/project')
|
||||||
cy.get('.project-list-sidebar-scroll').within(() => {
|
cy.get('.project-list-sidebar-scroll').within(() => {
|
||||||
cy.findByText('Trashed Projects').click()
|
cy.findByText('Trashed projects').click()
|
||||||
})
|
})
|
||||||
cy.findByText(`${deletedProjectName} (Restored)`)
|
cy.findByText(`${deletedProjectName} (Restored)`)
|
||||||
})
|
})
|
||||||
|
|
|
@ -51,7 +51,7 @@ describe('Project creation and compilation', function () {
|
||||||
login('user@example.com')
|
login('user@example.com')
|
||||||
|
|
||||||
createProject(sourceProjectName, {
|
createProject(sourceProjectName, {
|
||||||
type: 'Example Project',
|
type: 'Example project',
|
||||||
open: false,
|
open: false,
|
||||||
}).as('sourceProjectId')
|
}).as('sourceProjectId')
|
||||||
createProject(targetProjectName)
|
createProject(targetProjectName)
|
||||||
|
@ -79,7 +79,7 @@ describe('Project creation and compilation', function () {
|
||||||
const targetProjectName = `${sourceProjectName}-target`
|
const targetProjectName = `${sourceProjectName}-target`
|
||||||
login('user@example.com')
|
login('user@example.com')
|
||||||
createProject(sourceProjectName, {
|
createProject(sourceProjectName, {
|
||||||
type: 'Example Project',
|
type: 'Example project',
|
||||||
open: false,
|
open: false,
|
||||||
}).as('sourceProjectId')
|
}).as('sourceProjectId')
|
||||||
createProject(targetProjectName).as('targetProjectId')
|
createProject(targetProjectName).as('targetProjectId')
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
import {
|
import {
|
||||||
createNewFile,
|
createNewFile,
|
||||||
createProject,
|
createProject,
|
||||||
enableLinkSharing,
|
|
||||||
openFile,
|
|
||||||
openProjectById,
|
openProjectById,
|
||||||
openProjectViaLinkSharingAsUser,
|
|
||||||
toggleTrackChanges,
|
|
||||||
} from './helpers/project'
|
} from './helpers/project'
|
||||||
import { isExcludedBySharding, startWith } from './helpers/config'
|
import { isExcludedBySharding, startWith } from './helpers/config'
|
||||||
import { ensureUserExists, login } from './helpers/login'
|
import { ensureUserExists, login } from './helpers/login'
|
||||||
|
@ -26,7 +22,7 @@ describe('editor', () => {
|
||||||
beforeWithReRunOnTestRetry(function () {
|
beforeWithReRunOnTestRetry(function () {
|
||||||
projectName = `project-${uuid()}`
|
projectName = `project-${uuid()}`
|
||||||
login('user@example.com')
|
login('user@example.com')
|
||||||
createProject(projectName, { type: 'Example Project', open: false }).then(
|
createProject(projectName, { type: 'Example project', open: false }).then(
|
||||||
id => (projectId = id)
|
id => (projectId = id)
|
||||||
)
|
)
|
||||||
;({ recompile, waitForCompileRateLimitCoolOff } =
|
;({ recompile, waitForCompileRateLimitCoolOff } =
|
||||||
|
@ -66,12 +62,12 @@ describe('editor', () => {
|
||||||
|
|
||||||
cy.log('add word to dictionary')
|
cy.log('add word to dictionary')
|
||||||
cy.get('.ol-cm-spelling-error').contains(word).rightclick()
|
cy.get('.ol-cm-spelling-error').contains(word).rightclick()
|
||||||
cy.findByText('Add to Dictionary').click()
|
cy.findByText('Add to dictionary').click()
|
||||||
cy.get('.ol-cm-spelling-error').should('not.exist')
|
cy.get('.ol-cm-spelling-error').should('not.exist')
|
||||||
|
|
||||||
cy.log('remove word from dictionary')
|
cy.log('remove word from dictionary')
|
||||||
cy.get('button').contains('Menu').click()
|
cy.get('button').contains('Menu').click()
|
||||||
cy.get('button').contains('Edit').click()
|
cy.get('button#dictionary-settings').contains('Edit').click()
|
||||||
cy.get('[id="dictionary-modal"]').within(() => {
|
cy.get('[id="dictionary-modal"]').within(() => {
|
||||||
cy.findByText(word)
|
cy.findByText(word)
|
||||||
.parent()
|
.parent()
|
||||||
|
@ -92,136 +88,6 @@ describe('editor', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('collaboration', () => {
|
|
||||||
beforeWithReRunOnTestRetry(function () {
|
|
||||||
enableLinkSharing().then(({ linkSharingReadAndWrite }) => {
|
|
||||||
const email = 'collaborator@example.com'
|
|
||||||
login(email)
|
|
||||||
openProjectViaLinkSharingAsUser(
|
|
||||||
linkSharingReadAndWrite,
|
|
||||||
projectName,
|
|
||||||
email
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
login('user@example.com')
|
|
||||||
waitForCompileRateLimitCoolOff(() => {
|
|
||||||
openProjectById(projectId)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('track-changes', () => {
|
|
||||||
cy.log('disable track-changes before populating doc')
|
|
||||||
toggleTrackChanges(false)
|
|
||||||
|
|
||||||
const fileName = createNewFile()
|
|
||||||
const oldContent = 'oldContent'
|
|
||||||
cy.get('.cm-line').type(`${oldContent}\n\nstatic`)
|
|
||||||
|
|
||||||
cy.log('recompile to force flush')
|
|
||||||
recompile()
|
|
||||||
|
|
||||||
cy.log('enable track-changes for everyone')
|
|
||||||
toggleTrackChanges(true)
|
|
||||||
|
|
||||||
login('collaborator@example.com')
|
|
||||||
waitForCompileRateLimitCoolOff(() => {
|
|
||||||
openProjectById(projectId)
|
|
||||||
})
|
|
||||||
openFile(fileName, 'static')
|
|
||||||
|
|
||||||
cy.log('make changes in main file')
|
|
||||||
// cy.type() "clicks" in the center of the selected element before typing. This "click" discards the text as selected by the dblclick.
|
|
||||||
// Go down to the lower level event based typing, the frontend tests in web use similar events.
|
|
||||||
cy.get('.cm-editor').as('editor')
|
|
||||||
cy.get('@editor').findByText(oldContent).dblclick()
|
|
||||||
cy.get('@editor').trigger('keydown', { key: 'Delete' })
|
|
||||||
cy.get('@editor').trigger('keydown', { key: 'Enter' })
|
|
||||||
cy.get('@editor').trigger('keydown', { key: 'Enter' })
|
|
||||||
|
|
||||||
cy.log('recompile to force flush')
|
|
||||||
recompile()
|
|
||||||
|
|
||||||
login('user@example.com')
|
|
||||||
waitForCompileRateLimitCoolOff(() => {
|
|
||||||
openProjectById(projectId)
|
|
||||||
})
|
|
||||||
openFile(fileName, 'static')
|
|
||||||
|
|
||||||
cy.log('reject changes')
|
|
||||||
cy.contains('.toolbar-item', 'Review').click()
|
|
||||||
cy.get('.cm-content').should('not.contain.text', oldContent)
|
|
||||||
cy.findByText('Reject change').click({ force: true })
|
|
||||||
cy.contains('.toolbar-item', 'Review').click()
|
|
||||||
|
|
||||||
cy.log('recompile to force flush')
|
|
||||||
recompile()
|
|
||||||
|
|
||||||
cy.log('verify the changes are applied')
|
|
||||||
cy.get('.cm-content').should('contain.text', oldContent)
|
|
||||||
|
|
||||||
cy.log('disable track-changes for everyone again')
|
|
||||||
toggleTrackChanges(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('track-changes rich text', () => {
|
|
||||||
cy.log('disable track-changes before populating doc')
|
|
||||||
toggleTrackChanges(false)
|
|
||||||
|
|
||||||
const fileName = createNewFile()
|
|
||||||
const oldContent = 'oldContent'
|
|
||||||
cy.get('.cm-line').type(`\\section{{}${oldContent}}\n\nstatic`)
|
|
||||||
|
|
||||||
cy.log('recompile to force flush')
|
|
||||||
recompile()
|
|
||||||
|
|
||||||
cy.log('enable track-changes for everyone')
|
|
||||||
toggleTrackChanges(true)
|
|
||||||
|
|
||||||
login('collaborator@example.com')
|
|
||||||
waitForCompileRateLimitCoolOff(() => {
|
|
||||||
openProjectById(projectId)
|
|
||||||
})
|
|
||||||
cy.log('enable visual editor and make changes in main file')
|
|
||||||
cy.findByText('Visual Editor').click()
|
|
||||||
|
|
||||||
openFile(fileName, 'static')
|
|
||||||
|
|
||||||
// cy.type() "clicks" in the center of the selected element before typing. This "click" discards the text as selected by the dblclick.
|
|
||||||
// Go down to the lower level event based typing, the frontend tests in web use similar events.
|
|
||||||
cy.get('.cm-editor').as('editor')
|
|
||||||
cy.get('@editor').findByText(oldContent).dblclick()
|
|
||||||
cy.get('@editor').trigger('keydown', { key: 'Delete' })
|
|
||||||
cy.get('@editor').trigger('keydown', { key: 'Enter' })
|
|
||||||
cy.get('@editor').trigger('keydown', { key: 'Enter' })
|
|
||||||
|
|
||||||
cy.log('recompile to force flush')
|
|
||||||
recompile()
|
|
||||||
|
|
||||||
login('user@example.com')
|
|
||||||
waitForCompileRateLimitCoolOff(() => {
|
|
||||||
openProjectById(projectId)
|
|
||||||
})
|
|
||||||
openFile(fileName, 'static')
|
|
||||||
|
|
||||||
cy.log('reject changes')
|
|
||||||
cy.contains('.toolbar-item', 'Review').click()
|
|
||||||
cy.get('.cm-content').should('not.contain.text', oldContent)
|
|
||||||
cy.findAllByText('Reject change').first().click({ force: true })
|
|
||||||
cy.contains('.toolbar-item', 'Review').click()
|
|
||||||
|
|
||||||
cy.log('recompile to force flush')
|
|
||||||
recompile()
|
|
||||||
|
|
||||||
cy.log('verify the changes are applied in the visual editor')
|
|
||||||
cy.findByText('Visual Editor').click()
|
|
||||||
cy.get('.cm-content').should('contain.text', oldContent)
|
|
||||||
|
|
||||||
cy.log('disable track-changes for everyone again')
|
|
||||||
toggleTrackChanges(false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('editor', () => {
|
describe('editor', () => {
|
||||||
it('renders jpg', () => {
|
it('renders jpg', () => {
|
||||||
cy.findByTestId('file-tree').findByText('frog.jpg').click()
|
cy.findByTestId('file-tree').findByText('frog.jpg').click()
|
||||||
|
|
|
@ -46,7 +46,7 @@ describe('git-bridge', function () {
|
||||||
|
|
||||||
function maybeClearAllTokens() {
|
function maybeClearAllTokens() {
|
||||||
cy.visit('/user/settings')
|
cy.visit('/user/settings')
|
||||||
cy.findByText('Git Integration')
|
cy.findByText('Git integration')
|
||||||
cy.get('button')
|
cy.get('button')
|
||||||
.contains(/Generate token|Add another token/)
|
.contains(/Generate token|Add another token/)
|
||||||
.then(btn => {
|
.then(btn => {
|
||||||
|
@ -63,7 +63,7 @@ describe('git-bridge', function () {
|
||||||
it('should render the git-bridge UI in the settings', () => {
|
it('should render the git-bridge UI in the settings', () => {
|
||||||
maybeClearAllTokens()
|
maybeClearAllTokens()
|
||||||
cy.visit('/user/settings')
|
cy.visit('/user/settings')
|
||||||
cy.findByText('Git Integration')
|
cy.findByText('Git integration')
|
||||||
cy.get('button').contains('Generate token').click()
|
cy.get('button').contains('Generate token').click()
|
||||||
cy.get('code')
|
cy.get('code')
|
||||||
.contains(/olp_[a-zA-Z0-9]{16}/)
|
.contains(/olp_[a-zA-Z0-9]{16}/)
|
||||||
|
@ -93,7 +93,7 @@ describe('git-bridge', function () {
|
||||||
cy.get('code').contains(`git clone ${gitURL(id.toString())}`)
|
cy.get('code').contains(`git clone ${gitURL(id.toString())}`)
|
||||||
})
|
})
|
||||||
cy.findByRole('button', {
|
cy.findByRole('button', {
|
||||||
name: 'Generate token',
|
name: /generate token/i,
|
||||||
}).click()
|
}).click()
|
||||||
cy.get('code').contains(/olp_[a-zA-Z0-9]{16}/)
|
cy.get('code').contains(/olp_[a-zA-Z0-9]{16}/)
|
||||||
})
|
})
|
||||||
|
@ -107,7 +107,7 @@ describe('git-bridge', function () {
|
||||||
cy.get('code').contains(`git clone ${gitURL(id.toString())}`)
|
cy.get('code').contains(`git clone ${gitURL(id.toString())}`)
|
||||||
})
|
})
|
||||||
cy.findByText('Generate token').should('not.exist')
|
cy.findByText('Generate token').should('not.exist')
|
||||||
cy.findByText(/generate a new one in Account Settings/)
|
cy.findByText(/generate a new one in Account settings/i)
|
||||||
cy.findByText('Go to settings')
|
cy.findByText('Go to settings')
|
||||||
.should('have.attr', 'target', '_blank')
|
.should('have.attr', 'target', '_blank')
|
||||||
.and('have.attr', 'href', '/user/settings')
|
.and('have.attr', 'href', '/user/settings')
|
||||||
|
@ -196,7 +196,7 @@ describe('git-bridge', function () {
|
||||||
cy.get('code').contains(`git clone ${gitURL(projectId.toString())}`)
|
cy.get('code').contains(`git clone ${gitURL(projectId.toString())}`)
|
||||||
})
|
})
|
||||||
cy.findByRole('button', {
|
cy.findByRole('button', {
|
||||||
name: 'Generate token',
|
name: /generate token/i,
|
||||||
}).click()
|
}).click()
|
||||||
cy.get('code')
|
cy.get('code')
|
||||||
.contains(/olp_[a-zA-Z0-9]{16}/)
|
.contains(/olp_[a-zA-Z0-9]{16}/)
|
||||||
|
@ -365,7 +365,7 @@ Hello world
|
||||||
it('should not render the git-bridge UI in the settings', () => {
|
it('should not render the git-bridge UI in the settings', () => {
|
||||||
login('user@example.com')
|
login('user@example.com')
|
||||||
cy.visit('/user/settings')
|
cy.visit('/user/settings')
|
||||||
cy.findByText('Git Integration').should('not.exist')
|
cy.findByText('Git integration').should('not.exist')
|
||||||
})
|
})
|
||||||
it('should not render the git-bridge UI in the editor', function () {
|
it('should not render the git-bridge UI in the editor', function () {
|
||||||
login('user@example.com')
|
login('user@example.com')
|
||||||
|
|
|
@ -5,11 +5,11 @@ import { v4 as uuid } from 'uuid'
|
||||||
export function createProject(
|
export function createProject(
|
||||||
name: string,
|
name: string,
|
||||||
{
|
{
|
||||||
type = 'Blank Project',
|
type = 'Blank project',
|
||||||
newProjectButtonMatcher = /new project/i,
|
newProjectButtonMatcher = /new project/i,
|
||||||
open = true,
|
open = true,
|
||||||
}: {
|
}: {
|
||||||
type?: 'Blank Project' | 'Example Project'
|
type?: 'Blank project' | 'Example project'
|
||||||
newProjectButtonMatcher?: RegExp
|
newProjectButtonMatcher?: RegExp
|
||||||
open?: boolean
|
open?: boolean
|
||||||
} = {}
|
} = {}
|
||||||
|
@ -37,7 +37,7 @@ export function createProject(
|
||||||
}
|
}
|
||||||
cy.findAllByRole('button').contains(newProjectButtonMatcher).click()
|
cy.findAllByRole('button').contains(newProjectButtonMatcher).click()
|
||||||
// FIXME: This should only look in the left menu
|
// FIXME: This should only look in the left menu
|
||||||
cy.findAllByText(type).first().click()
|
cy.findAllByText(new RegExp(type, 'i')).first().click()
|
||||||
cy.findByRole('dialog').within(() => {
|
cy.findByRole('dialog').within(() => {
|
||||||
cy.get('input').type(name)
|
cy.get('input').type(name)
|
||||||
cy.findByText('Create').click()
|
cy.findByText('Create').click()
|
||||||
|
@ -215,37 +215,3 @@ export function createNewFile() {
|
||||||
|
|
||||||
return fileName
|
return fileName
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toggleTrackChanges(state: boolean) {
|
|
||||||
cy.findByText('Review').click()
|
|
||||||
cy.get('.track-changes-menu-button').then(el => {
|
|
||||||
// when the menu is expanded renders the `expand_more` icon,
|
|
||||||
// and the `chevron_right` icon when it's collapsed
|
|
||||||
if (!el.text().includes('expand_more')) {
|
|
||||||
el.click()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
cy.findByText('Everyone')
|
|
||||||
.parent()
|
|
||||||
.within(() => {
|
|
||||||
cy.get('.form-check-input').then(el => {
|
|
||||||
if (el.prop('checked') === state) return
|
|
||||||
|
|
||||||
const id = uuid()
|
|
||||||
const alias = `@${id}`
|
|
||||||
cy.intercept({
|
|
||||||
method: 'POST',
|
|
||||||
url: '**/track_changes',
|
|
||||||
times: 1,
|
|
||||||
}).as(id)
|
|
||||||
if (state) {
|
|
||||||
cy.get('.form-check-input').check()
|
|
||||||
} else {
|
|
||||||
cy.get('.form-check-input').uncheck()
|
|
||||||
}
|
|
||||||
cy.wait(alias)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
cy.contains('.toolbar-item', 'Review').click()
|
|
||||||
}
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ describe('Project List', () => {
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
login(REGULAR_USER)
|
login(REGULAR_USER)
|
||||||
createProject(projectName, { type: 'Example Project', open: false })
|
createProject(projectName, { type: 'Example project', open: false })
|
||||||
})
|
})
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
login(REGULAR_USER)
|
login(REGULAR_USER)
|
||||||
|
|
|
@ -59,7 +59,9 @@ describe('SandboxedCompiles', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
function checkSyncTeX() {
|
function checkSyncTeX() {
|
||||||
describe('SyncTeX', function () {
|
// TODO(25342): re-enable
|
||||||
|
// eslint-disable-next-line mocha/no-skipped-tests
|
||||||
|
describe.skip('SyncTeX', function () {
|
||||||
let projectName: string
|
let projectName: string
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
projectName = `Project ${uuid()}`
|
projectName = `Project ${uuid()}`
|
||||||
|
|
|
@ -47,7 +47,9 @@ describe('Templates', () => {
|
||||||
cy.url().should('match', /\/templates$/)
|
cy.url().should('match', /\/templates$/)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should have templates feature', () => {
|
// TODO(25342): re-enable
|
||||||
|
// eslint-disable-next-line mocha/no-skipped-tests
|
||||||
|
it.skip('should have templates feature', () => {
|
||||||
login(TEMPLATES_USER)
|
login(TEMPLATES_USER)
|
||||||
const name = `Template ${Date.now()}`
|
const name = `Template ${Date.now()}`
|
||||||
const description = `Template Description ${Date.now()}`
|
const description = `Template Description ${Date.now()}`
|
||||||
|
|
12
services/chat/.gitignore
vendored
12
services/chat/.gitignore
vendored
|
@ -1,12 +0,0 @@
|
||||||
**.swp
|
|
||||||
|
|
||||||
public/build/
|
|
||||||
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
plato/
|
|
||||||
|
|
||||||
**/*.map
|
|
||||||
|
|
||||||
# managed by dev-environment$ bin/update_build_scripts
|
|
||||||
.npmrc
|
|
|
@ -1 +1 @@
|
||||||
20.18.2
|
22.15.1
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# Instead run bin/update_build_scripts from
|
# Instead run bin/update_build_scripts from
|
||||||
# https://github.com/overleaf/internal/
|
# https://github.com/overleaf/internal/
|
||||||
|
|
||||||
FROM node:20.18.2 AS base
|
FROM node:22.15.1 AS base
|
||||||
|
|
||||||
WORKDIR /overleaf/services/chat
|
WORKDIR /overleaf/services/chat
|
||||||
|
|
||||||
|
|
|
@ -32,12 +32,12 @@ HERE=$(shell pwd)
|
||||||
MONOREPO=$(shell cd ../../ && pwd)
|
MONOREPO=$(shell cd ../../ && pwd)
|
||||||
# Run the linting commands in the scope of the monorepo.
|
# Run the linting commands in the scope of the monorepo.
|
||||||
# Eslint and prettier (plus some configs) are on the root.
|
# Eslint and prettier (plus some configs) are on the root.
|
||||||
RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:20.18.2 npm run --silent
|
RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 npm run --silent
|
||||||
|
|
||||||
RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/.eslintignore:/overleaf/.eslintignore --volume $(MONOREPO)/.eslintrc:/overleaf/.eslintrc --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc:/overleaf/.prettierrc --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent
|
RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/.eslintignore:/overleaf/.eslintignore --volume $(MONOREPO)/.eslintrc:/overleaf/.eslintrc --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc:/overleaf/.prettierrc --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent
|
||||||
|
|
||||||
# Same but from the top of the monorepo
|
# Same but from the top of the monorepo
|
||||||
RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:20.18.2 npm run --silent
|
RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent
|
||||||
|
|
||||||
SHELLCHECK_OPTS = \
|
SHELLCHECK_OPTS = \
|
||||||
--shell=bash \
|
--shell=bash \
|
||||||
|
|
|
@ -4,6 +4,6 @@ chat
|
||||||
--env-add=
|
--env-add=
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--node-version=20.18.2
|
--node-version=22.15.1
|
||||||
--public-repo=False
|
--public-repo=False
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
|
|
|
@ -39,7 +39,7 @@ services:
|
||||||
command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
|
command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
|
||||||
user: root
|
user: root
|
||||||
mongo:
|
mongo:
|
||||||
image: mongo:6.0.13
|
image: mongo:7.0.20
|
||||||
command: --replSet overleaf
|
command: --replSet overleaf
|
||||||
volumes:
|
volumes:
|
||||||
- ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
|
- ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
|
||||||
|
|
|
@ -6,7 +6,7 @@ version: "2.3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
test_unit:
|
test_unit:
|
||||||
image: node:20.18.2
|
image: node:22.15.1
|
||||||
volumes:
|
volumes:
|
||||||
- .:/overleaf/services/chat
|
- .:/overleaf/services/chat
|
||||||
- ../../node_modules:/overleaf/node_modules
|
- ../../node_modules:/overleaf/node_modules
|
||||||
|
@ -21,7 +21,7 @@ services:
|
||||||
user: node
|
user: node
|
||||||
|
|
||||||
test_acceptance:
|
test_acceptance:
|
||||||
image: node:20.18.2
|
image: node:22.15.1
|
||||||
volumes:
|
volumes:
|
||||||
- .:/overleaf/services/chat
|
- .:/overleaf/services/chat
|
||||||
- ../../node_modules:/overleaf/node_modules
|
- ../../node_modules:/overleaf/node_modules
|
||||||
|
@ -42,7 +42,7 @@ services:
|
||||||
command: npm run --silent test:acceptance
|
command: npm run --silent test:acceptance
|
||||||
|
|
||||||
mongo:
|
mongo:
|
||||||
image: mongo:6.0.13
|
image: mongo:7.0.20
|
||||||
command: --replSet overleaf
|
command: --replSet overleaf
|
||||||
volumes:
|
volumes:
|
||||||
- ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
|
- ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
|
||||||
|
|
11
services/clsi/.gitignore
vendored
11
services/clsi/.gitignore
vendored
|
@ -1,14 +1,3 @@
|
||||||
**.swp
|
|
||||||
node_modules
|
|
||||||
test/acceptance/fixtures/tmp
|
|
||||||
compiles
|
compiles
|
||||||
output
|
output
|
||||||
.DS_Store
|
|
||||||
*~
|
|
||||||
cache
|
cache
|
||||||
.vagrant
|
|
||||||
config/*
|
|
||||||
npm-debug.log
|
|
||||||
|
|
||||||
# managed by dev-environment$ bin/update_build_scripts
|
|
||||||
.npmrc
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
20.18.2
|
22.15.1
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# Instead run bin/update_build_scripts from
|
# Instead run bin/update_build_scripts from
|
||||||
# https://github.com/overleaf/internal/
|
# https://github.com/overleaf/internal/
|
||||||
|
|
||||||
FROM node:20.18.2 AS base
|
FROM node:22.15.1 AS base
|
||||||
|
|
||||||
WORKDIR /overleaf/services/clsi
|
WORKDIR /overleaf/services/clsi
|
||||||
COPY services/clsi/install_deps.sh /overleaf/services/clsi/
|
COPY services/clsi/install_deps.sh /overleaf/services/clsi/
|
||||||
|
|
|
@ -24,7 +24,6 @@ DOCKER_COMPOSE_TEST_UNIT = \
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
-docker rmi ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
|
-docker rmi ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
|
||||||
-docker rmi gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
|
|
||||||
-docker rmi us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
|
-docker rmi us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
|
||||||
-$(DOCKER_COMPOSE_TEST_UNIT) down --rmi local
|
-$(DOCKER_COMPOSE_TEST_UNIT) down --rmi local
|
||||||
-$(DOCKER_COMPOSE_TEST_ACCEPTANCE) down --rmi local
|
-$(DOCKER_COMPOSE_TEST_ACCEPTANCE) down --rmi local
|
||||||
|
@ -33,12 +32,12 @@ HERE=$(shell pwd)
|
||||||
MONOREPO=$(shell cd ../../ && pwd)
|
MONOREPO=$(shell cd ../../ && pwd)
|
||||||
# Run the linting commands in the scope of the monorepo.
|
# Run the linting commands in the scope of the monorepo.
|
||||||
# Eslint and prettier (plus some configs) are on the root.
|
# Eslint and prettier (plus some configs) are on the root.
|
||||||
RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:20.18.2 npm run --silent
|
RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 npm run --silent
|
||||||
|
|
||||||
RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/.eslintignore:/overleaf/.eslintignore --volume $(MONOREPO)/.eslintrc:/overleaf/.eslintrc --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc:/overleaf/.prettierrc --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent
|
RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/.eslintignore:/overleaf/.eslintignore --volume $(MONOREPO)/.eslintrc:/overleaf/.eslintrc --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc:/overleaf/.prettierrc --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent
|
||||||
|
|
||||||
# Same but from the top of the monorepo
|
# Same but from the top of the monorepo
|
||||||
RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:20.18.2 npm run --silent
|
RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent
|
||||||
|
|
||||||
SHELLCHECK_OPTS = \
|
SHELLCHECK_OPTS = \
|
||||||
--shell=bash \
|
--shell=bash \
|
||||||
|
@ -129,11 +128,10 @@ build:
|
||||||
--pull \
|
--pull \
|
||||||
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
||||||
--tag ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \
|
--tag ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \
|
||||||
--tag gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \
|
|
||||||
--tag gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME) \
|
|
||||||
--cache-from gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME) \
|
|
||||||
--cache-from gcr.io/overleaf-ops/$(PROJECT_NAME):main \
|
|
||||||
--tag us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \
|
--tag us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \
|
||||||
|
--tag us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME):$(BRANCH_NAME) \
|
||||||
|
--cache-from us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME):$(BRANCH_NAME) \
|
||||||
|
--cache-from us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME):main \
|
||||||
--file Dockerfile \
|
--file Dockerfile \
|
||||||
../..
|
../..
|
||||||
|
|
||||||
|
|
|
@ -249,6 +249,9 @@ app.get('/health_check', function (req, res) {
|
||||||
if (Settings.processTooOld) {
|
if (Settings.processTooOld) {
|
||||||
return res.status(500).json({ processTooOld: true })
|
return res.status(500).json({ processTooOld: true })
|
||||||
}
|
}
|
||||||
|
if (ProjectPersistenceManager.isAnyDiskCriticalLow()) {
|
||||||
|
return res.status(500).json({ diskCritical: true })
|
||||||
|
}
|
||||||
smokeTest.sendLastResult(res)
|
smokeTest.sendLastResult(res)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -296,9 +299,14 @@ const loadTcpServer = net.createServer(function (socket) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const freeLoad = availableWorkingCpus - currentLoad
|
const freeLoad = availableWorkingCpus - currentLoad
|
||||||
const freeLoadPercentage = Math.round(
|
let freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100)
|
||||||
(freeLoad / availableWorkingCpus) * 100
|
if (ProjectPersistenceManager.isAnyDiskCriticalLow()) {
|
||||||
)
|
freeLoadPercentage = 0
|
||||||
|
}
|
||||||
|
if (ProjectPersistenceManager.isAnyDiskLow()) {
|
||||||
|
freeLoadPercentage = freeLoadPercentage / 2
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
Settings.internal.load_balancer_agent.allow_maintenance &&
|
Settings.internal.load_balancer_agent.allow_maintenance &&
|
||||||
freeLoadPercentage <= 0
|
freeLoadPercentage <= 0
|
||||||
|
|
|
@ -20,6 +20,19 @@ const TIMING_BUCKETS = [
|
||||||
0, 10, 100, 1000, 2000, 5000, 10000, 15000, 20000, 30000,
|
0, 10, 100, 1000, 2000, 5000, 10000, 15000, 20000, 30000,
|
||||||
]
|
]
|
||||||
const MAX_ENTRIES_IN_OUTPUT_TAR = 100
|
const MAX_ENTRIES_IN_OUTPUT_TAR = 100
|
||||||
|
const OBJECT_ID_REGEX = /^[0-9a-f]{24}$/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} projectId
|
||||||
|
* @return {{shard: string, url: string}}
|
||||||
|
*/
|
||||||
|
function getShard(projectId) {
|
||||||
|
// [timestamp 4bytes][random per machine 5bytes][counter 3bytes]
|
||||||
|
// [32bit 4bytes]
|
||||||
|
const last4Bytes = Buffer.from(projectId, 'hex').subarray(8, 12)
|
||||||
|
const idx = last4Bytes.readUInt32BE() % Settings.apis.clsiCache.shards.length
|
||||||
|
return Settings.apis.clsiCache.shards[idx]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} projectId
|
* @param {string} projectId
|
||||||
|
@ -29,6 +42,7 @@ const MAX_ENTRIES_IN_OUTPUT_TAR = 100
|
||||||
* @param {[{path: string}]} outputFiles
|
* @param {[{path: string}]} outputFiles
|
||||||
* @param {string} compileGroup
|
* @param {string} compileGroup
|
||||||
* @param {Record<string, any>} options
|
* @param {Record<string, any>} options
|
||||||
|
* @return {string | undefined}
|
||||||
*/
|
*/
|
||||||
function notifyCLSICacheAboutBuild({
|
function notifyCLSICacheAboutBuild({
|
||||||
projectId,
|
projectId,
|
||||||
|
@ -39,14 +53,16 @@ function notifyCLSICacheAboutBuild({
|
||||||
compileGroup,
|
compileGroup,
|
||||||
options,
|
options,
|
||||||
}) {
|
}) {
|
||||||
if (!Settings.apis.clsiCache.enabled) return
|
if (!Settings.apis.clsiCache.enabled) return undefined
|
||||||
|
if (!OBJECT_ID_REGEX.test(projectId)) return undefined
|
||||||
|
const { url, shard } = getShard(projectId)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {[{path: string}]} files
|
* @param {[{path: string}]} files
|
||||||
*/
|
*/
|
||||||
const enqueue = files => {
|
const enqueue = files => {
|
||||||
Metrics.count('clsi_cache_enqueue_files', files.length)
|
Metrics.count('clsi_cache_enqueue_files', files.length)
|
||||||
fetchNothing(`${Settings.apis.clsiCache.url}/enqueue`, {
|
fetchNothing(`${url}/enqueue`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
json: {
|
json: {
|
||||||
projectId,
|
projectId,
|
||||||
|
@ -97,6 +113,8 @@ function notifyCLSICacheAboutBuild({
|
||||||
'build output.tar.gz for clsi cache failed'
|
'build output.tar.gz for clsi cache failed'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return shard
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -155,6 +173,7 @@ async function downloadOutputDotSynctexFromCompileCache(
|
||||||
outputDir
|
outputDir
|
||||||
) {
|
) {
|
||||||
if (!Settings.apis.clsiCache.enabled) return false
|
if (!Settings.apis.clsiCache.enabled) return false
|
||||||
|
if (!OBJECT_ID_REGEX.test(projectId)) return false
|
||||||
|
|
||||||
const timer = new Metrics.Timer(
|
const timer = new Metrics.Timer(
|
||||||
'clsi_cache_download',
|
'clsi_cache_download',
|
||||||
|
@ -165,7 +184,7 @@ async function downloadOutputDotSynctexFromCompileCache(
|
||||||
let stream
|
let stream
|
||||||
try {
|
try {
|
||||||
stream = await fetchStream(
|
stream = await fetchStream(
|
||||||
`${Settings.apis.clsiCache.url}/project/${projectId}/${
|
`${getShard(projectId).url}/project/${projectId}/${
|
||||||
userId ? `user/${userId}/` : ''
|
userId ? `user/${userId}/` : ''
|
||||||
}build/${editorId}-${buildId}/search/output/output.synctex.gz`,
|
}build/${editorId}-${buildId}/search/output/output.synctex.gz`,
|
||||||
{
|
{
|
||||||
|
@ -205,8 +224,9 @@ async function downloadOutputDotSynctexFromCompileCache(
|
||||||
*/
|
*/
|
||||||
async function downloadLatestCompileCache(projectId, userId, compileDir) {
|
async function downloadLatestCompileCache(projectId, userId, compileDir) {
|
||||||
if (!Settings.apis.clsiCache.enabled) return false
|
if (!Settings.apis.clsiCache.enabled) return false
|
||||||
|
if (!OBJECT_ID_REGEX.test(projectId)) return false
|
||||||
|
|
||||||
const url = `${Settings.apis.clsiCache.url}/project/${projectId}/${
|
const url = `${getShard(projectId).url}/project/${projectId}/${
|
||||||
userId ? `user/${userId}/` : ''
|
userId ? `user/${userId}/` : ''
|
||||||
}latest/output/output.tar.gz`
|
}latest/output/output.tar.gz`
|
||||||
const timer = new Metrics.Timer(
|
const timer = new Metrics.Timer(
|
||||||
|
|
|
@ -112,12 +112,13 @@ function compile(req, res, next) {
|
||||||
buildId = error.buildId
|
buildId = error.buildId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let clsiCacheShard
|
||||||
if (
|
if (
|
||||||
status === 'success' &&
|
status === 'success' &&
|
||||||
request.editorId &&
|
request.editorId &&
|
||||||
request.populateClsiCache
|
request.populateClsiCache
|
||||||
) {
|
) {
|
||||||
notifyCLSICacheAboutBuild({
|
clsiCacheShard = notifyCLSICacheAboutBuild({
|
||||||
projectId: request.project_id,
|
projectId: request.project_id,
|
||||||
userId: request.user_id,
|
userId: request.user_id,
|
||||||
buildId: outputFiles[0].build,
|
buildId: outputFiles[0].build,
|
||||||
|
@ -144,6 +145,7 @@ function compile(req, res, next) {
|
||||||
stats,
|
stats,
|
||||||
timings,
|
timings,
|
||||||
buildId,
|
buildId,
|
||||||
|
clsiCacheShard,
|
||||||
outputUrlPrefix: Settings.apis.clsi.outputUrlPrefix,
|
outputUrlPrefix: Settings.apis.clsi.outputUrlPrefix,
|
||||||
outputFiles: outputFiles.map(file => ({
|
outputFiles: outputFiles.map(file => ({
|
||||||
url:
|
url:
|
||||||
|
@ -188,7 +190,8 @@ function clearCache(req, res, next) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncFromCode(req, res, next) {
|
function syncFromCode(req, res, next) {
|
||||||
const { file, editorId, buildId, compileFromClsiCache } = req.query
|
const { file, editorId, buildId } = req.query
|
||||||
|
const compileFromClsiCache = req.query.compileFromClsiCache === 'true'
|
||||||
const line = parseInt(req.query.line, 10)
|
const line = parseInt(req.query.line, 10)
|
||||||
const column = parseInt(req.query.column, 10)
|
const column = parseInt(req.query.column, 10)
|
||||||
const { imageName } = req.query
|
const { imageName } = req.query
|
||||||
|
@ -201,12 +204,13 @@ function syncFromCode(req, res, next) {
|
||||||
line,
|
line,
|
||||||
column,
|
column,
|
||||||
{ imageName, editorId, buildId, compileFromClsiCache },
|
{ imageName, editorId, buildId, compileFromClsiCache },
|
||||||
function (error, pdfPositions) {
|
function (error, pdfPositions, downloadedFromCache) {
|
||||||
if (error) {
|
if (error) {
|
||||||
return next(error)
|
return next(error)
|
||||||
}
|
}
|
||||||
res.json({
|
res.json({
|
||||||
pdf: pdfPositions,
|
pdf: pdfPositions,
|
||||||
|
downloadedFromCache,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -216,7 +220,8 @@ function syncFromPdf(req, res, next) {
|
||||||
const page = parseInt(req.query.page, 10)
|
const page = parseInt(req.query.page, 10)
|
||||||
const h = parseFloat(req.query.h)
|
const h = parseFloat(req.query.h)
|
||||||
const v = parseFloat(req.query.v)
|
const v = parseFloat(req.query.v)
|
||||||
const { imageName, editorId, buildId, compileFromClsiCache } = req.query
|
const { imageName, editorId, buildId } = req.query
|
||||||
|
const compileFromClsiCache = req.query.compileFromClsiCache === 'true'
|
||||||
const projectId = req.params.project_id
|
const projectId = req.params.project_id
|
||||||
const userId = req.params.user_id
|
const userId = req.params.user_id
|
||||||
CompileManager.syncFromPdf(
|
CompileManager.syncFromPdf(
|
||||||
|
@ -226,12 +231,13 @@ function syncFromPdf(req, res, next) {
|
||||||
h,
|
h,
|
||||||
v,
|
v,
|
||||||
{ imageName, editorId, buildId, compileFromClsiCache },
|
{ imageName, editorId, buildId, compileFromClsiCache },
|
||||||
function (error, codePositions) {
|
function (error, codePositions, downloadedFromCache) {
|
||||||
if (error) {
|
if (error) {
|
||||||
return next(error)
|
return next(error)
|
||||||
}
|
}
|
||||||
res.json({
|
res.json({
|
||||||
code: codePositions,
|
code: codePositions,
|
||||||
|
downloadedFromCache,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -23,6 +23,7 @@ const {
|
||||||
downloadLatestCompileCache,
|
downloadLatestCompileCache,
|
||||||
downloadOutputDotSynctexFromCompileCache,
|
downloadOutputDotSynctexFromCompileCache,
|
||||||
} = require('./CLSICacheHandler')
|
} = require('./CLSICacheHandler')
|
||||||
|
const { callbackifyMultiResult } = require('@overleaf/promise-utils')
|
||||||
|
|
||||||
const COMPILE_TIME_BUCKETS = [
|
const COMPILE_TIME_BUCKETS = [
|
||||||
// NOTE: These buckets are locked in per metric name.
|
// NOTE: These buckets are locked in per metric name.
|
||||||
|
@ -447,12 +448,20 @@ async function syncFromCode(projectId, userId, filename, line, column, opts) {
|
||||||
'-o',
|
'-o',
|
||||||
outputFilePath,
|
outputFilePath,
|
||||||
]
|
]
|
||||||
const stdout = await _runSynctex(projectId, userId, command, opts)
|
const { stdout, downloadedFromCache } = await _runSynctex(
|
||||||
|
projectId,
|
||||||
|
userId,
|
||||||
|
command,
|
||||||
|
opts
|
||||||
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
{ projectId, userId, filename, line, column, command, stdout },
|
{ projectId, userId, filename, line, column, command, stdout },
|
||||||
'synctex code output'
|
'synctex code output'
|
||||||
)
|
)
|
||||||
return SynctexOutputParser.parseViewOutput(stdout)
|
return {
|
||||||
|
codePositions: SynctexOutputParser.parseViewOutput(stdout),
|
||||||
|
downloadedFromCache,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function syncFromPdf(projectId, userId, page, h, v, opts) {
|
async function syncFromPdf(projectId, userId, page, h, v, opts) {
|
||||||
|
@ -465,9 +474,17 @@ async function syncFromPdf(projectId, userId, page, h, v, opts) {
|
||||||
'-o',
|
'-o',
|
||||||
`${page}:${h}:${v}:${outputFilePath}`,
|
`${page}:${h}:${v}:${outputFilePath}`,
|
||||||
]
|
]
|
||||||
const stdout = await _runSynctex(projectId, userId, command, opts)
|
const { stdout, downloadedFromCache } = await _runSynctex(
|
||||||
|
projectId,
|
||||||
|
userId,
|
||||||
|
command,
|
||||||
|
opts
|
||||||
|
)
|
||||||
logger.debug({ projectId, userId, page, h, v, stdout }, 'synctex pdf output')
|
logger.debug({ projectId, userId, page, h, v, stdout }, 'synctex pdf output')
|
||||||
return SynctexOutputParser.parseEditOutput(stdout, baseDir)
|
return {
|
||||||
|
pdfPositions: SynctexOutputParser.parseEditOutput(stdout, baseDir),
|
||||||
|
downloadedFromCache,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _checkFileExists(dir, filename) {
|
async function _checkFileExists(dir, filename) {
|
||||||
|
@ -522,9 +539,10 @@ async function _runSynctex(projectId, userId, command, opts) {
|
||||||
return await OutputCacheManager.promises.queueDirOperation(
|
return await OutputCacheManager.promises.queueDirOperation(
|
||||||
outputDir,
|
outputDir,
|
||||||
/**
|
/**
|
||||||
* @return {Promise<string>}
|
* @return {Promise<{stdout: string, downloadedFromCache: boolean}>}
|
||||||
*/
|
*/
|
||||||
async () => {
|
async () => {
|
||||||
|
let downloadedFromCache = false
|
||||||
try {
|
try {
|
||||||
await _checkFileExists(directory, 'output.synctex.gz')
|
await _checkFileExists(directory, 'output.synctex.gz')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -535,13 +553,14 @@ async function _runSynctex(projectId, userId, command, opts) {
|
||||||
buildId
|
buildId
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
await downloadOutputDotSynctexFromCompileCache(
|
downloadedFromCache =
|
||||||
projectId,
|
await downloadOutputDotSynctexFromCompileCache(
|
||||||
userId,
|
projectId,
|
||||||
editorId,
|
userId,
|
||||||
buildId,
|
editorId,
|
||||||
directory
|
buildId,
|
||||||
)
|
directory
|
||||||
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
{ err, projectId, userId, editorId, buildId },
|
{ err, projectId, userId, editorId, buildId },
|
||||||
|
@ -554,7 +573,7 @@ async function _runSynctex(projectId, userId, command, opts) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const output = await CommandRunner.promises.run(
|
const { stdout } = await CommandRunner.promises.run(
|
||||||
compileName,
|
compileName,
|
||||||
command,
|
command,
|
||||||
directory,
|
directory,
|
||||||
|
@ -563,7 +582,10 @@ async function _runSynctex(projectId, userId, command, opts) {
|
||||||
{},
|
{},
|
||||||
compileGroup
|
compileGroup
|
||||||
)
|
)
|
||||||
return output.stdout
|
return {
|
||||||
|
stdout,
|
||||||
|
downloadedFromCache,
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw OError.tag(error, 'error running synctex', {
|
throw OError.tag(error, 'error running synctex', {
|
||||||
command,
|
command,
|
||||||
|
@ -686,8 +708,14 @@ module.exports = {
|
||||||
stopCompile: callbackify(stopCompile),
|
stopCompile: callbackify(stopCompile),
|
||||||
clearProject: callbackify(clearProject),
|
clearProject: callbackify(clearProject),
|
||||||
clearExpiredProjects: callbackify(clearExpiredProjects),
|
clearExpiredProjects: callbackify(clearExpiredProjects),
|
||||||
syncFromCode: callbackify(syncFromCode),
|
syncFromCode: callbackifyMultiResult(syncFromCode, [
|
||||||
syncFromPdf: callbackify(syncFromPdf),
|
'codePositions',
|
||||||
|
'downloadedFromCache',
|
||||||
|
]),
|
||||||
|
syncFromPdf: callbackifyMultiResult(syncFromPdf, [
|
||||||
|
'pdfPositions',
|
||||||
|
'downloadedFromCache',
|
||||||
|
]),
|
||||||
wordcount: callbackify(wordcount),
|
wordcount: callbackify(wordcount),
|
||||||
promises: {
|
promises: {
|
||||||
doCompileWithLock,
|
doCompileWithLock,
|
||||||
|
|
|
@ -22,6 +22,9 @@ const fs = require('node:fs')
|
||||||
// projectId -> timestamp mapping.
|
// projectId -> timestamp mapping.
|
||||||
const LAST_ACCESS = new Map()
|
const LAST_ACCESS = new Map()
|
||||||
|
|
||||||
|
let ANY_DISK_LOW = false
|
||||||
|
let ANY_DISK_CRITICAL_LOW = false
|
||||||
|
|
||||||
async function collectDiskStats() {
|
async function collectDiskStats() {
|
||||||
const paths = [
|
const paths = [
|
||||||
Settings.path.compilesDir,
|
Settings.path.compilesDir,
|
||||||
|
@ -30,6 +33,8 @@ async function collectDiskStats() {
|
||||||
]
|
]
|
||||||
|
|
||||||
const diskStats = {}
|
const diskStats = {}
|
||||||
|
let anyDiskLow = false
|
||||||
|
let anyDiskCriticalLow = false
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
try {
|
try {
|
||||||
const { blocks, bavail, bsize } = await fs.promises.statfs(path)
|
const { blocks, bavail, bsize } = await fs.promises.statfs(path)
|
||||||
|
@ -45,10 +50,16 @@ async function collectDiskStats() {
|
||||||
})
|
})
|
||||||
const lowDisk = diskAvailablePercent < 10
|
const lowDisk = diskAvailablePercent < 10
|
||||||
diskStats[path] = { stats, lowDisk }
|
diskStats[path] = { stats, lowDisk }
|
||||||
|
|
||||||
|
const criticalLowDisk = diskAvailablePercent < 3
|
||||||
|
anyDiskLow = anyDiskLow || lowDisk
|
||||||
|
anyDiskCriticalLow = anyDiskCriticalLow || criticalLowDisk
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.err({ err, path }, 'error getting disk usage')
|
logger.err({ err, path }, 'error getting disk usage')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ANY_DISK_LOW = anyDiskLow
|
||||||
|
ANY_DISK_CRITICAL_LOW = anyDiskCriticalLow
|
||||||
return diskStats
|
return diskStats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,11 +81,22 @@ async function refreshExpiryTimeout() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Metrics.gauge(
|
||||||
|
'project_persistence_expiry_timeout',
|
||||||
|
ProjectPersistenceManager.EXPIRY_TIMEOUT
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ProjectPersistenceManager = {
|
module.exports = ProjectPersistenceManager = {
|
||||||
EXPIRY_TIMEOUT: Settings.project_cache_length_ms || oneDay * 2.5,
|
EXPIRY_TIMEOUT: Settings.project_cache_length_ms || oneDay * 2.5,
|
||||||
|
|
||||||
|
isAnyDiskLow() {
|
||||||
|
return ANY_DISK_LOW
|
||||||
|
},
|
||||||
|
isAnyDiskCriticalLow() {
|
||||||
|
return ANY_DISK_CRITICAL_LOW
|
||||||
|
},
|
||||||
|
|
||||||
promises: {
|
promises: {
|
||||||
refreshExpiryTimeout,
|
refreshExpiryTimeout,
|
||||||
},
|
},
|
||||||
|
@ -125,12 +147,12 @@ module.exports = ProjectPersistenceManager = {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Collect disk stats frequently to have them ready the next time /metrics is scraped (60s +- jitter).
|
// Collect disk stats frequently to have them ready the next time /metrics is scraped (60s +- jitter) or every 5th scrape of the load agent (3s +- jitter).
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
collectDiskStats().catch(err => {
|
collectDiskStats().catch(err => {
|
||||||
logger.err({ err }, 'low level error collecting disk stats')
|
logger.err({ err }, 'low level error collecting disk stats')
|
||||||
})
|
})
|
||||||
}, 50_000)
|
}, 15_000)
|
||||||
},
|
},
|
||||||
|
|
||||||
markProjectAsJustAccessed(projectId, callback) {
|
markProjectAsJustAccessed(projectId, callback) {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
clsi
|
clsi
|
||||||
--data-dirs=cache,compiles,output
|
--data-dirs=cache,compiles,output
|
||||||
--dependencies=
|
--dependencies=
|
||||||
--docker-repos=gcr.io/overleaf-ops,us-east1-docker.pkg.dev/overleaf-ops/ol-docker
|
--docker-repos=us-east1-docker.pkg.dev/overleaf-ops/ol-docker
|
||||||
--env-add=ENABLE_PDF_CACHING="true",PDF_CACHING_ENABLE_WORKER_POOL="true",ALLOWED_IMAGES=quay.io/sharelatex/texlive-full:2017.1,TEXLIVE_IMAGE=quay.io/sharelatex/texlive-full:2017.1,TEX_LIVE_IMAGE_NAME_OVERRIDE=gcr.io/overleaf-ops,TEXLIVE_IMAGE_USER="tex",DOCKER_RUNNER="true",COMPILES_HOST_DIR=$PWD/compiles,OUTPUT_HOST_DIR=$PWD/output
|
--env-add=ENABLE_PDF_CACHING="true",PDF_CACHING_ENABLE_WORKER_POOL="true",ALLOWED_IMAGES=quay.io/sharelatex/texlive-full:2017.1,TEXLIVE_IMAGE=quay.io/sharelatex/texlive-full:2017.1,TEX_LIVE_IMAGE_NAME_OVERRIDE=us-east1-docker.pkg.dev/overleaf-ops/ol-docker,TEXLIVE_IMAGE_USER="tex",DOCKER_RUNNER="true",COMPILES_HOST_DIR=$PWD/compiles,OUTPUT_HOST_DIR=$PWD/output
|
||||||
--env-pass-through=
|
--env-pass-through=
|
||||||
--esmock-loader=False
|
--esmock-loader=False
|
||||||
--node-version=20.18.2
|
--node-version=22.15.1
|
||||||
--public-repo=True
|
--public-repo=True
|
||||||
--script-version=4.7.0
|
--script-version=4.7.0
|
||||||
--use-large-ci-runner=True
|
--use-large-ci-runner=True
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue