16 Commits

Author SHA1 Message Date
Jimmy Domagala-Tang
5b8f6f83be Assume users are in enterprise commons if v1 affiliates lookup fails (#22728)
* updating affiliation check for enterprise commons to default to restricting wf on error

* feat: simplify logic on affiliate lookup failure

GitOrigin-RevId: ccd676af9a44413bf63fe7c1f7836080c2612f45
2025-11-14 09:05:58 +00:00
Liangjun Song
5730fb993a Merge pull request #29459 from overleaf/ls-support-stripe-subscription-when-accepting-group-invite
Support Stripe subscription when accepting group invite from managed group

GitOrigin-RevId: 8b374bd35926a3c074e20bbe45bd6625cc6ba99f
2025-11-14 09:05:50 +00:00
Eric Mc Sween
a67d70c74a Merge pull request #29609 from overleaf/renovate-npm-nanoid-vulnerability
Update dependency nanoid to v5 from ^4.0.2 [SECURITY]

GitOrigin-RevId: 9d87781d2169fb1c9003d3c42b4531bb9e1c377e
2025-11-14 09:05:45 +00:00
Tim Down
c0c3bfe185 DS button CSS and component implementation plus move most --ciam variables to --ds
GitOrigin-RevId: 5dd6b490a6f597892b47a89aabce748cea3e3bc6
2025-11-14 09:05:34 +00:00
Andrew Rumble
ccf1fb8fcb Merge pull request #29662 from overleaf/ar-allow-esm-tests-to-be-grepped
[web] Split vitest tests and spread args

GitOrigin-RevId: d0e06836fc4f4b9de50def456aef7f0ecb6cb128
2025-11-14 09:05:29 +00:00
Thomas
c059a3c5b0 Handle slashes in branch names for docker build tags (#29529)
* Refactor v1 Makefile to use DOCKER_COMPOSE variable for Docker commands with tag safe branch name

* Handle slashes in branch names for docker build tags

GitOrigin-RevId: 463940e8435845978aced745575905f3bfbb8e1c
2025-11-14 09:05:25 +00:00
Miguel Serrano
7d0e75e4f7 [web] Managed Group Audit Logs for project management (#29584)
GitOrigin-RevId: 8edf4e580c001db3129c276d23e90ce9e82ac2ea
2025-11-13 09:06:55 +00:00
Eric Mc Sween
44b79c895f Merge pull request #29655 from overleaf/em-record-replay-github-tests
Do not make real calls to GitHub during acceptance tests

GitOrigin-RevId: 83efaaa2a1abe960ecebf5e09288a4bcadf013e1
2025-11-13 09:06:46 +00:00
Andrew Rumble
ae6dec9dcb Merge pull request #29656 from overleaf/revert-29521-ar-models-es-conversion
Revert "[web] Convert models and self-referential test files to ESM "

GitOrigin-RevId: 5455cccbb513bd9ca36ce526ff1553065f83d233
2025-11-13 09:06:36 +00:00
Brian Gough
e7cc70baf7 Merge pull request #29639 from overleaf/bg-block-clone3-for-docker
use docker default seccomp rule for clone3

GitOrigin-RevId: 32a65a2f2262225fafa1ac1a9f8d6f2767c2829c
2025-11-13 09:06:28 +00:00
Brian Gough
67aa42a57a Merge pull request #29650 from overleaf/bg-update-clsi-tests-to-2025
update clsi acceptance tests to use texlive 2025.1 by default

GitOrigin-RevId: d69e97132e87873a8b91c39494c545250298d935
2025-11-13 09:06:23 +00:00
Brian Gough
4ca1407ab9 Merge pull request #29638 from overleaf/bg-add-shell-escape-tests-to-clsi
add shell escape tests to clsi

GitOrigin-RevId: 6cd3ab24fa76f74dccfec43bf6a3d06c0fe9ec6a
2025-11-13 09:06:18 +00:00
Andrew Rumble
7c9fea64ac [web] Convert models and self-referential test files to ESM (#29521)
from overleaf/ar-models-es-conversion

GitOrigin-RevId: a92ab8342c0f3e23155eacc0570458fc910c3d71
2025-11-13 09:06:13 +00:00
Malik Glossop
59aab0878d Merge pull request #29635 from overleaf/mg-project-actions-bp
Update projection action breakpoint for dropdown from md to lg

GitOrigin-RevId: 749f0abbdae6686959773ea6cfca22b488064451
2025-11-13 09:06:09 +00:00
David
c4b3cd2a77 Merge pull request #29511 from overleaf/dp-new-users-to-new-editor
Move all new users to use the new editor

GitOrigin-RevId: e3611e5853da4b96db9f4cc37114ededb8632aed
2025-11-13 09:06:00 +00:00
Miguel Serrano
1479df36db [web] Managed Group Audit Logs for github/dropbox (#29582)
GitOrigin-RevId: 9c2db408734469f67eaca7f4b7f0ebc6babcfcd4
2025-11-13 09:05:55 +00:00
70 changed files with 1143 additions and 1105 deletions

View File

@@ -22,7 +22,7 @@
"effect": "^3.19.0",
"lucide-react": "^0.548.0",
"motion": "^12.23.24",
"nanoid": "^4.0.2",
"nanoid": "^5.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"streamdown": "^1.4.0",

73
package-lock.json generated
View File

@@ -128,7 +128,7 @@
"effect": "^3.19.0",
"lucide-react": "^0.548.0",
"motion": "^12.23.24",
"nanoid": "^4.0.2",
"nanoid": "^5.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"streamdown": "^1.4.0",
@@ -15663,6 +15663,7 @@
"resolved": "https://registry.npmjs.org/@pollyjs/adapter-node-http/-/adapter-node-http-6.0.6.tgz",
"integrity": "sha512-jdJG7oncmSHZAtVMmRgOxh5A56b7G8H9ULlk/ZaVJ+jNrlFXhLmPpx8OQoSF4Cuq2ugdiWmwmAjFXHStcpY3Mw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@pollyjs/adapter": "^6.0.6",
"@pollyjs/utils": "^6.0.6",
@@ -15675,6 +15676,7 @@
"resolved": "https://registry.npmjs.org/@pollyjs/core/-/core-6.0.6.tgz",
"integrity": "sha512-1ZZcmojW8iSFmvHGeLlvuudM3WiDV842FsVvtPAo3HoAYE6jCNveLHJ+X4qvonL4enj1SyTF3hXA107UkQFQrA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@pollyjs/utils": "^6.0.6",
"@sindresorhus/fnv1a": "^2.0.1",
@@ -15732,6 +15734,7 @@
"resolved": "https://registry.npmjs.org/@pollyjs/persister-fs/-/persister-fs-6.0.6.tgz",
"integrity": "sha512-/ALVgZiH2zGqwLkW0Mntc0Oq1v7tR8LS8JD2SAyIsHpnSXeBUnfPWwjAuYw0vqORHFVEbwned6MBRFfvU/3qng==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@pollyjs/node-server": "^6.0.6",
"@pollyjs/persister": "^6.0.6"
@@ -23229,6 +23232,25 @@
"preact": "^10.5.13"
}
},
"node_modules/@uppy/core/node_modules/nanoid": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz",
"integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^14 || ^16 || >=18"
}
},
"node_modules/@uppy/informer": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@uppy/informer/-/informer-3.1.0.tgz",
@@ -23267,6 +23289,25 @@
"dev": true,
"license": "MIT"
},
"node_modules/@uppy/provider-views/node_modules/nanoid": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz",
"integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^14 || ^16 || >=18"
}
},
"node_modules/@uppy/provider-views/node_modules/p-queue": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-7.4.1.tgz",
@@ -42395,9 +42436,9 @@
"dev": true
},
"node_modules/nanoid": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz",
"integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==",
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz",
"integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==",
"funding": [
{
"type": "github",
@@ -42409,7 +42450,7 @@
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^14 || ^16 || >=18"
"node": "^18 || >=20"
}
},
"node_modules/nanomatch": {
@@ -57090,6 +57131,9 @@
"devDependencies": {
"@overleaf/fetch-utils": "*",
"@overleaf/migrations": "*",
"@pollyjs/adapter-node-http": "^6.0.6",
"@pollyjs/core": "^6.0.6",
"@pollyjs/persister-fs": "^6.0.6",
"chai": "^4.3.6",
"chai-as-promised": "^7.1.1",
"mocha": "^11.1.0",
@@ -59543,6 +59587,25 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"services/web/node_modules/nanoid": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz",
"integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^14 || ^16 || >=18"
}
},
"services/web/node_modules/nise": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz",

View File

@@ -4,13 +4,14 @@
BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g')
PROJECT_NAME = chat
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_REPO ?= us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/package.json \
$(MONOREPO)/package-lock.json \
@@ -28,7 +29,7 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \
BRANCH_NAME=$(BRANCH_NAME) \
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}

View File

@@ -4,13 +4,14 @@
BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g')
PROJECT_NAME = clsi
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_REPO ?= us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/package.json \
$(MONOREPO)/package-lock.json \
@@ -27,7 +28,7 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \
BRANCH_NAME=$(BRANCH_NAME) \
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}

View File

@@ -1,13 +1,12 @@
overleaf/clsi
===============
# overleaf/clsi
A web api for compiling LaTeX documents in the cloud
The Common LaTeX Service Interface (CLSI) provides a RESTful interface to traditional LaTeX tools (or, more generally, any command line tool for composing marked-up documents into a display format such as PDF or HTML). The CLSI listens on the following ports by default:
* TCP/3013 - the RESTful interface
* TCP/3048 - reports load information
* TCP/3049 - HTTP interface to control the CLSI service
- TCP/3013 - the RESTful interface
- TCP/3048 - reports load information
- TCP/3049 - HTTP interface to control the CLSI service
These defaults can be modified in `config/settings.defaults.js`.
@@ -15,29 +14,28 @@ The provided `Dockerfile` builds a Docker image which has the Docker command lin
The CLSI can be configured through the following environment variables:
* `ALLOWED_COMPILE_GROUPS` - Space separated list of allowed compile groups
* `ALLOWED_IMAGES` - Space separated list of allowed Docker TeX Live images
* `CATCH_ERRORS` - Set to `true` to log uncaught exceptions
* `COMPILE_GROUP_DOCKER_CONFIGS` - JSON string of Docker configs for compile groups
* `SANDBOXED_COMPILES` - Set to true to use sibling containers
* `SANDBOXED_COMPILES_HOST_DIR_COMPILES` - Working directory for LaTeX compiles
* `SANDBOXED_COMPILES_HOST_DIR_OUTPUT` - Output directory for LaTeX compiles
* `COMPILE_SIZE_LIMIT` - Sets the body-parser [limit](https://github.com/expressjs/body-parser#limit)
* `DOCKER_RUNTIME` -
* `FILESTORE_DOMAIN_OVERRIDE` - The url for the filestore service e.g.`http://$FILESTORE_HOST:3009`
* `FILESTORE_PARALLEL_FILE_DOWNLOADS` - Number of parallel file downloads
* `LISTEN_ADDRESS` - The address for the RESTful service to listen on. Set to `0.0.0.0` to listen on all network interfaces
* `PROCESS_LIFE_SPAN_LIMIT_MS` - Process life span limit in milliseconds
* `SMOKE_TEST` - Whether to run smoke tests
* `TEXLIVE_IMAGE` - The TeX Live Docker image to use for sibling containers, e.g. `us-east1-docker.pkg.dev/overleaf-ops/ol-docker/texlive-full:2017.1`
* `TEX_LIVE_IMAGE_NAME_OVERRIDE` - The name of the registry for the Docker image e.g. `us-east1-docker.pkg.dev/overleaf-ops/ol-docker`
* `TEXLIVE_IMAGE_USER` - When using sibling containers, the user to run as in the TeX Live image. Defaults to `tex`
* `TEXLIVE_OPENOUT_ANY` - Sets the `openout_any` environment variable for TeX Live (see the `\openout` primitive [documentation](http://tug.org/texinfohtml/web2c.html#tex-invocation))
- `ALLOWED_COMPILE_GROUPS` - Space separated list of allowed compile groups
- `ALLOWED_IMAGES` - Space separated list of allowed Docker TeX Live images
- `CATCH_ERRORS` - Set to `true` to log uncaught exceptions
- `COMPILE_GROUP_DOCKER_CONFIGS` - JSON string of Docker configs for compile groups
- `SANDBOXED_COMPILES` - Set to true to use sibling containers
- `SANDBOXED_COMPILES_HOST_DIR_COMPILES` - Working directory for LaTeX compiles
- `SANDBOXED_COMPILES_HOST_DIR_OUTPUT` - Output directory for LaTeX compiles
- `COMPILE_SIZE_LIMIT` - Sets the body-parser [limit](https://github.com/expressjs/body-parser#limit)
- `DOCKER_RUNTIME` -
- `FILESTORE_DOMAIN_OVERRIDE` - The url for the filestore service e.g.`http://$FILESTORE_HOST:3009`
- `FILESTORE_PARALLEL_FILE_DOWNLOADS` - Number of parallel file downloads
- `LISTEN_ADDRESS` - The address for the RESTful service to listen on. Set to `0.0.0.0` to listen on all network interfaces
- `PROCESS_LIFE_SPAN_LIMIT_MS` - Process life span limit in milliseconds
- `SMOKE_TEST` - Whether to run smoke tests
- `TEXLIVE_IMAGE` - The TeX Live Docker image to use for sibling containers, e.g. `us-east1-docker.pkg.dev/overleaf-ops/ol-docker/texlive-full:2025.1`
- `TEX_LIVE_IMAGE_NAME_OVERRIDE` - The name of the registry for the Docker image e.g. `us-east1-docker.pkg.dev/overleaf-ops/ol-docker`
- `TEXLIVE_IMAGE_USER` - When using sibling containers, the user to run as in the TeX Live image. Defaults to `tex`
- `TEXLIVE_OPENOUT_ANY` - Sets the `openout_any` environment variable for TeX Live (see the `\openout` primitive [documentation](http://tug.org/texinfohtml/web2c.html#tex-invocation))
Further environment variables configure the [metrics module](https://github.com/overleaf/metrics-module)
Installation
------------
## Installation
The CLSI can be installed and set up as part of the entire [Overleaf stack](https://github.com/overleaf/overleaf) (complete with front end editor and document storage), or it can be run as a standalone service. To run is as a standalone service, first checkout this repository:
@@ -78,14 +76,14 @@ Note: if you're running the CLSI in macOS you may need to use `-v /var/run/docke
The CLSI should then be running at <http://localhost:3013>
Important note for Linux users
==============================
# Important note for Linux users
The Node application runs as user `node` in the CLSI, which has uid `1000`. As a consequence of this, the `compiles` folder gets created on your host with `uid` and `gid` set to `1000`.
```shell
ls -lnd compiles
```
> `drwxr-xr-x 2 1000 1000 4096 Mar 19 12:41 compiles`
If there is a user/group on your host which also happens to have `uid` / `gid` `1000` then that user/group will have ownership of the compiles folder on your host.
@@ -114,9 +112,7 @@ sudo chmod g+s compiles
This is a facet of the way docker works on Linux. See this [upstream issue](https://github.com/moby/moby/issues/7198)
API
---
## API
The CLSI is based on a JSON API.
@@ -128,29 +124,29 @@ The CLSI is based on a JSON API.
```json5
{
"compile": {
"options": {
// Which compiler to use. Can be latex, pdflatex, xelatex or lualatex
"compiler": "lualatex",
// How many seconds to wait before killing the process. Default is 60.
"timeout": 40
},
// The main file to run LaTeX on
"rootResourcePath": "main.tex",
// An array of files to include in the compilation. May have either the content
// passed directly, or a URL where it can be downloaded.
"resources": [
{
"path": "main.tex",
"content": "\\documentclass{article}\n\\begin{document}\nHello World\n\\end{document}"
}
// ,{
// "path": "image.png",
// "url": "www.example.com/image.png",
// "modified": 123456789 // Unix time since epoch
// }
]
}
compile: {
options: {
// Which compiler to use. Can be latex, pdflatex, xelatex or lualatex
compiler: 'lualatex',
// How many seconds to wait before killing the process. Default is 60.
timeout: 40,
},
// The main file to run LaTeX on
rootResourcePath: 'main.tex',
// An array of files to include in the compilation. May have either the content
// passed directly, or a URL where it can be downloaded.
resources: [
{
path: 'main.tex',
content: '\\documentclass{article}\n\\begin{document}\nHello World\n\\end{document}',
},
// ,{
// "path": "image.png",
// "url": "www.example.com/image.png",
// "modified": 123456789 // Unix time since epoch
// }
],
},
}
```
@@ -167,21 +163,23 @@ URLs will be downloaded and cached until provided with a more recent modified da
```json
{
"compile": {
"status": "success",
"outputFiles": [{
"type": "pdf",
"url": "http://localhost:3013/project/<project-id>/output/output.pdf"
}, {
"type": "log",
"url": "http://localhost:3013/project/<project-id>/output/output.log"
}]
}
"compile": {
"status": "success",
"outputFiles": [
{
"type": "pdf",
"url": "http://localhost:3013/project/<project-id>/output/output.pdf"
},
{
"type": "log",
"url": "http://localhost:3013/project/<project-id>/output/output.log"
}
]
}
}
```
License
-------
## License
The code in this repository is released under the GNU AFFERO GENERAL PUBLIC LICENSE, version 3. A copy can be found in the `LICENSE` file.

View File

@@ -85,7 +85,7 @@ function runLatex(projectId, options, callback) {
}
// number of latex runs and whether there were errors
const runs =
output?.stderr?.match(/^Run number \d+ of .*latex/gm)?.length || 0
output?.stdout?.match(/^Run number \d+ of .*latex/gm)?.length || 0
const failed = output?.stdout?.match(/^Latexmk: Errors/m) != null ? 1 : 0
// counters from latexmk output
stats['latexmk-errors'] = failed

View File

@@ -1,7 +1,7 @@
clsi
--data-dirs=cache,compiles,output
--dependencies=
--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",SANDBOXED_COMPILES="true",SANDBOXED_COMPILES_HOST_DIR_COMPILES=$PWD/compiles,SANDBOXED_COMPILES_HOST_DIR_OUTPUT=$PWD/output
--env-add=ENABLE_PDF_CACHING="true",PDF_CACHING_ENABLE_WORKER_POOL="true",ALLOWED_IMAGES=quay.io/sharelatex/texlive-full:2025.1,TEXLIVE_IMAGE=quay.io/sharelatex/texlive-full:2025.1,TEX_LIVE_IMAGE_NAME_OVERRIDE=us-east1-docker.pkg.dev/overleaf-ops/ol-docker,TEXLIVE_IMAGE_USER="tex",SANDBOXED_COMPILES="true",SANDBOXED_COMPILES_HOST_DIR_COMPILES=$PWD/compiles,SANDBOXED_COMPILES_HOST_DIR_OUTPUT=$PWD/output
--env-pass-through=
--esmock-loader=False
--node-version=22.18.0

View File

@@ -28,8 +28,8 @@ services:
NODE_OPTIONS: "--unhandled-rejections=strict"
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
ALLOWED_IMAGES: quay.io/sharelatex/texlive-full:2025.1
TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2025.1
TEX_LIVE_IMAGE_NAME_OVERRIDE: us-east1-docker.pkg.dev/overleaf-ops/ol-docker
TEXLIVE_IMAGE_USER: "tex"
SANDBOXED_COMPILES: "true"

View File

@@ -42,8 +42,8 @@ services:
NODE_OPTIONS: "--unhandled-rejections=strict"
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
ALLOWED_IMAGES: quay.io/sharelatex/texlive-full:2025.1
TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2025.1
TEX_LIVE_IMAGE_NAME_OVERRIDE: us-east1-docker.pkg.dev/overleaf-ops/ol-docker
TEXLIVE_IMAGE_USER: "tex"
SANDBOXED_COMPILES: "true"

View File

@@ -63,6 +63,11 @@
}
]
},
{
"name": "clone3",
"action": "SCMP_ACT_ERRNO",
"errnoRet": 38
},
{
"name": "close",
"action": "SCMP_ACT_ALLOW",
@@ -848,4 +853,4 @@
"args": []
}
]
}
}

View File

@@ -1,81 +1,66 @@
1/0: uncompressed; offset = 123103
2/0: uncompressed; offset = 123422
1/0: uncompressed; offset = 92043
2/0: uncompressed; offset = 92293
3/0: uncompressed; offset = 15
4/0: uncompressed; offset = 216
5/0: uncompressed; offset = 1084
6/0: uncompressed; offset = 1244
7/0: uncompressed; offset = 4001
8/0: uncompressed; offset = 4155
9/0: uncompressed; offset = 4297
10/0: uncompressed; offset = 4933
11/0: uncompressed; offset = 5309
12/0: uncompressed; offset = 5498
13/0: uncompressed; offset = 30250
14/0: uncompressed; offset = 31471
15/0: uncompressed; offset = 38404
16/0: uncompressed; offset = 39046
17/0: uncompressed; offset = 40166
18/0: uncompressed; offset = 40906
19/0: uncompressed; offset = 65560
20/0: uncompressed; offset = 74702
21/0: uncompressed; offset = 81705
22/0: uncompressed; offset = 97182
23/0: uncompressed; offset = 104117
24/0: uncompressed; offset = 111195
25/0: uncompressed; offset = 118571
26/0: compressed; stream = 6, index = 0
27/0: compressed; stream = 6, index = 1
28/0: compressed; stream = 6, index = 2
29/0: compressed; stream = 6, index = 3
30/0: compressed; stream = 6, index = 4
31/0: compressed; stream = 6, index = 5
32/0: compressed; stream = 6, index = 6
33/0: compressed; stream = 6, index = 7
34/0: compressed; stream = 6, index = 8
35/0: compressed; stream = 6, index = 9
36/0: compressed; stream = 6, index = 10
37/0: compressed; stream = 6, index = 11
38/0: compressed; stream = 6, index = 12
39/0: compressed; stream = 6, index = 13
40/0: compressed; stream = 6, index = 14
41/0: compressed; stream = 6, index = 15
42/0: compressed; stream = 6, index = 16
43/0: compressed; stream = 6, index = 17
44/0: compressed; stream = 6, index = 18
45/0: compressed; stream = 6, index = 19
46/0: compressed; stream = 6, index = 20
47/0: compressed; stream = 6, index = 21
48/0: compressed; stream = 6, index = 22
49/0: compressed; stream = 6, index = 23
50/0: compressed; stream = 6, index = 24
51/0: compressed; stream = 6, index = 25
52/0: compressed; stream = 6, index = 26
53/0: compressed; stream = 6, index = 27
54/0: compressed; stream = 6, index = 28
55/0: compressed; stream = 6, index = 29
56/0: compressed; stream = 6, index = 30
57/0: compressed; stream = 6, index = 31
58/0: compressed; stream = 6, index = 32
59/0: compressed; stream = 6, index = 33
60/0: compressed; stream = 6, index = 34
61/0: compressed; stream = 6, index = 35
62/0: compressed; stream = 6, index = 36
63/0: compressed; stream = 6, index = 37
64/0: compressed; stream = 6, index = 38
65/0: compressed; stream = 6, index = 39
66/0: compressed; stream = 6, index = 40
67/0: compressed; stream = 6, index = 41
68/0: compressed; stream = 6, index = 42
69/0: compressed; stream = 6, index = 43
70/0: compressed; stream = 6, index = 44
71/0: compressed; stream = 6, index = 45
72/0: compressed; stream = 6, index = 46
73/0: compressed; stream = 6, index = 47
74/0: compressed; stream = 6, index = 48
75/0: compressed; stream = 6, index = 49
76/0: compressed; stream = 6, index = 50
77/0: compressed; stream = 6, index = 51
78/0: compressed; stream = 6, index = 52
79/0: compressed; stream = 6, index = 53
80/0: compressed; stream = 6, index = 54
81/0: compressed; stream = 6, index = 55
5/0: uncompressed; offset = 979
6/0: uncompressed; offset = 1029
7/0: uncompressed; offset = 1191
8/0: uncompressed; offset = 1300
9/0: uncompressed; offset = 1902
10/0: uncompressed; offset = 2233
11/0: uncompressed; offset = 2870
12/0: uncompressed; offset = 3910
13/0: uncompressed; offset = 4666
14/0: uncompressed; offset = 5435
15/0: uncompressed; offset = 6204
16/0: uncompressed; offset = 7177
17/0: uncompressed; offset = 28756
18/0: uncompressed; offset = 37898
19/0: uncompressed; offset = 44901
20/0: uncompressed; offset = 60378
21/0: uncompressed; offset = 67313
22/0: uncompressed; offset = 74391
23/0: uncompressed; offset = 81767
24/0: uncompressed; offset = 86299
25/0: uncompressed; offset = 87068
26/0: uncompressed; offset = 87881
27/0: uncompressed; offset = 88694
28/0: uncompressed; offset = 89507
29/0: uncompressed; offset = 90252
30/0: compressed; stream = 29, index = 0
31/0: compressed; stream = 29, index = 1
32/0: compressed; stream = 29, index = 2
33/0: compressed; stream = 29, index = 3
34/0: compressed; stream = 29, index = 4
35/0: compressed; stream = 29, index = 5
36/0: compressed; stream = 29, index = 6
37/0: compressed; stream = 29, index = 7
38/0: compressed; stream = 29, index = 8
39/0: compressed; stream = 29, index = 9
40/0: compressed; stream = 29, index = 10
41/0: compressed; stream = 29, index = 11
42/0: compressed; stream = 29, index = 12
43/0: compressed; stream = 29, index = 13
44/0: compressed; stream = 29, index = 14
45/0: compressed; stream = 29, index = 15
46/0: compressed; stream = 29, index = 16
47/0: compressed; stream = 29, index = 17
48/0: compressed; stream = 29, index = 18
49/0: compressed; stream = 29, index = 19
50/0: compressed; stream = 29, index = 20
51/0: compressed; stream = 29, index = 21
52/0: compressed; stream = 29, index = 22
53/0: compressed; stream = 29, index = 23
54/0: compressed; stream = 29, index = 24
55/0: compressed; stream = 29, index = 25
56/0: compressed; stream = 29, index = 26
57/0: compressed; stream = 29, index = 27
58/0: compressed; stream = 29, index = 28
59/0: compressed; stream = 29, index = 29
60/0: compressed; stream = 29, index = 30
61/0: compressed; stream = 29, index = 31
62/0: compressed; stream = 29, index = 32
63/0: compressed; stream = 29, index = 33
64/0: compressed; stream = 29, index = 34
65/0: compressed; stream = 29, index = 35
66/0: compressed; stream = 29, index = 36

View File

@@ -1,20 +1,20 @@
1/0: uncompressed; offset = 4964
2/0: uncompressed; offset = 5023
3/0: uncompressed; offset = 5234
1/0: uncompressed; offset = 4966
2/0: uncompressed; offset = 5025
3/0: uncompressed; offset = 5259
4/0: uncompressed; offset = 15
5/0: uncompressed; offset = 734
6/0: uncompressed; offset = 799
7/0: uncompressed; offset = 933
8/0: uncompressed; offset = 1104
9/0: uncompressed; offset = 1947
10/0: uncompressed; offset = 1992
11/0: uncompressed; offset = 2182
12/0: uncompressed; offset = 2427
13/0: uncompressed; offset = 2597
14/0: uncompressed; offset = 2822
15/0: uncompressed; offset = 2989
16/0: uncompressed; offset = 3239
17/0: uncompressed; offset = 3271
18/0: uncompressed; offset = 3328
19/0: uncompressed; offset = 3740
20/0: uncompressed; offset = 4270
9/0: uncompressed; offset = 1946
10/0: uncompressed; offset = 1994
11/0: uncompressed; offset = 2184
12/0: uncompressed; offset = 2429
13/0: uncompressed; offset = 2599
14/0: uncompressed; offset = 2824
15/0: uncompressed; offset = 2991
16/0: uncompressed; offset = 3241
17/0: uncompressed; offset = 3273
18/0: uncompressed; offset = 3330
19/0: uncompressed; offset = 3742
20/0: uncompressed; offset = 4272

View File

@@ -1,26 +1,26 @@
1/0: uncompressed; offset = 25097
2/0: uncompressed; offset = 25156
3/0: uncompressed; offset = 25367
1/0: uncompressed; offset = 25082
2/0: uncompressed; offset = 25141
3/0: uncompressed; offset = 25375
4/0: uncompressed; offset = 15
5/0: uncompressed; offset = 854
6/0: uncompressed; offset = 919
7/0: uncompressed; offset = 1074
8/0: uncompressed; offset = 1245
9/0: uncompressed; offset = 18343
10/0: uncompressed; offset = 18388
11/0: uncompressed; offset = 18752
12/0: uncompressed; offset = 19071
13/0: uncompressed; offset = 19360
14/0: uncompressed; offset = 19604
15/0: uncompressed; offset = 19770
16/0: uncompressed; offset = 20007
17/0: uncompressed; offset = 20174
18/0: uncompressed; offset = 20424
19/0: uncompressed; offset = 20456
20/0: uncompressed; offset = 20525
21/0: uncompressed; offset = 23109
22/0: uncompressed; offset = 23500
23/0: uncompressed; offset = 24229
24/0: uncompressed; offset = 24641
25/0: uncompressed; offset = 24741
26/0: uncompressed; offset = 24985
10/0: uncompressed; offset = 18391
11/0: uncompressed; offset = 18755
12/0: uncompressed; offset = 19074
13/0: uncompressed; offset = 19363
14/0: uncompressed; offset = 19607
15/0: uncompressed; offset = 19773
16/0: uncompressed; offset = 20010
17/0: uncompressed; offset = 20177
18/0: uncompressed; offset = 20427
19/0: uncompressed; offset = 20459
20/0: uncompressed; offset = 20528
21/0: uncompressed; offset = 20919
22/0: uncompressed; offset = 21648
23/0: uncompressed; offset = 22060
24/0: uncompressed; offset = 24626
25/0: uncompressed; offset = 24726
26/0: uncompressed; offset = 24970

View File

@@ -1,19 +1,21 @@
1/0: uncompressed; offset = 20679
2/0: uncompressed; offset = 20927
1/0: uncompressed; offset = 22291
2/0: uncompressed; offset = 22541
3/0: uncompressed; offset = 15
4/0: uncompressed; offset = 216
5/0: uncompressed; offset = 650
6/0: uncompressed; offset = 700
7/0: uncompressed; offset = 826
8/0: uncompressed; offset = 934
9/0: uncompressed; offset = 1252
10/0: uncompressed; offset = 8248
11/0: uncompressed; offset = 20115
12/0: compressed; stream = 11, index = 0
13/0: compressed; stream = 11, index = 1
14/0: compressed; stream = 11, index = 2
15/0: compressed; stream = 11, index = 3
16/0: compressed; stream = 11, index = 4
17/0: compressed; stream = 11, index = 5
18/0: compressed; stream = 11, index = 6
19/0: compressed; stream = 11, index = 7
5/0: uncompressed; offset = 664
6/0: uncompressed; offset = 714
7/0: uncompressed; offset = 844
8/0: uncompressed; offset = 952
9/0: uncompressed; offset = 1294
10/0: uncompressed; offset = 8288
11/0: uncompressed; offset = 20150
12/0: uncompressed; offset = 20963
13/0: uncompressed; offset = 21708
14/0: compressed; stream = 13, index = 0
15/0: compressed; stream = 13, index = 1
16/0: compressed; stream = 13, index = 2
17/0: compressed; stream = 13, index = 3
18/0: compressed; stream = 13, index = 4
19/0: compressed; stream = 13, index = 5
20/0: compressed; stream = 13, index = 6
21/0: compressed; stream = 13, index = 7

View File

@@ -1,6 +1,7 @@
const Client = require('./helpers/Client')
const { fetchNothing } = require('@overleaf/fetch-utils')
const ClsiApp = require('./helpers/ClsiApp')
const { expect } = require('chai')
describe('Simple LaTeX file', function () {
const content = `\
@@ -72,8 +73,8 @@ Hello world
'latexmk-errors',
'latex-runs',
'latex-runs-with-errors',
'latex-runs-2',
'latex-runs-with-errors-2',
'latex-runs-1',
'latex-runs-with-errors-1',
'pdf-caching-total-ranges-size',
'pdf-caching-reclaimed-space',
'pdf-caching-new-ranges-size',
@@ -92,4 +93,40 @@ Hello world
})
})
}
describe('document with shell commands', function () {
before(async function () {
this.project_id = Client.randomId()
this.request = {
resources: [
{
path: 'main.tex',
content: `\
\\documentclass{article}
\\begin{document}
Testing system calls:
\\immediate\\write18{/bin/date > date.txt}
The current date from system is: \\input{date.txt}
The current date from popen is: \\input{"|date"}
\\end{document}\
`,
},
],
}
await ClsiApp.ensureRunning()
})
it('should compile successfully', async function () {
const body = await Client.compile(this.project_id, this.request)
expect(body).to.exist
expect(body.compile?.status, 'compile status').to.equal('success')
})
it('should return the PDF', async function () {
const body = await Client.compile(this.project_id, this.request)
const pdf = Client.getOutputFile(body, 'pdf')
expect(pdf, 'pdf file not produced').to.exist
expect(pdf.type, 'invalid pdf file').to.equal('pdf')
})
})
})

View File

@@ -0,0 +1,14 @@
const Client = require('./helpers/Client')
const ClsiApp = require('./helpers/ClsiApp')
const { expect } = require('chai')
describe('Smoke Test', function () {
before(async function () {
await ClsiApp.ensureRunning()
})
it('should compile the test document and return a response of "OK"', async function () {
const response = await Client.smokeTest()
expect(response).to.equal('OK')
})
})

View File

@@ -1,5 +1,9 @@
const express = require('express')
const { fetchJson, fetchNothing } = require('@overleaf/fetch-utils')
const {
fetchJson,
fetchNothing,
fetchString,
} = require('@overleaf/fetch-utils')
const fs = require('node:fs')
const fsPromises = require('node:fs/promises')
const Settings = require('@overleaf/settings')
@@ -177,12 +181,19 @@ async function compileDirectory(projectId, baseDirectory, directory) {
return await compile(projectId, req)
}
function smokeTest() {
return fetchString(`${host}/smoke_test_force`, {
method: 'GET',
})
}
module.exports = {
randomId,
compile,
stopCompile,
clearCache,
getOutputFile,
smokeTest,
runFakeFilestoreService,
startFakeFilestoreApp,
syncFromCode,

View File

@@ -54,6 +54,9 @@ module.exports = {
\\documentclass{article}
\\usepackage{tikz}
\\usetikzlibrary{calc,fadings,decorations.pathreplacing}
\\usepackage{ifplatform} % test shell escape, conditionals to test which platform is being used
\\usepackage{minted} % to test shell commands
\\usepackage{bashful} % to test shell commands
\\begin{document}
\\begin{tikzpicture}
\\def\\nuPi{3.1459265}
@@ -79,6 +82,24 @@ module.exports = {
}
}
\\end{tikzpicture}
% Test minted (shell commands)
\\begin{minted}{python}
x = 1 + 2
\\end{minted}
% Test bashful (shell commands)
\\bash[stdout,stderr]
date
\\END
% Test system
\\immediate\\write18{/bin/date > date.txt}
\\input date.txt
% Test popen
\\input{"|date"}
\\end{document}\
`,
},

View File

@@ -1,359 +1,123 @@
{
"xRefEntries": [
{
"offset": 0,
"gen": 0,
"free": true
"offset": 0
},
{
"offset": 123103,
"gen": 0,
"offset": 92043,
"uncompressed": true
},
{
"offset": 123422,
"gen": 0,
"offset": 92293,
"uncompressed": true
},
{
"offset": 15,
"gen": 0,
"uncompressed": true
},
{
"offset": 216,
"gen": 0,
"uncompressed": true
},
{
"offset": 1084,
"gen": 0,
"offset": 979,
"uncompressed": true
},
{
"offset": 1244,
"gen": 0,
"offset": 1029,
"uncompressed": true
},
{
"offset": 4001,
"gen": 0,
"offset": 1191,
"uncompressed": true
},
{
"offset": 4155,
"gen": 0,
"offset": 1300,
"uncompressed": true
},
{
"offset": 4297,
"gen": 0,
"offset": 1902,
"uncompressed": true
},
{
"offset": 4933,
"gen": 0,
"offset": 2233,
"uncompressed": true
},
{
"offset": 5309,
"gen": 0,
"offset": 2870,
"uncompressed": true
},
{
"offset": 5498,
"gen": 0,
"offset": 3910,
"uncompressed": true
},
{
"offset": 30250,
"gen": 0,
"offset": 4666,
"uncompressed": true
},
{
"offset": 31471,
"gen": 0,
"offset": 5435,
"uncompressed": true
},
{
"offset": 38404,
"gen": 0,
"offset": 6204,
"uncompressed": true
},
{
"offset": 39046,
"gen": 0,
"offset": 7177,
"uncompressed": true
},
{
"offset": 40166,
"gen": 0,
"offset": 28756,
"uncompressed": true
},
{
"offset": 40906,
"gen": 0,
"offset": 37898,
"uncompressed": true
},
{
"offset": 65560,
"gen": 0,
"offset": 44901,
"uncompressed": true
},
{
"offset": 74702,
"gen": 0,
"offset": 60378,
"uncompressed": true
},
{
"offset": 81705,
"gen": 0,
"offset": 67313,
"uncompressed": true
},
{
"offset": 97182,
"gen": 0,
"offset": 74391,
"uncompressed": true
},
{
"offset": 104117,
"gen": 0,
"offset": 81767,
"uncompressed": true
},
{
"offset": 111195,
"gen": 0,
"offset": 86299,
"uncompressed": true
},
{
"offset": 118571,
"gen": 0,
"offset": 87068,
"uncompressed": true
},
{
"offset": 6,
"gen": 0
"offset": 87881,
"uncompressed": true
},
{
"offset": 6,
"gen": 1
"offset": 88694,
"uncompressed": true
},
{
"offset": 6,
"gen": 2
"offset": 89507,
"uncompressed": true
},
{
"offset": 6,
"gen": 3
},
{
"offset": 6,
"gen": 4
},
{
"offset": 6,
"gen": 5
},
{
"offset": 6,
"gen": 6
},
{
"offset": 6,
"gen": 7
},
{
"offset": 6,
"gen": 8
},
{
"offset": 6,
"gen": 9
},
{
"offset": 6,
"gen": 10
},
{
"offset": 6,
"gen": 11
},
{
"offset": 6,
"gen": 12
},
{
"offset": 6,
"gen": 13
},
{
"offset": 6,
"gen": 14
},
{
"offset": 6,
"gen": 15
},
{
"offset": 6,
"gen": 16
},
{
"offset": 6,
"gen": 17
},
{
"offset": 6,
"gen": 18
},
{
"offset": 6,
"gen": 19
},
{
"offset": 6,
"gen": 20
},
{
"offset": 6,
"gen": 21
},
{
"offset": 6,
"gen": 22
},
{
"offset": 6,
"gen": 23
},
{
"offset": 6,
"gen": 24
},
{
"offset": 6,
"gen": 25
},
{
"offset": 6,
"gen": 26
},
{
"offset": 6,
"gen": 27
},
{
"offset": 6,
"gen": 28
},
{
"offset": 6,
"gen": 29
},
{
"offset": 6,
"gen": 30
},
{
"offset": 6,
"gen": 31
},
{
"offset": 6,
"gen": 32
},
{
"offset": 6,
"gen": 33
},
{
"offset": 6,
"gen": 34
},
{
"offset": 6,
"gen": 35
},
{
"offset": 6,
"gen": 36
},
{
"offset": 6,
"gen": 37
},
{
"offset": 6,
"gen": 38
},
{
"offset": 6,
"gen": 39
},
{
"offset": 6,
"gen": 40
},
{
"offset": 6,
"gen": 41
},
{
"offset": 6,
"gen": 42
},
{
"offset": 6,
"gen": 43
},
{
"offset": 6,
"gen": 44
},
{
"offset": 6,
"gen": 45
},
{
"offset": 6,
"gen": 46
},
{
"offset": 6,
"gen": 47
},
{
"offset": 6,
"gen": 48
},
{
"offset": 6,
"gen": 49
},
{
"offset": 6,
"gen": 50
},
{
"offset": 6,
"gen": 51
},
{
"offset": 6,
"gen": 52
},
{
"offset": 6,
"gen": 53
},
{
"offset": 6,
"gen": 54
},
{
"offset": 6,
"gen": 55
"offset": 90252,
"uncompressed": true
}
],
"startXRefTable": 123422
]
}

View File

@@ -1,110 +1,87 @@
{
"xRefEntries": [
{
"offset": 0,
"gen": 65535,
"free": true
"offset": 0
},
{
"offset": 4964,
"gen": 0,
"offset": 4966,
"uncompressed": true
},
{
"offset": 5023,
"gen": 0,
"offset": 5025,
"uncompressed": true
},
{
"offset": 5234,
"gen": 0,
"offset": 5259,
"uncompressed": true
},
{
"offset": 15,
"gen": 0,
"uncompressed": true
},
{
"offset": 734,
"gen": 0,
"uncompressed": true
},
{
"offset": 799,
"gen": 0,
"uncompressed": true
},
{
"offset": 933,
"gen": 0,
"uncompressed": true
},
{
"offset": 1104,
"gen": 0,
"uncompressed": true
},
{
"offset": 1947,
"gen": 0,
"offset": 1946,
"uncompressed": true
},
{
"offset": 1992,
"gen": 0,
"offset": 1994,
"uncompressed": true
},
{
"offset": 2182,
"gen": 0,
"offset": 2184,
"uncompressed": true
},
{
"offset": 2427,
"gen": 0,
"offset": 2429,
"uncompressed": true
},
{
"offset": 2597,
"gen": 0,
"offset": 2599,
"uncompressed": true
},
{
"offset": 2822,
"gen": 0,
"offset": 2824,
"uncompressed": true
},
{
"offset": 2989,
"gen": 0,
"offset": 2991,
"uncompressed": true
},
{
"offset": 3239,
"gen": 0,
"offset": 3241,
"uncompressed": true
},
{
"offset": 3271,
"gen": 0,
"offset": 3273,
"uncompressed": true
},
{
"offset": 3328,
"gen": 0,
"offset": 3330,
"uncompressed": true
},
{
"offset": 3740,
"gen": 0,
"offset": 3742,
"uncompressed": true
},
{
"offset": 4270,
"gen": 0,
"offset": 4272,
"uncompressed": true
}
],
"startXRefTable": 6682
]
}

View File

@@ -1,140 +1,111 @@
{
"xRefEntries": [
{
"offset": 0,
"gen": 65535,
"free": true
"offset": 0
},
{
"offset": 25097,
"gen": 0,
"offset": 25082,
"uncompressed": true
},
{
"offset": 25156,
"gen": 0,
"offset": 25141,
"uncompressed": true
},
{
"offset": 25367,
"gen": 0,
"offset": 25375,
"uncompressed": true
},
{
"offset": 15,
"gen": 0,
"uncompressed": true
},
{
"offset": 854,
"gen": 0,
"uncompressed": true
},
{
"offset": 919,
"gen": 0,
"uncompressed": true
},
{
"offset": 1074,
"gen": 0,
"uncompressed": true
},
{
"offset": 1245,
"gen": 0,
"uncompressed": true
},
{
"offset": 18343,
"gen": 0,
"uncompressed": true
},
{
"offset": 18388,
"gen": 0,
"offset": 18391,
"uncompressed": true
},
{
"offset": 18752,
"gen": 0,
"offset": 18755,
"uncompressed": true
},
{
"offset": 19071,
"gen": 0,
"offset": 19074,
"uncompressed": true
},
{
"offset": 19360,
"gen": 0,
"offset": 19363,
"uncompressed": true
},
{
"offset": 19604,
"gen": 0,
"offset": 19607,
"uncompressed": true
},
{
"offset": 19770,
"gen": 0,
"offset": 19773,
"uncompressed": true
},
{
"offset": 20007,
"gen": 0,
"offset": 20010,
"uncompressed": true
},
{
"offset": 20174,
"gen": 0,
"offset": 20177,
"uncompressed": true
},
{
"offset": 20424,
"gen": 0,
"offset": 20427,
"uncompressed": true
},
{
"offset": 20456,
"gen": 0,
"offset": 20459,
"uncompressed": true
},
{
"offset": 20525,
"gen": 0,
"offset": 20528,
"uncompressed": true
},
{
"offset": 23109,
"gen": 0,
"offset": 20919,
"uncompressed": true
},
{
"offset": 23500,
"gen": 0,
"offset": 21648,
"uncompressed": true
},
{
"offset": 24229,
"gen": 0,
"offset": 22060,
"uncompressed": true
},
{
"offset": 24641,
"gen": 0,
"offset": 24626,
"uncompressed": true
},
{
"offset": 24741,
"gen": 0,
"offset": 24726,
"uncompressed": true
},
{
"offset": 24985,
"gen": 0,
"offset": 24970,
"uncompressed": true
}
],
"startXRefTable": 26815
]
}

View File

@@ -1,97 +1,59 @@
{
"xRefEntries": [
{
"offset": 0,
"gen": 0,
"free": true
"offset": 0
},
{
"offset": 20679,
"gen": 0,
"offset": 22291,
"uncompressed": true
},
{
"offset": 20927,
"gen": 0,
"offset": 22541,
"uncompressed": true
},
{
"offset": 15,
"gen": 0,
"uncompressed": true
},
{
"offset": 216,
"gen": 0,
"uncompressed": true
},
{
"offset": 650,
"gen": 0,
"offset": 664,
"uncompressed": true
},
{
"offset": 700,
"gen": 0,
"offset": 714,
"uncompressed": true
},
{
"offset": 826,
"gen": 0,
"offset": 844,
"uncompressed": true
},
{
"offset": 934,
"gen": 0,
"offset": 952,
"uncompressed": true
},
{
"offset": 1252,
"gen": 0,
"offset": 1294,
"uncompressed": true
},
{
"offset": 8248,
"gen": 0,
"offset": 8288,
"uncompressed": true
},
{
"offset": 20115,
"gen": 0,
"offset": 20150,
"uncompressed": true
},
{
"offset": 11,
"gen": 0
"offset": 20963,
"uncompressed": true
},
{
"offset": 11,
"gen": 1
},
{
"offset": 11,
"gen": 2
},
{
"offset": 11,
"gen": 3
},
{
"offset": 11,
"gen": 4
},
{
"offset": 11,
"gen": 5
},
{
"offset": 11,
"gen": 6
},
{
"offset": 11,
"gen": 7
"offset": 21708,
"uncompressed": true
}
],
"startXRefTable": 20927
]
}

View File

@@ -4,13 +4,14 @@
BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g')
PROJECT_NAME = contacts
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_REPO ?= us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/package.json \
$(MONOREPO)/package-lock.json \
@@ -28,7 +29,7 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \
BRANCH_NAME=$(BRANCH_NAME) \
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}

View File

@@ -4,13 +4,14 @@
BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g')
PROJECT_NAME = docstore
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_REPO ?= us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/package.json \
$(MONOREPO)/package-lock.json \
@@ -30,7 +31,7 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \
BRANCH_NAME=$(BRANCH_NAME) \
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}

View File

@@ -4,13 +4,14 @@
BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g')
PROJECT_NAME = document-updater
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_REPO ?= us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/package.json \
$(MONOREPO)/package-lock.json \
@@ -31,7 +32,7 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \
BRANCH_NAME=$(BRANCH_NAME) \
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}

View File

@@ -4,13 +4,14 @@
BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g')
PROJECT_NAME = filestore
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_REPO ?= us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/package.json \
$(MONOREPO)/package-lock.json \
@@ -27,7 +28,7 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \
BRANCH_NAME=$(BRANCH_NAME) \
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}

View File

@@ -5,12 +5,13 @@ MVN_TARGET := target/writelatex-git-bridge-1.0-SNAPSHOT-jar-with-dependencies.ja
export BUILD_NUMBER ?= local
export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g')
export COMMIT_SHA ?= $(shell git rev-parse HEAD)
PROJECT_NAME = git-bridge
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_REPO ?= us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME)
IMAGE_REPO_BRANCH ?= $(IMAGE_REPO):$(BRANCH_NAME)
IMAGE_REPO_BRANCH ?= $(IMAGE_REPO):$(BRANCH_NAME_TAG_SAFE)
IMAGE_REPO_MAIN ?= $(IMAGE_REPO):main
IMAGE_REPO_FINAL ?= $(IMAGE_REPO_BRANCH)-$(BUILD_NUMBER)

View File

@@ -4,13 +4,14 @@
BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g')
PROJECT_NAME = history-v1
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_REPO ?= us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/package.json \
$(MONOREPO)/package-lock.json \
@@ -32,7 +33,7 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \
BRANCH_NAME=$(BRANCH_NAME) \
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}

View File

@@ -4,13 +4,14 @@
BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g')
PROJECT_NAME = notifications
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_REPO ?= us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/package.json \
$(MONOREPO)/package-lock.json \
@@ -29,7 +30,7 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \
BRANCH_NAME=$(BRANCH_NAME) \
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}

View File

@@ -4,13 +4,14 @@
BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g')
PROJECT_NAME = project-history
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_REPO ?= us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/package.json \
$(MONOREPO)/package-lock.json \
@@ -31,7 +32,7 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \
BRANCH_NAME=$(BRANCH_NAME) \
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}

View File

@@ -4,13 +4,14 @@
BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g')
PROJECT_NAME = real-time
BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
HERE=$(shell pwd)
export MONOREPO ?= $(shell cd ../../ && pwd)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_CI ?= ci/$(PROJECT_NAME):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_REPO ?= us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME)-$(BUILD_NUMBER)
IMAGE_REPO_FINAL ?= $(IMAGE_REPO):$(BRANCH_NAME_TAG_SAFE)-$(BUILD_NUMBER)
IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/package.json \
$(MONOREPO)/package-lock.json \
@@ -27,7 +28,7 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \
BRANCH_NAME=$(BRANCH_NAME) \
BRANCH_NAME=$(BRANCH_NAME_TAG_SAFE) \
PROJECT_NAME=$(PROJECT_NAME) \
MOCHA_GREP=${MOCHA_GREP} \
docker compose ${DOCKER_COMPOSE_FLAGS}

View File

@@ -3,7 +3,17 @@ import { ProjectAuditLogEntry } from '../../models/ProjectAuditLogEntry.js'
import { callbackify } from '@overleaf/promise-utils'
import SubscriptionLocator from '../Subscription/SubscriptionLocator.mjs'
const MANAGED_GROUP_PROJECT_EVENTS = ['accept-invite', 'project-created']
const MANAGED_GROUP_PROJECT_EVENTS = [
'accept-invite',
'project-created',
'project-deleted',
'project-archived',
'project-unarchived',
'project-trashed',
'project-untrashed',
'project-restored',
'project-cloned',
]
export default {
promises: {

View File

@@ -168,7 +168,12 @@ const _ProjectController = {
deleterUser: user,
ipAddress: req.ip,
})
ProjectAuditLogHandler.addEntryIfManagedInBackground(
projectId,
'project-deleted',
user._id,
req.ip
)
res.sendStatus(200)
},
@@ -176,6 +181,12 @@ const _ProjectController = {
const projectId = req.params.Project_id
const userId = SessionManager.getLoggedInUserId(req.session)
await ProjectDeleter.promises.archiveProject(projectId, userId)
ProjectAuditLogHandler.addEntryIfManagedInBackground(
projectId,
'project-archived',
userId,
req.ip
)
res.sendStatus(200)
},
@@ -183,6 +194,12 @@ const _ProjectController = {
const projectId = req.params.Project_id
const userId = SessionManager.getLoggedInUserId(req.session)
await ProjectDeleter.promises.unarchiveProject(projectId, userId)
ProjectAuditLogHandler.addEntryIfManagedInBackground(
projectId,
'project-unarchived',
userId,
req.ip
)
res.sendStatus(200)
},
@@ -190,6 +207,12 @@ const _ProjectController = {
const projectId = req.params.project_id
const userId = SessionManager.getLoggedInUserId(req.session)
await ProjectDeleter.promises.trashProject(projectId, userId)
ProjectAuditLogHandler.addEntryIfManagedInBackground(
projectId,
'project-trashed',
userId,
req.ip
)
res.sendStatus(200)
},
@@ -197,6 +220,12 @@ const _ProjectController = {
const projectId = req.params.project_id
const userId = SessionManager.getLoggedInUserId(req.session)
await ProjectDeleter.promises.untrashProject(projectId, userId)
ProjectAuditLogHandler.addEntryIfManagedInBackground(
projectId,
'project-untrashed',
userId,
req.ip
)
res.sendStatus(200)
},
@@ -212,8 +241,15 @@ const _ProjectController = {
},
async restoreProject(req, res) {
const user = SessionManager.getLoggedInUserId(req.session)
const projectId = req.params.Project_id
await ProjectDeleter.promises.restoreProject(projectId)
ProjectAuditLogHandler.addEntryIfManagedInBackground(
projectId,
'project-restored',
user._id,
req.ip
)
res.sendStatus(200)
},
@@ -235,6 +271,12 @@ const _ProjectController = {
projectName,
tags
)
ProjectAuditLogHandler.addEntryIfManagedInBackground(
projectId,
'project-cloned',
currentUser._id,
req.ip
)
res.json({
name: project.name,
lastUpdated: project.lastUpdated,
@@ -446,7 +488,10 @@ const _ProjectController = {
affiliations: InstitutionsGetter.promises
.getCurrentAffiliations(userId)
.catch(err => {
logger.error({ err, userId }, 'failed to get institution licence')
logger.error(
{ err, userId },
'failed to get current affiliations'
)
return false
}),
subscription:
@@ -1174,12 +1219,15 @@ const _ProjectController = {
aiFeaturesAllowed,
userIsMemberOfGroupSubscription
) {
let inEnterpriseCommons = false
const affiliations = userValues.affiliations || []
for (const affiliation of affiliations) {
inEnterpriseCommons =
inEnterpriseCommons || affiliation.institution?.enterpriseCommons
}
const affiliations = userValues.affiliations
const affiliateLookupFailed = affiliations === false
// if affiliations is specifically false instead of empty, we know the affiliate lookup failed, and should defer to blocking auto-loading
const inEnterpriseCommons =
affiliateLookupFailed ||
affiliations.some(
affiliation => affiliation.institution?.enterpriseCommons
)
// check if a user has never tried writefull before (writefull.enabled will be null)
// if they previously accepted writefull, or are have been already assigned to a trial, user.writefull will be true,

View File

@@ -14,7 +14,6 @@ import EmailHelper from '../Helpers/EmailHelper.js'
import Errors from '../Errors/Errors.js'
import { callbackify, callbackifyMultiResult } from '@overleaf/promise-utils'
import NotificationsBuilder from '../Notifications/NotificationsBuilder.mjs'
import RecurlyClient from './RecurlyClient.mjs'
const { ObjectId } = mongodb
@@ -78,18 +77,13 @@ async function _deleteUserSubscription(subscription, userId, ipAddress) {
deleterData
)
// Terminate the subscription in Recurly
if (subscription.recurlySubscription_id) {
try {
await RecurlyClient.promises.terminateSubscriptionByUuid(
subscription.recurlySubscription_id
)
} catch (err) {
logger.error(
{ err, subscriptionId: subscription._id },
'terminating subscription failed'
)
}
try {
await Modules.promises.hooks.fire('terminateSubscription', subscription)
} catch (err) {
logger.error(
{ err, subscriptionId: subscription._id },
'terminating subscription failed'
)
}
}

View File

@@ -32,7 +32,15 @@ function _canHaveNoInitiatorId(operation, info) {
}
// events that are visible to managed user admins in Group Audit Logs view
const MANAGED_GROUP_USER_EVENTS = ['login', 'reset-password', 'update-password']
const MANAGED_GROUP_USER_EVENTS = [
'login',
'reset-password',
'update-password',
'link-dropbox',
'unlink-dropbox',
'link-github',
'unlink-github',
]
/**
* Add an audit log entry

View File

@@ -3,15 +3,15 @@
declare -a vitest_args=("$@")
has_mocha_test=0
has_vitest_test=0
has_sequential_test=0
for dir_path in "$@"; do
if [ -n "$(find "$dir_path" -name "*.js" -type f -print -quit 2>/dev/null)" ]; then
has_mocha_test=1
fi
if [ -n "$(find "$dir_path" -name "*.test.mjs" -type f -print -quit 2>/dev/null)" ]; then
has_vitest_test=1
if [ -n "$(find "$dir_path" -name "*.sequential.test.mjs" -type f -print -quit 2>/dev/null)" ]; then
has_sequential_test=1
fi
done
@@ -26,24 +26,26 @@ fi
echo "Running unit tests in directory: $*"
# Remove this if/else when we have converted all module tests to vitest.
if (( has_vitest_test == 1 )); then
npm run test:unit:esm -- "${vitest_args[@]}"
vitest_status=$?
npm run test:unit:esm:parallel -- "${vitest_args[@]}"
vitest_parallel_status=$?
if (( has_sequential_test == 0 )); then
echo "No sequential vitest tests found, skipping sequential vitest step."
vitest_sequential_status=0
else
echo "No vitest tests found in $*, skipping vitest step."
vitest_status=0
npm run test:unit:esm:sequential -- "${vitest_args[@]}"
vitest_sequential_status=$?
fi
if (( has_mocha_test == 1 )); then
mocha --recursive --timeout 25000 --exit --grep="$MOCHA_GREP" --require test/unit/bootstrap.js --extension=js "$@"
mocha_status=$?
else
echo "No mocha tests found in $TARGET_DIR, skipping mocha step."
echo "No mocha tests found, skipping mocha step."
mocha_status=0
fi
if [ "$mocha_status" -eq 0 ] && [ "$vitest_status" -eq 0 ]; then
if [ "$mocha_status" -eq 0 ] && [ "$vitest_sequential_status" -eq 0 ] && [ "$vitest_parallel_status" -eq 0 ]; then
exit 0
fi
@@ -53,8 +55,12 @@ if [ "$mocha_status" -ne 0 ]; then
echo "Mocha tests failed with status: $mocha_status"
fi
if [ "$vitest_status" -ne 0 ]; then
echo "Vitest tests failed with status: $vitest_status"
if [ "$vitest_parallel_status" -ne 0 ]; then
echo "Vitest parallel tests failed with status: $vitest_parallel_status"
fi
if [ "$vitest_sequential_status" -ne 0 ]; then
echo "Vitest sequential tests failed with status: $vitest_sequential_status"
fi
exit 1

View File

@@ -4,7 +4,7 @@ import { useSwitchEnableNewEditorState } from '@/features/ide-redesign/hooks/use
import { useLayoutContext } from '@/shared/context/layout-context'
import { useCallback } from 'react'
import {
canUseNewEditorViaNewUserFeatureFlag,
canUseNewEditorAsNewUser,
useIsNewEditorEnabled,
} from '@/features/ide-redesign/utils/new-editor-utils'
@@ -13,7 +13,7 @@ export default function SettingsNewEditor() {
const { setEditorRedesignStatus } = useSwitchEnableNewEditorState()
const { setLeftMenuShown } = useLayoutContext()
const enabled = useIsNewEditorEnabled()
const show = canUseNewEditorViaNewUserFeatureFlag()
const show = canUseNewEditorAsNewUser()
const onChange = useCallback(
(newValue: boolean) => {

View File

@@ -2,13 +2,13 @@ import { useCallback } from 'react'
import OLButton from '../../shared/components/ol/ol-button'
import { useIdeRedesignSwitcherContext } from '../ide-react/context/ide-redesign-switcher-context'
import { useTranslation } from 'react-i18next'
import { canUseNewEditorViaPrimaryFeatureFlag } from '../ide-redesign/utils/new-editor-utils'
import { canUseNewEditorAsExistingUser } from '../ide-redesign/utils/new-editor-utils'
import { useSwitchEnableNewEditorState } from '../ide-redesign/hooks/use-switch-enable-new-editor-state'
const TryNewEditorButton = () => {
const { t } = useTranslation()
const { setShowSwitcherModal } = useIdeRedesignSwitcherContext()
const showModal = canUseNewEditorViaPrimaryFeatureFlag()
const showModal = canUseNewEditorAsExistingUser()
const { loading, setEditorRedesignStatus } = useSwitchEnableNewEditorState()
const onClick = useCallback(() => {

View File

@@ -11,7 +11,7 @@ import { FC, useCallback, useEffect } from 'react'
import {
canUseNewEditor,
useIsNewEditorEnabled,
useIsNewEditorEnabledViaPrimaryFeatureFlag,
useIsNewEditorEnabledAsExistingUser,
} from '../../utils/new-editor-utils'
import Notification from '@/shared/components/notification'
import { useSwitchEnableNewEditorState } from '../../hooks/use-switch-enable-new-editor-state'
@@ -32,7 +32,7 @@ export const IdeRedesignIntroModal: FC = () => {
name: TUTORIAL_KEY,
}
)
const hasAccess = useIsNewEditorEnabledViaPrimaryFeatureFlag()
const hasAccess = useIsNewEditorEnabledAsExistingUser()
useEffect(() => {
if (!hasAccess) return

View File

@@ -4,7 +4,7 @@ import OLTooltip from '@/shared/components/ol/ol-tooltip'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { GiveFeedbackLink } from './give-feedback-link'
import { useIsNewEditorEnabledViaPrimaryFeatureFlag } from '../../utils/new-editor-utils'
import { useIsNewEditorEnabledAsExistingUser } from '../../utils/new-editor-utils'
export const BetaActions = () => {
const { t } = useTranslation()
@@ -12,7 +12,7 @@ export const BetaActions = () => {
const openEditorRedesignSwitcherModal = useCallback(() => {
setShowSwitcherModal(true)
}, [setShowSwitcherModal])
const showBetaActions = useIsNewEditorEnabledViaPrimaryFeatureFlag()
const showBetaActions = useIsNewEditorEnabledAsExistingUser()
if (!showBetaActions) {
return null

View File

@@ -30,7 +30,7 @@ import { useSurveyUrl } from '../../hooks/use-survey-url'
import getMeta from '@/utils/meta'
import EditorCloneProjectModalWrapper from '@/features/clone-project-modal/components/editor-clone-project-modal-wrapper'
import useOpenProject from '@/shared/hooks/use-open-project'
import { canUseNewEditorViaPrimaryFeatureFlag } from '../../utils/new-editor-utils'
import { canUseNewEditorAsExistingUser } from '../../utils/new-editor-utils'
export const ToolbarMenuBar = () => {
const { t } = useTranslation()
@@ -44,7 +44,7 @@ export const ToolbarMenuBar = () => {
const [showWordCountModal, setShowWordCountModal] = useState(false)
const [showCloneProjectModal, setShowCloneProjectModal] = useState(false)
const openProject = useOpenProject()
const showEditorSwitchMenuOption = canUseNewEditorViaPrimaryFeatureFlag()
const showEditorSwitchMenuOption = canUseNewEditorAsExistingUser()
const anonymous = getMeta('ol-anonymous')

View File

@@ -25,7 +25,7 @@ import FontFamilySetting from '../components/settings/appearance-settings/font-f
import { AvailableUnfilledIcon } from '@/shared/components/material-icon'
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
import NewEditorSetting from '../components/settings/editor-settings/new-editor-setting'
import { canUseNewEditorViaNewUserFeatureFlag } from '../utils/new-editor-utils'
import { canUseNewEditorAsNewUser } from '../utils/new-editor-utils'
const [referenceSearchSettingModule] = importOverleafModules(
'referenceSearchSetting'
@@ -77,7 +77,7 @@ export const SettingsModalProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const { t } = useTranslation()
const showEditorSwitch = canUseNewEditorViaNewUserFeatureFlag()
const showEditorSwitch = canUseNewEditorAsNewUser()
// TODO ide-redesign-cleanup: Rename this field and move it directly into this context
const { leftMenuShown, setLeftMenuShown } = useLayoutContext()

View File

@@ -2,13 +2,24 @@ import { useUserSettingsContext } from '@/shared/context/user-settings-context'
import getMeta from '@/utils/meta'
import { isSplitTestEnabled, getSplitTestVariant } from '@/utils/splitTestUtils'
export const ignoringUserCutoffDate =
const ignoringUserCutoffDate =
new URLSearchParams(window.location.search).get('skip-new-user-check') ===
'true'
const NEW_USER_CUTOFF_DATE = new Date(Date.UTC(2025, 8, 23, 13, 0, 0)) // 2pm British Summer Time on September 23, 2025
// For E2E tests, allow forcing a user to be treated as an existing user
const existingUserOverride =
new URLSearchParams(window.location.search).get('existing-user-override') ===
'true'
// We don't want to enable the new editor on server-pro/CE until we have fully rolled it out on SaaS
const { isOverleaf } = getMeta('ol-ExposedSettings')
const SPLIT_TEST_USER_CUTOFF_DATE = new Date(Date.UTC(2025, 8, 23, 13, 0, 0)) // 2pm British Summer Time on September 23, 2025
const NEW_USER_CUTOFF_DATE = new Date(Date.UTC(2025, 10, 12, 12, 0, 0)) // 12pm GMT on November 12, 2025
export const isNewUser = () => {
if (existingUserOverride) return false
if (ignoringUserCutoffDate) return true
const user = getMeta('ol-user')
@@ -18,32 +29,37 @@ export const isNewUser = () => {
return createdAt > NEW_USER_CUTOFF_DATE
}
export const canUseNewEditorViaPrimaryFeatureFlag = () => {
return isSplitTestEnabled('editor-redesign')
export const isSplitTestUser = () => {
if (existingUserOverride) return false
const user = getMeta('ol-user')
if (!user.signUpDate) return false
const createdAt = new Date(user.signUpDate)
return (
createdAt > SPLIT_TEST_USER_CUTOFF_DATE && createdAt <= NEW_USER_CUTOFF_DATE
)
}
export const canUseNewEditorViaNewUserFeatureFlag = () => {
const newUserTestVariant = getSplitTestVariant('editor-redesign-new-users')
export const canUseNewEditorAsExistingUser = () => {
return !canUseNewEditorAsNewUser() && isSplitTestEnabled('editor-redesign')
}
export const canUseNewEditorAsNewUser = () => {
const newUserTestVariant = getSplitTestVariant('editor-redesign-new-users')
return (
!canUseNewEditorViaPrimaryFeatureFlag() &&
isNewUser() &&
(newUserTestVariant === 'new-editor' ||
newUserTestVariant === 'new-editor-old-logs' ||
newUserTestVariant === 'new-editor-new-logs-old-position')
isOverleaf &&
(isNewUser() || (isSplitTestUser() && newUserTestVariant !== 'default'))
)
}
export const canUseNewEditor = () => {
return (
canUseNewEditorViaPrimaryFeatureFlag() ||
canUseNewEditorViaNewUserFeatureFlag()
)
return canUseNewEditorAsExistingUser() || canUseNewEditorAsNewUser()
}
export const useIsNewEditorEnabledViaPrimaryFeatureFlag = () => {
export const useIsNewEditorEnabledAsExistingUser = () => {
const { userSettings } = useUserSettingsContext()
const hasAccess = canUseNewEditorViaPrimaryFeatureFlag()
const hasAccess = canUseNewEditorAsExistingUser()
const enabled = userSettings.enableNewEditor
return hasAccess && enabled
}
@@ -54,9 +70,3 @@ export const useIsNewEditorEnabled = () => {
const enabled = userSettings.enableNewEditor
return hasAccess && enabled
}
export function useNewEditorVariant() {
const newEditor = useIsNewEditorEnabled()
if (!newEditor) return 'default'
return 'new-editor-new-logs-old-position'
}

View File

@@ -8,12 +8,9 @@ import OLButton from '@/shared/components/ol/ol-button'
import * as eventTracking from '../../../infrastructure/event-tracking'
import getMeta from '@/utils/meta'
import { populateEditorRedesignSegmentation } from '@/shared/hooks/use-editor-analytics'
import {
isNewUser,
useIsNewEditorEnabled,
} from '@/features/ide-redesign/utils/new-editor-utils'
import { getSplitTestVariant, isSplitTestEnabled } from '@/utils/splitTestUtils'
import CompileTimeoutPaywallModal from '@/features/pdf-preview/components/compile-timeout-paywall-modal'
import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
function TimeoutUpgradePromptNew() {
const {
@@ -98,21 +95,12 @@ const CompileTimeout = memo(function CompileTimeout({
isCompileTimeoutTargetPlansEnabled,
}: CompileTimeoutProps) {
const { t } = useTranslation()
const newEditor = useIsNewEditorEnabled()
const extraSearchParams = useMemo(() => {
if (!isNewUser()) {
return undefined
}
const variant = getSplitTestVariant('editor-redesign-new-users')
if (!variant) {
return undefined
}
return {
itm_content: variant,
itm_content: newEditor ? 'new-editor' : 'old-editor',
}
}, [])
}, [newEditor])
const handleFreeTrialClick = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {

View File

@@ -41,10 +41,10 @@ function ProjectListTableRow({ project, selected }: ProjectListTableRowProps) {
<InlineTags projectId={project.id} />
</td>
<td className="dash-cell-actions">
<div className="d-none d-md-block">
<div className="d-none d-lg-block">
<ActionsCell project={project} />
</div>
<div className="d-md-none">
<div className="d-lg-none">
<ActionsDropdown project={project} />
</div>
</td>

View File

@@ -0,0 +1,60 @@
import { forwardRef, ReactNode } from 'react'
import { Button, ButtonProps } from 'react-bootstrap'
type DSButtonProps = Pick<
ButtonProps,
| 'children'
| 'disabled'
| 'href'
| 'id'
| 'target'
| 'rel'
| 'onClick'
| 'onMouseDown'
| 'onMouseOver'
| 'onMouseOut'
| 'onFocus'
| 'onBlur'
| 'size'
| 'active'
| 'type'
> & {
leadingIcon?: ReactNode
trailingIcon?: ReactNode
variant?: 'primary' | 'secondary' | 'tertiary' | 'danger'
}
const DSButton = forwardRef<HTMLButtonElement, DSButtonProps>(
(
{
children,
leadingIcon,
trailingIcon,
variant = 'primary',
size,
...props
},
ref
) => {
return (
<Button
className="d-inline-grid btn-ds"
variant={variant}
size={size}
{...props}
ref={ref}
role={undefined}
>
<span className="button-content">
{leadingIcon}
{children}
{trailingIcon}
</span>
</Button>
)
}
)
DSButton.displayName = 'DSButton'
export default DSButton

View File

@@ -0,0 +1,37 @@
import { Meta } from '@storybook/react'
import { figmaDesignUrl } from '../../../.storybook/utils/figma-design-url'
import DSButton from '@/shared/components/ds/ds-button'
type Args = React.ComponentProps<typeof DSButton>
export const Button = (args: Args) => {
return <DSButton {...args} />
}
const meta: Meta<typeof DSButton> = {
title: 'Shared / Components / DS Button',
component: DSButton,
args: {
children: 'Button',
},
argTypes: {
size: {
control: 'radio',
options: ['lg', 'md', 'sm'],
},
variant: {
control: 'radio',
options: ['primary', 'secondary', 'tertiary', 'danger'],
},
},
parameters: {
controls: {
include: ['children', 'disabled', 'size', 'variant'],
},
...figmaDesignUrl(
'https://www.figma.com/design/aJQlecvqCS9Ry8b6JA1lQN/DS---Components?node-id=4565-2932&m=dev'
),
},
}
export default meta

View File

@@ -21,7 +21,8 @@
$borderless: true,
$disabled-color: $content-disabled,
$disabled-background: $bg-light-disabled,
$disabled-border: $bg-light-disabled
$disabled-border: $disabled-background,
$active-background: $hover-background
) {
--bs-btn-color: #{$color};
--bs-btn-bg: #{$background};
@@ -30,7 +31,7 @@
--bs-btn-hover-bg: #{$hover-background};
--bs-btn-hover-border-color: #{$hover-border};
--bs-btn-active-color: #{$color};
--bs-btn-active-bg: #{$hover-background};
--bs-btn-active-bg: #{$active-background};
--bs-btn-active-border-color: #{$hover-border};
--bs-btn-disabled-color: #{$disabled-color};
--bs-btn-disabled-bg: #{$disabled-background};

View File

@@ -1,5 +1,3 @@
@import 'ciam-colors';
@import '../ds/all'; // DS design system styles
@import 'ciam-layout';
@import 'ciam-mixins';
@import 'ciam-spacing';
@import 'ciam-variables';

View File

@@ -1,68 +0,0 @@
.ciam-enabled {
--ciam-color-neutral-50: #fafafa;
--ciam-color-neutral-100: #f2f2f2;
--ciam-color-neutral-200: #e6e6e6;
--ciam-color-neutral-300: #d6d6d6;
--ciam-color-neutral-400: #c7c7c7;
--ciam-color-neutral-500: #b5b5b5;
--ciam-color-neutral-600: #a1a1a1;
--ciam-color-neutral-700: #8a8a8a;
--ciam-color-neutral-800: #6b6b6b;
--ciam-color-neutral-900: #383838;
--ciam-color-neutral-950: #262626;
--ciam-color-green-50: #f2f8f5;
--ciam-color-green-100: #e3f2eb;
--ciam-color-green-200: #c0e7d6;
--ciam-color-green-300: #8adbb7;
--ciam-color-green-400: #38cc89;
--ciam-color-green-500: #26b072;
--ciam-color-green-600: #158954;
--ciam-color-green-700: #19754c;
--ciam-color-green-800: #196241;
--ciam-color-green-900: #164630;
--ciam-color-green-950: #112c20;
--ciam-color-yellow-50: #fffaeb;
--ciam-color-yellow-100: #fff7db;
--ciam-color-yellow-200: #ffeeb8;
--ciam-color-yellow-300: #ffe58f;
--ciam-color-yellow-400: #ffda61;
--ciam-color-yellow-500: #ffcc20;
--ciam-color-yellow-600: #f0b800;
--ciam-color-yellow-700: #d1a000;
--ciam-color-yellow-800: #ad8500;
--ciam-color-yellow-900: #806200;
--ciam-color-yellow-950: #574200;
--ciam-color-red-50: #fff5f7;
--ciam-color-red-100: #fee7eb;
--ciam-color-red-200: #fdc9d3;
--ciam-color-red-300: #fba7b7;
--ciam-color-red-400: #f97b92;
--ciam-color-red-500: #f51d43;
--ciam-color-red-600: #e60a32;
--ciam-color-red-700: #c3092b;
--ciam-color-red-800: #a10723;
--ciam-color-red-900: #75051a;
--ciam-color-red-950: #530412;
--ciam-color-blue-50: #f7fafd;
--ciam-color-blue-100: #ecf2f9;
--ciam-color-blue-200: #d4e3f2;
--ciam-color-blue-300: #bdd4ea;
--ciam-color-blue-400: #a2c3e2;
--ciam-color-blue-500: #7facd7;
--ciam-color-blue-600: #5893cb;
--ciam-color-blue-700: #3470a8;
--ciam-color-blue-800: #2b5d8c;
--ciam-color-blue-900: #1e4161;
--ciam-color-blue-950: #17314a;
--ciam-color-teal-50: #f1f8f8;
--ciam-color-teal-100: #e3f2f1;
--ciam-color-teal-200: #c1e2df;
--ciam-color-teal-300: #9ed1cd;
--ciam-color-teal-400: #6dbab4;
--ciam-color-teal-500: #4a9d96;
--ciam-color-teal-600: #438e88;
--ciam-color-teal-700: #3b7d77;
--ciam-color-teal-800: #2f6460;
--ciam-color-teal-900: #244c49;
--ciam-color-teal-950: #193432;
}

View File

@@ -1,18 +1,16 @@
@import 'ciam-mixins';
.ciam-layout {
padding: var(--ciam-spacing-350);
padding: var(--ds-spacing-350);
display: flex;
min-height: 100vh;
flex-direction: column;
@include ciam-body-md-regular;
@include ds-body-md-regular;
}
.ciam-enabled {
font-family: var(--ciam-font-family-sans), sans-serif;
color: var(--ciam-color-text-primary);
font-size: var(--ciam-font-size-400);
font-family: var(--ds-font-family-sans), sans-serif;
color: var(--ds-color-text-primary);
font-size: var(--ds-font-size-400);
line-height: 1.5;
.ciam-container {
@@ -25,48 +23,48 @@
background-size: contain;
height: 64px;
width: 130px;
margin: var(--ciam-spacing-350) auto;
margin: var(--ds-spacing-350) auto;
display: block;
@include media-breakpoint-up(sm) {
margin: var(--ciam-spacing-350) var(--ciam-spacing-800);
margin: var(--ds-spacing-350) var(--ds-spacing-800);
}
}
h1 {
@include ciam-heading-sm-semibold;
@include ds-heading-sm-semibold;
}
.ciam-card {
box-shadow:
0 4px 6px -4px rgb(0 0 0 / 10%),
0 1px 29px -3px rgb(0 0 0 / 16%);
padding: var(--ciam-spacing-800) var(--ciam-spacing-400);
border-radius: var(--ciam-border-radius-400);
padding: var(--ds-spacing-800) var(--ds-spacing-400);
border-radius: var(--ds-border-radius-400);
max-width: 460px;
margin: var(--ciam-spacing-400) auto;
margin: var(--ds-spacing-400) auto;
@include media-breakpoint-up(sm) {
padding: var(--ciam-spacing-1300);
padding: var(--ds-spacing-1300);
}
}
footer {
display: flex;
gap: var(--ciam-spacing-600);
gap: var(--ds-spacing-600);
text-transform: uppercase;
justify-content: center;
margin: var(--ciam-spacing-350) auto;
margin: var(--ds-spacing-350) auto;
@include media-breakpoint-up(sm) {
margin: var(--ciam-spacing-350) var(--ciam-spacing-800);
margin: var(--ds-spacing-350) var(--ds-spacing-800);
justify-content: start;
}
a {
text-decoration: none;
@include ciam-body-sm-regular;
@include ds-body-sm-regular;
}
}
}

View File

@@ -1,107 +0,0 @@
@mixin ciam-body-xs-regular() {
font-size: 0.75rem;
font-weight: 400;
line-height: 1.25rem;
}
@mixin ciam-body-xs-semibold() {
font-size: 0.75rem;
font-weight: 600;
line-height: 1.25rem;
}
@mixin ciam-body-sm-regular() {
font-size: 0.875rem;
font-weight: 400;
line-height: 1.25rem;
}
@mixin ciam-body-sm-semibold() {
font-size: 0.875rem;
font-weight: 600;
line-height: 1.25rem;
}
@mixin ciam-body-md-regular() {
font-size: 1rem;
font-weight: 400;
line-height: 1.5rem;
}
@mixin ciam-body-md-semibold() {
font-size: 1rem;
font-weight: 600;
line-height: 1.5rem;
}
@mixin ciam-body-lg-regular() {
font-size: 1.125rem;
font-weight: 400;
line-height: 1.75rem;
}
@mixin ciam-body-lg-semibold() {
font-size: 1.125rem;
font-weight: 600;
line-height: 1.75rem;
}
@mixin ciam-heading-xs-regular() {
font-size: 1.125rem;
font-weight: 400;
line-height: 1.5rem;
}
@mixin ciam-heading-xs-semibold() {
font-size: 1.125rem;
font-weight: 600;
line-height: 1.5rem;
}
@mixin ciam-heading-sm-regular() {
font-size: 1.25rem;
font-weight: 400;
line-height: 1.75rem;
}
@mixin ciam-heading-sm-semibold() {
font-size: 1.25rem;
font-weight: 600;
line-height: 1.75rem;
}
@mixin ciam-heading-md-regular() {
font-size: 1.5rem;
font-weight: 400;
line-height: 2rem;
}
@mixin ciam-heading-md-semibold() {
font-size: 1.5rem;
font-weight: 600;
line-height: 2rem;
}
@mixin ciam-heading-lg-regular() {
font-size: 2rem;
font-weight: 400;
line-height: 2.5rem;
}
@mixin ciam-heading-lg-semibold() {
font-size: 2rem;
font-weight: 600;
line-height: 2.5rem;
}
@mixin ciam-heading-xl-regular() {
font-size: 2.5rem;
font-weight: 400;
line-height: 3rem;
}
@mixin ciam-heading-xl-semibold() {
font-size: 2.5rem;
font-weight: 600;
line-height: 3rem;
}

View File

@@ -1,66 +0,0 @@
.ciam-enabled {
--ciam-font-weight-regular: 400;
--ciam-font-weight-medium: 500;
--ciam-font-weight-semibold: 600;
--ciam-font-weight-bold: 700;
--ciam-font-family-sans: inter, sans-serif;
--ciam-spacing-50: 2px;
--ciam-spacing-100: 4px;
--ciam-spacing-150: 6px;
--ciam-spacing-200: 8px;
--ciam-spacing-250: 10px;
--ciam-spacing-300: 12px;
--ciam-spacing-350: 14px;
--ciam-spacing-400: 16px;
--ciam-spacing-500: 20px;
--ciam-spacing-600: 24px;
--ciam-spacing-700: 28px;
--ciam-spacing-800: 32px;
--ciam-spacing-900: 36px;
--ciam-spacing-1000: 40px;
--ciam-spacing-1100: 44px;
--ciam-spacing-1200: 48px;
--ciam-spacing-1300: 52px;
--ciam-spacing-1400: 56px;
--ciam-spacing-1500: 60px;
--ciam-spacing-1600: 64px;
--ciam-spacing-1700: 68px;
--ciam-spacing-1800: 72px;
--ciam-spacing-1900: 76px;
--ciam-spacing-2000: 80px;
--ciam-spacing-2100: 84px;
--ciam-spacing-2200: 88px;
--ciam-spacing-2300: 92px;
--ciam-spacing-2400: 96px;
--ciam-base-unit: 4px;
--ciam-font-size-300: 12px;
--ciam-font-size-350: 14px;
--ciam-font-size-400: 16px;
--ciam-font-size-450: 18px;
--ciam-font-size-500: 20px;
--ciam-font-size-600: 24px;
--ciam-font-size-700: 28px;
--ciam-font-size-800: 32px;
--ciam-font-size-1000: 40px;
--ciam-font-size-1400: 56px;
--ciam-font-size-1800: 72px;
--ciam-font-line-height-400: 16px;
--ciam-font-line-height-500: 20px;
--ciam-font-line-height-600: 24px;
--ciam-font-line-height-700: 28px;
--ciam-font-line-height-800: 32px;
--ciam-font-line-height-900: 36px;
--ciam-font-line-height-1000: 40px;
--ciam-font-line-height-1200: 48px;
--ciam-font-line-height-1600: 64px;
--ciam-font-line-height-1800: 72px;
--ciam-border-width-25: 1px;
--ciam-border-width-50: 2px;
--ciam-border-radius-50: 2px;
--ciam-border-radius-100: 4px;
--ciam-border-radius-200: 8px;
--ciam-border-radius-300: 12px;
--ciam-border-radius-400: 16px;
--ciam-border-radius-600: 24px;
--ciam-border-radius-full: 9999px;
}

View File

@@ -1,17 +1,13 @@
// TODO: Replace `fuchsia` by the correct colors.
.ciam-enabled {
// Base variables
--ciam-color-text-secondary: var(--ciam-color-neutral-800);
--ciam-color-text-primary: var(--ciam-color-neutral-900);
// Links
// used in services/web/frontend/stylesheets/base/links.scss
--link-color: var(--ciam-color-text-secondary);
--link-color: var(--ds-color-text-secondary);
--link-hover-color: fuchsia;
// TODO: validate that this is correct
--link-visited-color: var(--ciam-color-text-secondary);
--link-visited-color: var(--ds-color-text-secondary);
--link-color-dark: fuchsia;
--link-hover-color-dark: fuchsia;
--link-visited-color-dark: fuchsia;

View File

@@ -0,0 +1,7 @@
@import 'colors';
@import 'typography';
@import 'spacing';
@import 'border';
@import 'theme';
@import 'mixins';
@import 'components/all';

View File

@@ -0,0 +1,13 @@
:root {
--ds-border-width-25: 1px;
--ds-border-width-50: 2px;
// Border radius
--ds-border-radius-full: 9999px;
--ds-border-radius-50: 2px;
--ds-border-radius-100: 4px;
--ds-border-radius-200: 8px;
--ds-border-radius-300: 12px;
--ds-border-radius-400: 16px;
--ds-border-radius-600: 24px;
}

View File

@@ -0,0 +1,83 @@
:root {
// Neutral/grey
--ds-color-neutral-50: #fafafa;
--ds-color-neutral-100: #f2f2f2;
--ds-color-neutral-200: #e6e6e6;
--ds-color-neutral-300: #d6d6d6;
--ds-color-neutral-400: #c7c7c7;
--ds-color-neutral-500: #b5b5b5;
--ds-color-neutral-600: #a1a1a1;
--ds-color-neutral-700: #8a8a8a;
--ds-color-neutral-800: #6b6b6b;
--ds-color-neutral-900: #383838;
--ds-color-neutral-950: #262626;
// Green
--ds-color-green-50: #f2f8f5;
--ds-color-green-100: #e3f2eb;
--ds-color-green-200: #c0e7d6;
--ds-color-green-300: #8adbb7;
--ds-color-green-400: #38cc89;
--ds-color-green-500: #26b072;
--ds-color-green-600: #158954;
--ds-color-green-700: #19754c;
--ds-color-green-800: #196241;
--ds-color-green-900: #164630;
--ds-color-green-950: #112c20;
// Yellow
--ds-color-yellow-50: #fffaeb;
--ds-color-yellow-100: #fff7db;
--ds-color-yellow-200: #ffeeb8;
--ds-color-yellow-300: #ffe58f;
--ds-color-yellow-400: #ffda61;
--ds-color-yellow-500: #ffcc20;
--ds-color-yellow-600: #f0b800;
--ds-color-yellow-700: #d1a000;
--ds-color-yellow-800: #ad8500;
--ds-color-yellow-900: #806200;
--ds-color-yellow-950: #574200;
// Red
--ds-color-red-50: #fff5f7;
--ds-color-red-100: #fee7eb;
--ds-color-red-200: #fdc9d3;
--ds-color-red-300: #fba7b7;
--ds-color-red-400: #f97b92;
--ds-color-red-500: #f51d43;
--ds-color-red-600: #e60a32;
--ds-color-red-700: #c3092b;
--ds-color-red-800: #a10723;
--ds-color-red-900: #75051a;
--ds-color-red-950: #530412;
// Blue
--ds-color-blue-50: #f7fafd;
--ds-color-blue-100: #ecf2f9;
--ds-color-blue-200: #d4e3f2;
--ds-color-blue-300: #bdd4ea;
--ds-color-blue-400: #a2c3e2;
--ds-color-blue-500: #7facd7;
--ds-color-blue-600: #5893cb;
--ds-color-blue-700: #3470a8;
--ds-color-blue-800: #2b5d8c;
--ds-color-blue-900: #1e4161;
--ds-color-blue-950: #17314a;
// Teal
--ds-color-teal-50: #f1f8f8;
--ds-color-teal-100: #e3f2f1;
--ds-color-teal-200: #c1e2df;
--ds-color-teal-300: #9ed1cd;
--ds-color-teal-400: #6dbab4;
--ds-color-teal-500: #4a9d96;
--ds-color-teal-600: #438e88;
--ds-color-teal-700: #3b7d77;
--ds-color-teal-800: #2f6460;
--ds-color-teal-900: #244c49;
--ds-color-teal-950: #193432;
// Black and white
--ds-color-white: #fff;
--ds-color-black: #000;
}

View File

@@ -0,0 +1 @@
@import 'button';

View File

@@ -0,0 +1,81 @@
.btn.btn-ds {
--bs-btn-font-family: var(--ds-font-family-sans);
--bs-btn-border-radius: var(--ds-border-radius-200);
&:focus-visible {
outline: 2px solid var(--ds-color-yellow-500);
outline-offset: 2px;
box-shadow: none;
}
// Default size
@include ol-button-size(
$font-size: var(--ds-font-size-400),
$line-height: var(--ds-font-line-height-600),
$padding-x: var(--ds-spacing-400),
$padding-y: var(--ds-spacing-100)
);
&.btn-lg {
@include ol-button-size(
$font-size: var(--ds-font-size-400),
$line-height: var(--ds-font-line-height-600),
$padding-x: var(--ds-spacing-400),
$padding-y: var(--ds-spacing-250)
);
}
&.btn-sm {
@include ol-button-size(
$font-size: var(--ds-font-size-350),
$line-height: var(--ds-font-line-height-500),
$padding-x: var(--ds-spacing-200),
$padding-y: var(--ds-spacing-100)
);
}
// Variants
&.btn-primary {
@include ol-button-variant(
$color: var(--ds-color-white),
$background: var(--ds-color-neutral-950),
$hover-background: var(--ds-color-neutral-900),
$active-background: var(--ds-color-neutral-800),
$disabled-color: var(--ds-color-neutral-700),
$disabled-background: var(--ds-color-neutral-100)
);
}
&.btn-secondary {
@include ol-button-variant(
$color: var(--ds-color-neutral-950),
$background: var(--ds-color-neutral-200),
$hover-background: var(--ds-color-neutral-300),
$active-background: var(--ds-color-neutral-400),
$disabled-color: var(--ds-color-neutral-700),
$disabled-background: var(--ds-color-neutral-100)
);
}
&.btn-tertiary {
@include ol-button-variant(
$color: var(--ds-color-neutral-950),
$background: var(--ds-color-white),
$hover-background: var(--ds-color-neutral-100),
$active-background: var(--ds-color-neutral-200),
$disabled-color: var(--ds-color-neutral-700),
$disabled-background: var(--ds-color-neutral-100)
);
}
&.btn-danger {
@include ol-button-variant(
$color: var(--ds-color-white),
$background: var(--ds-color-red-700),
$hover-background: var(--ds-color-red-800),
$active-background: var(--ds-color-red-900),
$disabled-color: var(--ds-color-neutral-700),
$disabled-background: var(--ds-color-neutral-100)
);
}
}

View File

@@ -0,0 +1,109 @@
// Typography mixins for consistent text styles
@mixin ds-body-xs-regular() {
font-size: var(--ds-font-size-300);
font-weight: var(--ds-font-weight-regular);
line-height: var(--ds-font-line-height-500);
}
@mixin ds-body-xs-semibold() {
font-size: var(--ds-font-size-300);
font-weight: var(--ds-font-weight-semibold);
line-height: var(--ds-font-line-height-500);
}
@mixin ds-body-sm-regular() {
font-size: var(--ds-font-size-350);
font-weight: var(--ds-font-weight-regular);
line-height: var(--ds-font-line-height-500);
}
@mixin ds-body-sm-semibold() {
font-size: var(--ds-font-size-350);
font-weight: var(--ds-font-weight-semibold);
line-height: var(--ds-font-line-height-500);
}
@mixin ds-body-md-regular() {
font-size: var(--ds-font-size-400);
font-weight: var(--ds-font-weight-regular);
line-height: var(--ds-font-line-height-600);
}
@mixin ds-body-md-semibold() {
font-size: var(--ds-font-size-400);
font-weight: var(--ds-font-weight-semibold);
line-height: var(--ds-font-line-height-600);
}
@mixin ds-body-lg-regular() {
font-size: var(--ds-font-size-450);
font-weight: var(--ds-font-weight-regular);
line-height: var(--ds-font-line-height-700);
}
@mixin ds-body-lg-semibold() {
font-size: var(--ds-font-size-450);
font-weight: var(--ds-font-weight-semibold);
line-height: var(--ds-font-line-height-700);
}
@mixin ds-heading-xs-regular() {
font-size: var(--ds-font-size-450);
font-weight: var(--ds-font-weight-regular);
line-height: var(--ds-font-line-height-600);
}
@mixin ds-heading-xs-semibold() {
font-size: var(--ds-font-size-450);
font-weight: var(--ds-font-weight-semibold);
line-height: var(--ds-font-line-height-600);
}
@mixin ds-heading-sm-regular() {
font-size: var(--ds-font-size-500);
font-weight: var(--ds-font-weight-regular);
line-height: var(--ds-font-line-height-700);
}
@mixin ds-heading-sm-semibold() {
font-size: var(--ds-font-size-500);
font-weight: var(--ds-font-weight-semibold);
line-height: var(--ds-font-line-height-700);
}
@mixin ds-heading-md-regular() {
font-size: var(--ds-font-size-600);
font-weight: var(--ds-font-weight-regular);
line-height: var(--ds-font-line-height-800);
}
@mixin ds-heading-md-semibold() {
font-size: var(--ds-font-size-600);
font-weight: var(--ds-font-weight-semibold);
line-height: var(--ds-font-line-height-800);
}
@mixin ds-heading-lg-regular() {
font-size: var(--ds-font-size-800);
font-weight: var(--ds-font-weight-regular);
line-height: var(--ds-font-line-height-1000);
}
@mixin ds-heading-lg-semibold() {
font-size: var(--ds-font-size-800);
font-weight: var(--ds-font-weight-semibold);
line-height: var(--ds-font-line-height-1000);
}
@mixin ds-heading-xl-regular() {
font-size: var(--ds-font-size-1000);
font-weight: var(--ds-font-weight-regular);
line-height: var(--ds-font-line-height-1200);
}
@mixin ds-heading-xl-semibold() {
font-size: var(--ds-font-size-1000);
font-weight: var(--ds-font-weight-semibold);
line-height: var(--ds-font-line-height-1200);
}

View File

@@ -0,0 +1,31 @@
:root {
--ds-base-unit: 4px;
--ds-spacing-50: 2px;
--ds-spacing-100: 4px;
--ds-spacing-150: 6px;
--ds-spacing-200: 8px;
--ds-spacing-250: 10px;
--ds-spacing-300: 12px;
--ds-spacing-350: 14px;
--ds-spacing-400: 16px;
--ds-spacing-500: 20px;
--ds-spacing-600: 24px;
--ds-spacing-700: 28px;
--ds-spacing-800: 32px;
--ds-spacing-900: 36px;
--ds-spacing-1000: 40px;
--ds-spacing-1100: 44px;
--ds-spacing-1200: 48px;
--ds-spacing-1300: 52px;
--ds-spacing-1400: 56px;
--ds-spacing-1500: 60px;
--ds-spacing-1600: 64px;
--ds-spacing-1700: 68px;
--ds-spacing-1800: 72px;
--ds-spacing-1900: 76px;
--ds-spacing-2000: 80px;
--ds-spacing-2100: 84px;
--ds-spacing-2200: 88px;
--ds-spacing-2300: 92px;
--ds-spacing-2400: 96px;
}

View File

@@ -0,0 +1,30 @@
:root {
// Default to the light theme
--ds-color-bg-default: var(--ds-color-white);
--ds-color-text-primary: var(--ds-color-neutral-900);
--ds-color-text-secondary: var(--ds-color-neutral-800);
--ds-color-text-disabled: var(--ds-color-neutral-500);
--ds-color-text-danger-default: var(--ds-color-red-700);
--ds-color-text-danger-hover: var(--ds-color-red-800);
--ds-color-text-warning-default: var(--ds-color-yellow-900);
--ds-color-text-warning-hover: var(--ds-color-yellow-950);
--ds-color-text-info-default: var(--ds-color-blue-700);
--ds-color-text-info-hover: var(--ds-color-blue-800);
--ds-color-text-success-default: var(--ds-color-green-700);
--ds-color-text-success-hover: var(--ds-color-green-800);
.ds-theme-dark {
--ds-color-bg-default: var(--ds-color-neutral-950);
--ds-color-text-primary: var(--ds-color-white);
--ds-color-text-secondary: var(--ds-color-neutral-50);
--ds-color-text-disabled: var(--ds-color-neutral-500);
--ds-color-text-danger-default: var(--ds-color-red-700);
--ds-color-text-danger-hover: var(--ds-color-red-800);
--ds-color-text-warning-default: var(--ds-color-yellow-900);
--ds-color-text-warning-hover: var(--ds-color-yellow-950);
--ds-color-text-info-default: var(--ds-color-blue-700);
--ds-color-text-info-hover: var(--ds-color-blue-800);
--ds-color-text-success-default: var(--ds-color-green-700);
--ds-color-text-success-hover: var(--ds-color-green-800);
}
}

View File

@@ -0,0 +1,34 @@
:root {
--ds-font-family-sans: 'Inter';
// Font weight
--ds-font-weight-regular: 400;
--ds-font-weight-medium: 500;
--ds-font-weight-semibold: 600;
--ds-font-weight-bold: 700;
// Font size
--ds-font-size-300: 0.75rem; // 12px
--ds-font-size-350: 0.875rem; // 14px
--ds-font-size-400: 1rem; // 16px
--ds-font-size-450: 1.125rem; // 18px
--ds-font-size-500: 1.25rem; // 20px
--ds-font-size-600: 1.5rem; // 24px
--ds-font-size-700: 1.75rem; // 28px
--ds-font-size-800: 2rem; // 32px
--ds-font-size-1000: 2.5rem; // 40px
--ds-font-size-1400: 3.5rem; // 56px
--ds-font-size-1800: 4.5rem; // 72px
// Line height
--ds-font-line-height-400: 1rem; // 16px
--ds-font-line-height-500: 1.25rem; // 20px
--ds-font-line-height-600: 1.5rem; // 24px
--ds-font-line-height-700: 1.75rem; // 28px
--ds-font-line-height-800: 2rem; // 32px
--ds-font-line-height-1000: 2.5rem; // 40px
--ds-font-line-height-1200: 3rem; // 48px
--ds-font-line-height-1600: 4rem; // 64px
--ds-font-line-height-1800: 4.5rem; // 72px
--ds-font-line-height-900: 2.25rem; // 36px
}

View File

@@ -1,86 +0,0 @@
import { expect } from 'chai'
import { mockScope } from './scope'
import { EditorProviders } from '../../helpers/editor-providers'
import { renderHook } from '@testing-library/react'
import { useNewEditorVariant } from '@/features/ide-redesign/utils/new-editor-utils'
describe('new-editor-utils', function () {
describe('useNewEditorVariant', function () {
const newEditorVariants = [
{ splitTestVariant: 'default', uiVariant: 'default' },
{
splitTestVariant: 'new-editor',
uiVariant: 'new-editor-new-logs-old-position',
},
{
splitTestVariant: 'new-editor-old-logs',
uiVariant: 'new-editor-new-logs-old-position',
},
{
splitTestVariant: 'new-editor-new-logs-old-position',
uiVariant: 'new-editor-new-logs-old-position',
},
]
for (const variant of newEditorVariants) {
it(`forwards ?editor-redesign-new-users=${variant}`, function () {
window.metaAttributesCache.set('ol-splitTestVariants', {
'editor-redesign-new-users': variant.splitTestVariant,
})
const scope = mockScope()
const { result } = renderHook(() => useNewEditorVariant(), {
wrapper: ({ children }) => (
<EditorProviders
scope={scope}
userSettings={{ enableNewEditor: true }}
>
{children}
</EditorProviders>
),
})
expect(result.current).to.equal(variant.uiVariant)
})
}
for (const variant of newEditorVariants) {
it(`ignores ?editor-redesign-new-users=${variant} when disabled by user`, function () {
window.metaAttributesCache.set('ol-splitTestVariants', {
'editor-redesign-new-users': variant.splitTestVariant,
})
const scope = mockScope()
const { result } = renderHook(() => useNewEditorVariant(), {
wrapper: ({ children }) => (
<EditorProviders
scope={scope}
userSettings={{ enableNewEditor: false }}
>
{children}
</EditorProviders>
),
})
expect(result.current).to.equal('default')
})
}
it(`handles ?editor-redesign=enabled`, function () {
window.metaAttributesCache.set('ol-splitTestVariants', {
'editor-redesign': 'enabled',
})
const scope = mockScope()
const { result } = renderHook(() => useNewEditorVariant(), {
wrapper: ({ children }) => (
<EditorProviders
scope={scope}
userSettings={{ enableNewEditor: true }}
>
{children}
</EditorProviders>
),
})
expect(result.current).to.equal('new-editor-new-logs-old-position')
})
})
})

View File

@@ -499,8 +499,9 @@ describe('TeamInvitesHandler', function () {
{ id: ctx.user.id, ip: ctx.ipAddress }
)
sinon.assert.calledWith(
ctx.RecurlyClient.promises.terminateSubscriptionByUuid,
ctx.user_subscription.recurlySubscription_id
ctx.Modules.promises.hooks.fire,
'terminateSubscription',
ctx.user_subscription
)
sinon.assert.calledWith(
ctx.Modules.promises.hooks.fire,